diff --git a/.travis.yml b/.travis.yml index 7398baf..61e9891 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,9 +22,6 @@ before_install: install: - 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: - vendor/bin/phpunit --coverage-text --coverage-clover build/logs/clover.xml @@ -32,10 +29,9 @@ script: - vendor/bin/psalm after_script: - - curl -OL https://github.com/php-coveralls/php-coveralls/releases/download/v1.0.0/coveralls.phar - - chmod +x coveralls.phar - - phpdbg -qrr coverage/bin/phpcov merge --clover build/logs/clover.xml coverage/cov - - ./coveralls.phar + - curl -OL https://github.com/php-coveralls/php-coveralls/releases/download/v2.2.0/php-coveralls.phar + - chmod +x php-coveralls.phar + - ./php-coveralls.phar -v cache: directories: diff --git a/README.md b/README.md index b900542..e947b57 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 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) `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. diff --git a/composer.json b/composer.json index 9d35987..56f02a3 100644 --- a/composer.json +++ b/composer.json @@ -43,6 +43,6 @@ ], "cs": "php-cs-fixer fix -v --diff --dry-run", "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" } } diff --git a/lib/Generic/PeriodicLoop.php b/lib/Generic/PeriodicLoop.php index 42e09d4..7af7fda 100644 --- a/lib/Generic/PeriodicLoop.php +++ b/lib/Generic/PeriodicLoop.php @@ -20,6 +20,12 @@ use danog\Loop\ResumableSignalLoop; * The loop can be stopped from the outside or * from the inside by signaling or returning `true`. * + * @template T as bool + * @template TGenerator as \Generator,mixed,Promise|T> + * @template TPromise as Promise + * + * @template TCallable as T|TPromise|TGenerator + * * @author Daniil Gentili */ class PeriodicLoop extends ResumableSignalLoop @@ -28,6 +34,8 @@ class PeriodicLoop extends ResumableSignalLoop * Callback. * * @var callable + * + * @psalm-var callable():TCallable */ private $callback; /** @@ -48,6 +56,8 @@ class PeriodicLoop extends ResumableSignalLoop * @param callable $callback Callback to call * @param string $name Loop name * @param ?int $interval Loop interval + * + * @psalm-param callable():TCallable $callback Callable to run */ public function __construct(callable $callback, string $name, ?int $interval) { @@ -65,16 +75,21 @@ class PeriodicLoop extends ResumableSignalLoop $callback = $this->callback; while (true) { /** @psalm-suppress MixedAssignment */ - $result = yield $this->waitSignal($this->pause($this->interval)); - if ($result) { + $result = $callback(); + 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; } /** @psalm-suppress MixedAssignment */ - $result = $callback(); - if ($result instanceof \Generator) { - yield from $result; - } elseif ($result instanceof Promise) { - yield $result; + $result = yield $this->waitSignal($this->pause($this->interval)); + if ($result === true) { + return; } } } diff --git a/test/Fixtures.php b/test/Fixtures.php index af3481a..e05a77d 100644 --- a/test/Fixtures.php +++ b/test/Fixtures.php @@ -26,7 +26,7 @@ abstract class Fixtures extends AsyncTestCase * * @return boolean */ - public static function isResolved(Promise $promise): bool + protected static function isResolved(Promise $promise): bool { $resolved = false; $promise->onResolve(static function ($e, $res) use (&$resolved) { @@ -44,7 +44,7 @@ abstract class Fixtures extends AsyncTestCase * * @return void */ - public function assertPreStart(BasicInterface $loop) + protected function assertPreStart(BasicInterface $loop) { $this->assertEquals(self::LOOP_NAME, "$loop"); @@ -64,7 +64,7 @@ abstract class Fixtures extends AsyncTestCase * * @return void */ - public function assertAfterStart(BasicInterface $loop, bool $running = true) + protected function assertAfterStart(BasicInterface $loop, bool $running = true) { $this->assertTrue($loop->inited()); @@ -85,7 +85,7 @@ abstract class Fixtures extends AsyncTestCase * * @return void */ - public function assertFinal(BasicInterface $loop) + protected function assertFinal(BasicInterface $loop) { $this->assertTrue($loop->ran()); $this->assertFalse($loop->isRunning()); diff --git a/test/GenericTest.php b/test/GenericTest.php new file mode 100644 index 0000000..0cf248c --- /dev/null +++ b/test/GenericTest.php @@ -0,0 +1,187 @@ + + * @copyright 2016-2020 Daniil Gentili + * @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] + ]; + } +} diff --git a/test/Interfaces/BasicInterface.php b/test/Interfaces/BasicInterface.php index da3bc8f..3a425ae 100644 --- a/test/Interfaces/BasicInterface.php +++ b/test/Interfaces/BasicInterface.php @@ -17,7 +17,7 @@ use danog\Loop\Interfaces\LoopInterface; * * @author Daniil Gentili */ -interface BasicInterface extends LoopInterface +interface BasicInterface extends LoopInterface, LoggingInterface { /** * Check whether the loop inited. @@ -31,16 +31,4 @@ interface BasicInterface extends LoopInterface * @return boolean */ public function ran(): bool; - /** - * Get start counter. - * - * @return integer - */ - public function startCounter(): int; - /** - * Get end counter. - * - * @return integer - */ - public function endCounter(): int; } diff --git a/test/Interfaces/IntervalInterface.php b/test/Interfaces/IntervalInterface.php new file mode 100644 index 0000000..272399d --- /dev/null +++ b/test/Interfaces/IntervalInterface.php @@ -0,0 +1,28 @@ + + * @copyright 2016-2020 Daniil Gentili + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace danog\Loop\Test\Interfaces; + +/** + * Resumable loop test interface. + * + * @author Daniil Gentili + */ +interface IntervalInterface +{ + /** + * Set sleep interval. + * + * @param ?int $interval Interval + * + * @return void + */ + public function setInterval(?int $interval): void; +} diff --git a/test/Interfaces/LoggingInterface.php b/test/Interfaces/LoggingInterface.php new file mode 100644 index 0000000..038ba42 --- /dev/null +++ b/test/Interfaces/LoggingInterface.php @@ -0,0 +1,34 @@ + + * @copyright 2016-2020 Daniil Gentili + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace danog\Loop\Test\Interfaces; + +use danog\Loop\Interfaces\LoopInterface; + +/** + * Basic loop test interface. + * + * @author Daniil Gentili + */ +interface LoggingInterface extends LoopInterface +{ + /** + * Get start counter. + * + * @return integer + */ + public function startCounter(): int; + /** + * Get end counter. + * + * @return integer + */ + public function endCounter(): int; +} diff --git a/test/Interfaces/LoggingPauseInterface.php b/test/Interfaces/LoggingPauseInterface.php new file mode 100644 index 0000000..3281443 --- /dev/null +++ b/test/Interfaces/LoggingPauseInterface.php @@ -0,0 +1,34 @@ + + * @copyright 2016-2020 Daniil Gentili + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace danog\Loop\Test\Interfaces; + +use danog\Loop\Interfaces\LoopInterface; + +/** + * Basic loop test interface. + * + * @author Daniil Gentili + */ +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; +} diff --git a/test/Interfaces/ResumableInterface.php b/test/Interfaces/ResumableInterface.php index d800d16..42c1db0 100644 --- a/test/Interfaces/ResumableInterface.php +++ b/test/Interfaces/ResumableInterface.php @@ -10,19 +10,13 @@ namespace danog\Loop\Test\Interfaces; +use danog\Loop\Interfaces\ResumableLoopInterface; + /** * Resumable loop test interface. * * @author Daniil Gentili */ -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; } diff --git a/test/Interfaces/SignalInterface.php b/test/Interfaces/SignalInterface.php index 410807d..8cc996e 100644 --- a/test/Interfaces/SignalInterface.php +++ b/test/Interfaces/SignalInterface.php @@ -17,7 +17,7 @@ use danog\Loop\Interfaces\SignalLoopInterface; * * @author Daniil Gentili */ -interface SignalInterface extends ResumableInterface, SignalLoopInterface +interface SignalInterface extends BasicInterface, IntervalInterface, SignalLoopInterface { /** * Get signaled payload. diff --git a/test/PeriodicTest.php b/test/PeriodicTest.php new file mode 100644 index 0000000..7e19a58 --- /dev/null +++ b/test/PeriodicTest.php @@ -0,0 +1,167 @@ + + * @copyright 2016-2020 Daniil Gentili + * @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] + ]; + } +} diff --git a/test/Traits/Basic.php b/test/Traits/Basic.php index dd54b55..53e002f 100644 --- a/test/Traits/Basic.php +++ b/test/Traits/Basic.php @@ -16,18 +16,7 @@ use function Amp\delay; trait Basic { - /** - * Check whether the loop started. - * - * @var int - */ - private $startCounter = 0; - /** - * Check whether the loop ended. - * - * @var int - */ - private $endCounter = 0; + use Logging; /** * Check whether the loop inited. * @@ -78,44 +67,4 @@ trait Basic { 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; - } } diff --git a/test/Traits/Logging.php b/test/Traits/Logging.php new file mode 100644 index 0000000..606b3b8 --- /dev/null +++ b/test/Traits/Logging.php @@ -0,0 +1,66 @@ + + * @copyright 2016-2020 Daniil Gentili + * @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; + } +} diff --git a/test/Traits/LoggingPause.php b/test/Traits/LoggingPause.php new file mode 100644 index 0000000..2600ee1 --- /dev/null +++ b/test/Traits/LoggingPause.php @@ -0,0 +1,60 @@ + + * @copyright 2016-2020 Daniil Gentili + * @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; + } +} diff --git a/test/Traits/Signal.php b/test/Traits/Signal.php index feff9b4..125eaca 100644 --- a/test/Traits/Signal.php +++ b/test/Traits/Signal.php @@ -48,6 +48,17 @@ trait Signal { 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. * @@ -58,7 +69,7 @@ trait Signal $this->inited = true; try { 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) { $this->exception = $e;