1
0
mirror of https://github.com/danog/loop.git synced 2024-11-30 04:19:04 +01:00

Finalize tests

This commit is contained in:
Daniil Gentili 2020-07-23 13:26:16 +02:00
parent aa6250b05a
commit ee04355a63
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
17 changed files with 625 additions and 96 deletions

View File

@ -22,9 +22,6 @@ before_install:
install: install:
- composer update -n --prefer-dist - composer update -n --prefer-dist
- mkdir -p coverage/cov coverage/bin
- wget https://phar.phpunit.de/phpcov.phar -O coverage/bin/phpcov
- chmod +x coverage/bin/phpcov
script: script:
- vendor/bin/phpunit --coverage-text --coverage-clover build/logs/clover.xml - vendor/bin/phpunit --coverage-text --coverage-clover build/logs/clover.xml
@ -32,10 +29,9 @@ script:
- vendor/bin/psalm - vendor/bin/psalm
after_script: after_script:
- curl -OL https://github.com/php-coveralls/php-coveralls/releases/download/v1.0.0/coveralls.phar - curl -OL https://github.com/php-coveralls/php-coveralls/releases/download/v2.2.0/php-coveralls.phar
- chmod +x coveralls.phar - chmod +x php-coveralls.phar
- phpdbg -qrr coverage/bin/phpcov merge --clover build/logs/clover.xml coverage/cov - ./php-coveralls.phar -v
- ./coveralls.phar
cache: cache:
directories: directories:

View File

@ -1,6 +1,6 @@
# IPC # IPC
[![Build Status](https://img.shields.io/travis/danog/loop/master.svg?style=flat-square)](https://travis-ci.com/danog/loop) [![Build Status](https://travis-ci.com/danog/loop.svg?branch=master)](https://travis-ci.com/danog/loop)
![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square) ![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)
`danog/loop` provides a very useful set of async loop APIs based on [AMPHP](https://amphp.org), for executing operations periodically or on demand, in background loops a-la threads. `danog/loop` provides a very useful set of async loop APIs based on [AMPHP](https://amphp.org), for executing operations periodically or on demand, in background loops a-la threads.

View File

@ -43,6 +43,6 @@
], ],
"cs": "php-cs-fixer fix -v --diff --dry-run", "cs": "php-cs-fixer fix -v --diff --dry-run",
"cs-fix": "php-cs-fixer fix -v --diff", "cs-fix": "php-cs-fixer fix -v --diff",
"test": "@php -dzend.assertions=1 -dassert.exception=1 ./vendor/bin/phpunit --coverage-text" "test": "phpdbg -qrr -dzend.assertions=1 -dassert.exception=1 ./vendor/bin/phpunit --coverage-text"
} }
} }

View File

@ -20,6 +20,12 @@ use danog\Loop\ResumableSignalLoop;
* The loop can be stopped from the outside or * The loop can be stopped from the outside or
* from the inside by signaling or returning `true`. * from the inside by signaling or returning `true`.
* *
* @template T as bool
* @template TGenerator as \Generator<mixed,Promise|array<array-key,Promise>,mixed,Promise<T>|T>
* @template TPromise as Promise<T>
*
* @template TCallable as T|TPromise|TGenerator
*
* @author Daniil Gentili <daniil@daniil.it> * @author Daniil Gentili <daniil@daniil.it>
*/ */
class PeriodicLoop extends ResumableSignalLoop class PeriodicLoop extends ResumableSignalLoop
@ -28,6 +34,8 @@ class PeriodicLoop extends ResumableSignalLoop
* Callback. * Callback.
* *
* @var callable * @var callable
*
* @psalm-var callable():TCallable
*/ */
private $callback; private $callback;
/** /**
@ -48,6 +56,8 @@ class PeriodicLoop extends ResumableSignalLoop
* @param callable $callback Callback to call * @param callable $callback Callback to call
* @param string $name Loop name * @param string $name Loop name
* @param ?int $interval Loop interval * @param ?int $interval Loop interval
*
* @psalm-param callable():TCallable $callback Callable to run
*/ */
public function __construct(callable $callback, string $name, ?int $interval) public function __construct(callable $callback, string $name, ?int $interval)
{ {
@ -65,16 +75,21 @@ class PeriodicLoop extends ResumableSignalLoop
$callback = $this->callback; $callback = $this->callback;
while (true) { while (true) {
/** @psalm-suppress MixedAssignment */ /** @psalm-suppress MixedAssignment */
$result = yield $this->waitSignal($this->pause($this->interval)); $result = $callback();
if ($result) { if ($result instanceof \Generator) {
/** @psalm-var TGenerator */
$result = yield from $result;
} elseif ($result instanceof Promise) {
/** @psalm-var TPromise */
$result = yield $result;
}
if ($result === true) {
return; return;
} }
/** @psalm-suppress MixedAssignment */ /** @psalm-suppress MixedAssignment */
$result = $callback(); $result = yield $this->waitSignal($this->pause($this->interval));
if ($result instanceof \Generator) { if ($result === true) {
yield from $result; return;
} elseif ($result instanceof Promise) {
yield $result;
} }
} }
} }

View File

@ -26,7 +26,7 @@ abstract class Fixtures extends AsyncTestCase
* *
* @return boolean * @return boolean
*/ */
public static function isResolved(Promise $promise): bool protected static function isResolved(Promise $promise): bool
{ {
$resolved = false; $resolved = false;
$promise->onResolve(static function ($e, $res) use (&$resolved) { $promise->onResolve(static function ($e, $res) use (&$resolved) {
@ -44,7 +44,7 @@ abstract class Fixtures extends AsyncTestCase
* *
* @return void * @return void
*/ */
public function assertPreStart(BasicInterface $loop) protected function assertPreStart(BasicInterface $loop)
{ {
$this->assertEquals(self::LOOP_NAME, "$loop"); $this->assertEquals(self::LOOP_NAME, "$loop");
@ -64,7 +64,7 @@ abstract class Fixtures extends AsyncTestCase
* *
* @return void * @return void
*/ */
public function assertAfterStart(BasicInterface $loop, bool $running = true) protected function assertAfterStart(BasicInterface $loop, bool $running = true)
{ {
$this->assertTrue($loop->inited()); $this->assertTrue($loop->inited());
@ -85,7 +85,7 @@ abstract class Fixtures extends AsyncTestCase
* *
* @return void * @return void
*/ */
public function assertFinal(BasicInterface $loop) protected function assertFinal(BasicInterface $loop)
{ {
$this->assertTrue($loop->ran()); $this->assertTrue($loop->ran());
$this->assertFalse($loop->isRunning()); $this->assertFalse($loop->isRunning());

187
test/GenericTest.php Normal file
View File

@ -0,0 +1,187 @@
<?php
/**
* Loop test.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/MIT MIT
*/
namespace danog\Loop\Test;
use Amp\PHPUnit\AsyncTestCase;
use Amp\Promise;
use Amp\Success;
use danog\Loop\Generic\GenericLoop;
use danog\Loop\Loop;
use danog\Loop\Test\Interfaces\LoggingPauseInterface;
use danog\Loop\Test\Traits\Basic;
use danog\Loop\Test\Traits\LoggingPause;
use function Amp\delay;
class GenericTest extends AsyncTestCase
{
/**
* Test basic loop.
*
* @param bool $stopSig Whether to stop with signal
*
* @return \Generator
*
* @dataProvider provideTrueFalse
*/
public function testGeneric(bool $stopSig): \Generator
{
$runCount = 0;
$pauseTime = GenericLoop::PAUSE;
$callable = function () use (&$runCount, &$pauseTime) {
$runCount++;
return $pauseTime;
};
yield from $this->fixtureAssertions($callable, $runCount, $pauseTime, $stopSig);
}
/**
* Test generator loop.
*
* @param bool $stopSig Whether to stop with signal
*
* @return \Generator
*
* @dataProvider provideTrueFalse
*/
public function testGenerator(bool $stopSig): \Generator
{
$runCount = 0;
$pauseTime = GenericLoop::PAUSE;
$callable = function () use (&$runCount, &$pauseTime): \Generator {
yield delay(1);
$runCount++;
return $pauseTime;
};
yield from $this->fixtureAssertions($callable, $runCount, $pauseTime, $stopSig);
}
/**
* Test promise loop.
*
* @param bool $stopSig Whether to stop with signal
*
* @return \Generator
*
* @dataProvider provideTrueFalse
*/
public function testPromise(bool $stopSig): \Generator
{
$runCount = 0;
$pauseTime = GenericLoop::PAUSE;
$callable = function () use (&$runCount, &$pauseTime): Promise {
$runCount++;
return new Success($pauseTime);
};
yield from $this->fixtureAssertions($callable, $runCount, $pauseTime, $stopSig);
}
/**
* Fixture assertions for started loop.
*
* @param LoggingPauseInterface $loop Loop
*
* @return void
*/
private function fixtureStarted(LoggingPauseInterface $loop): void
{
$this->assertTrue($loop->isRunning());
$this->assertEquals(1, $loop->startCounter());
$this->assertEquals(0, $loop->endCounter());
}
/**
* Run fixture assertions.
*
* @param \Closure $closure Closure
* @param integer $runCount Run count
* @param ?integer $pauseTime Pause time
* @param bool $stopSig Whether to stop with signal
*
* @return \Generator
*/
private function fixtureAssertions(\Closure $closure, int &$runCount, ?int &$pauseTime, bool $stopSig = false): \Generator
{
$loop = new class($closure, Fixtures::LOOP_NAME) extends GenericLoop implements LoggingPauseInterface {
use LoggingPause;
};
$this->assertEquals(Fixtures::LOOP_NAME, "$loop");
$this->assertFalse($loop->isRunning());
$this->assertEquals(0, $loop->startCounter());
$this->assertEquals(0, $loop->endCounter());
$this->assertEquals(0, $runCount);
$this->assertEquals(0, $loop->getPauseCount());
$loop->start();
yield delay(2);
$this->fixtureStarted($loop);
$this->assertEquals(1, $runCount);
$this->assertEquals(1, $loop->getPauseCount());
$this->assertEquals(0, $loop->getLastPause());
$pauseTime = 100;
$loop->resume();
yield delay(2);
$this->fixtureStarted($loop);
$this->assertEquals(2, $runCount);
$this->assertEquals(2, $loop->getPauseCount());
$this->assertEquals(100, $loop->getLastPause());
yield delay(48);
$this->fixtureStarted($loop);
$this->assertEquals(2, $runCount);
$this->assertEquals(2, $loop->getPauseCount());
$this->assertEquals(100, $loop->getLastPause());
yield delay(60);
$this->fixtureStarted($loop);
$this->assertEquals(3, $runCount);
$this->assertEquals(3, $loop->getPauseCount());
$this->assertEquals(100, $loop->getLastPause());
$loop->resume();
yield delay(1);
$this->assertEquals(4, $runCount);
$this->assertEquals(4, $loop->getPauseCount());
$this->assertEquals(100, $loop->getLastPause());
if ($stopSig) {
$loop->signal(true);
} else {
$pauseTime = GenericLoop::STOP;
$loop->resume();
}
yield delay(1);
$this->assertEquals($stopSig ? 4 : 5, $runCount);
$this->assertEquals(4, $loop->getPauseCount());
$this->assertEquals(100, $loop->getLastPause());
$this->assertFalse($loop->isRunning());
$this->assertEquals(1, $loop->startCounter());
$this->assertEquals(1, $loop->endCounter());
}
/**
* Provide true false.
*
* @return array
*/
public function provideTrueFalse(): array
{
return [
[true],
[false]
];
}
}

View File

@ -17,7 +17,7 @@ use danog\Loop\Interfaces\LoopInterface;
* *
* @author Daniil Gentili <daniil@daniil.it> * @author Daniil Gentili <daniil@daniil.it>
*/ */
interface BasicInterface extends LoopInterface interface BasicInterface extends LoopInterface, LoggingInterface
{ {
/** /**
* Check whether the loop inited. * Check whether the loop inited.
@ -31,16 +31,4 @@ interface BasicInterface extends LoopInterface
* @return boolean * @return boolean
*/ */
public function ran(): bool; public function ran(): bool;
/**
* Get start counter.
*
* @return integer
*/
public function startCounter(): int;
/**
* Get end counter.
*
* @return integer
*/
public function endCounter(): int;
} }

View File

@ -0,0 +1,28 @@
<?php
/**
* Resumable loop test interface.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/MIT MIT
*/
namespace danog\Loop\Test\Interfaces;
/**
* Resumable loop test interface.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
interface IntervalInterface
{
/**
* Set sleep interval.
*
* @param ?int $interval Interval
*
* @return void
*/
public function setInterval(?int $interval): void;
}

View File

@ -0,0 +1,34 @@
<?php
/**
* Basic loop test interface.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/MIT MIT
*/
namespace danog\Loop\Test\Interfaces;
use danog\Loop\Interfaces\LoopInterface;
/**
* Basic loop test interface.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
interface LoggingInterface extends LoopInterface
{
/**
* Get start counter.
*
* @return integer
*/
public function startCounter(): int;
/**
* Get end counter.
*
* @return integer
*/
public function endCounter(): int;
}

View File

@ -0,0 +1,34 @@
<?php
/**
* Basic loop test interface.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/MIT MIT
*/
namespace danog\Loop\Test\Interfaces;
use danog\Loop\Interfaces\LoopInterface;
/**
* Basic loop test interface.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
interface LoggingPauseInterface extends LoopInterface, LoggingInterface
{
/**
* Get number of times loop was paused.
*
* @return integer
*/
public function getPauseCount(): int;
/**
* Get last pause.
*
* @return integer
*/
public function getLastPause(): int;
}

View File

@ -10,19 +10,13 @@
namespace danog\Loop\Test\Interfaces; namespace danog\Loop\Test\Interfaces;
use danog\Loop\Interfaces\ResumableLoopInterface;
/** /**
* Resumable loop test interface. * Resumable loop test interface.
* *
* @author Daniil Gentili <daniil@daniil.it> * @author Daniil Gentili <daniil@daniil.it>
*/ */
interface ResumableInterface extends BasicInterface interface ResumableInterface extends BasicInterface, IntervalInterface, ResumableLoopInterface
{ {
/**
* Set sleep interval.
*
* @param ?int $interval Interval
*
* @return void
*/
public function setInterval(?int $interval): void;
} }

View File

@ -17,7 +17,7 @@ use danog\Loop\Interfaces\SignalLoopInterface;
* *
* @author Daniil Gentili <daniil@daniil.it> * @author Daniil Gentili <daniil@daniil.it>
*/ */
interface SignalInterface extends ResumableInterface, SignalLoopInterface interface SignalInterface extends BasicInterface, IntervalInterface, SignalLoopInterface
{ {
/** /**
* Get signaled payload. * Get signaled payload.

167
test/PeriodicTest.php Normal file
View File

@ -0,0 +1,167 @@
<?php
/**
* Loop test.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/MIT MIT
*/
namespace danog\Loop\Test;
use Amp\PHPUnit\AsyncTestCase;
use Amp\Promise;
use Amp\Success;
use danog\Loop\Generic\PeriodicLoop;
use danog\Loop\Loop;
use danog\Loop\Test\Interfaces\LoggingInterface;
use danog\Loop\Test\Traits\Basic;
use danog\Loop\Test\Traits\Logging;
use function Amp\delay;
class PeriodicTest extends AsyncTestCase
{
/**
* Test basic loop.
*
* @param bool $stopSig Whether to stop with signal
*
* @return \Generator
*
* @dataProvider provideTrueFalse
*/
public function testGeneric(bool $stopSig): \Generator
{
$runCount = 0;
$retValue = false;
$callable = function () use (&$runCount, &$retValue) {
$runCount++;
return $retValue;
};
yield from $this->fixtureAssertions($callable, $runCount, $retValue, $stopSig);
}
/**
* Test generator loop.
*
* @param bool $stopSig Whether to stop with signal
*
* @return \Generator
*
* @dataProvider provideTrueFalse
*/
public function testGenerator(bool $stopSig): \Generator
{
$runCount = 0;
$retValue = false;
$callable = function () use (&$runCount, &$retValue): \Generator {
yield delay(1);
$runCount++;
return $retValue;
};
yield from $this->fixtureAssertions($callable, $runCount, $retValue, $stopSig);
}
/**
* Test promise loop.
*
* @param bool $stopSig Whether to stop with signal
*
* @return \Generator
*
* @dataProvider provideTrueFalse
*/
public function testPromise(bool $stopSig): \Generator
{
$runCount = 0;
$retValue = false;
$callable = function () use (&$runCount, &$retValue): Promise {
$runCount++;
return new Success($retValue);
};
yield from $this->fixtureAssertions($callable, $runCount, $retValue, $stopSig);
}
/**
* Fixture assertions for started loop.
*
* @param LoggingInterface $loop Loop
*
* @return void
*/
private function fixtureStarted(LoggingInterface $loop): void
{
$this->assertTrue($loop->isRunning());
$this->assertEquals(1, $loop->startCounter());
$this->assertEquals(0, $loop->endCounter());
}
/**
* Run fixture assertions.
*
* @param \Closure $closure Closure
* @param integer $runCount Run count
* @param bool $retValue Pause time
* @param bool $stopSig Whether to stop with signal
*
* @return \Generator
*/
private function fixtureAssertions(\Closure $closure, int &$runCount, bool &$retValue, bool $stopSig = false): \Generator
{
$loop = new class($closure, Fixtures::LOOP_NAME, 100) extends PeriodicLoop implements LoggingInterface {
use Logging;
};
$this->assertEquals(Fixtures::LOOP_NAME, "$loop");
$this->assertFalse($loop->isRunning());
$this->assertEquals(0, $loop->startCounter());
$this->assertEquals(0, $loop->endCounter());
$this->assertEquals(0, $runCount);
$loop->start();
yield delay(2);
$this->fixtureStarted($loop);
$this->assertEquals(1, $runCount);
yield delay(48);
$this->fixtureStarted($loop);
$this->assertEquals(1, $runCount);
yield delay(60);
$this->fixtureStarted($loop);
$this->assertEquals(2, $runCount);
$loop->resume();
yield delay(1);
$this->assertEquals(3, $runCount);
if ($stopSig) {
$loop->signal(true);
} else {
$retValue = true;
$loop->resume();
}
yield delay(1);
$this->assertEquals($stopSig ? 3 : 4, $runCount);
$this->assertFalse($loop->isRunning());
$this->assertEquals(1, $loop->startCounter());
$this->assertEquals(1, $loop->endCounter());
}
/**
* Provide true false.
*
* @return array
*/
public function provideTrueFalse(): array
{
return [
[true],
[false]
];
}
}

View File

@ -16,18 +16,7 @@ use function Amp\delay;
trait Basic trait Basic
{ {
/** use Logging;
* Check whether the loop started.
*
* @var int
*/
private $startCounter = 0;
/**
* Check whether the loop ended.
*
* @var int
*/
private $endCounter = 0;
/** /**
* Check whether the loop inited. * Check whether the loop inited.
* *
@ -78,44 +67,4 @@ trait Basic
{ {
return LoopTest::LOOP_NAME; return LoopTest::LOOP_NAME;
} }
/**
* Signal that loop started.
*
* @return void
*/
protected function startedLoop(): void
{
$this->startCounter++;
parent::startedLoop();
}
/**
* Signal that loop ended.
*
* @return void
*/
protected function exitedLoop(): void
{
$this->endCounter++;
parent::exitedLoop();
}
/**
* Get start counter.
*
* @return integer
*/
public function startCounter(): int
{
return $this->startCounter;
}
/**
* Get end counter.
*
* @return integer
*/
public function endCounter(): int
{
return $this->endCounter;
}
} }

66
test/Traits/Logging.php Normal file
View File

@ -0,0 +1,66 @@
<?php
/**
* Loop test trait.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/MIT MIT
*/
namespace danog\Loop\Test\Traits;
trait Logging
{
/**
* Check whether the loop started.
*
* @var int
*/
private $startCounter = 0;
/**
* Check whether the loop ended.
*
* @var int
*/
private $endCounter = 0;
/**
* Signal that loop started.
*
* @return void
*/
protected function startedLoop(): void
{
$this->startCounter++;
parent::startedLoop();
}
/**
* Signal that loop ended.
*
* @return void
*/
protected function exitedLoop(): void
{
$this->endCounter++;
parent::exitedLoop();
}
/**
* Get start counter.
*
* @return integer
*/
public function startCounter(): int
{
return $this->startCounter;
}
/**
* Get end counter.
*
* @return integer
*/
public function endCounter(): int
{
return $this->endCounter;
}
}

View File

@ -0,0 +1,60 @@
<?php
/**
* Loop test trait.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/MIT MIT
*/
namespace danog\Loop\Test\Traits;
use function Amp\delay;
trait LoggingPause
{
use Logging;
/**
* Number of times loop was paused.
*
* @var integer
*/
private $pauseCount = 0;
/**
* Last pause delay.
*
* @var int
*/
private $lastPause = 0;
/**
* Get number of times loop was paused.
*
* @return integer
*/
public function getPauseCount(): int
{
return $this->pauseCount;
}
/**
* Get last pause.
*
* @return integer
*/
public function getLastPause(): int
{
return $this->lastPause;
}
/**
* Report pause, can be overriden for logging.
*
* @param integer $timeout Pause duration, 0 = forever
*
* @return void
*/
protected function reportPause(int $timeout): void
{
$this->pauseCount++;
$this->lastPause= $timeout;
}
}

View File

@ -48,6 +48,17 @@ trait Signal
{ {
return $this->exception; return $this->exception;
} }
/**
* Test waiting signal on interval.
*
* @param integer $interval Interval
*
* @return \Generator
*/
private function testGenerator(int $interval): \Generator
{
yield delay($interval);
}
/** /**
* Loop implementation. * Loop implementation.
* *
@ -58,7 +69,7 @@ trait Signal
$this->inited = true; $this->inited = true;
try { try {
while (true) { while (true) {
$this->payload = yield $this->waitSignal($this instanceof ResumableLoopInterface ? $this->pause($this->interval) : delay($this->interval)); $this->payload = yield $this->waitSignal($this instanceof ResumableLoopInterface ? $this->pause($this->interval) : $this->testGenerator($this->interval));
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->exception = $e; $this->exception = $e;