From 81a524f4d2301e68174b9b0dfef7a377ece504a4 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 21 Jul 2020 18:06:19 +0200 Subject: [PATCH] First commit --- .gitignore | 7 ++ .php_cs.dist | 13 +++ .travis.yml | 40 +++++++++ LICENSE | 7 ++ composer.json | 48 +++++++++++ lib/Generic/GenericLoop.php | 129 +++++++++++++++++++++++++++++ lib/Generic/PeriodicLoop.php | 83 +++++++++++++++++++ lib/Impl/Loop.php | 23 ++++++ lib/Impl/ResumableLoop.php | 23 ++++++ lib/Impl/ResumableSignalLoop.php | 26 ++++++ lib/Impl/SignalLoop.php | 25 ++++++ lib/LoopInterface.php | 48 +++++++++++ lib/ResumableLoopInterface.php | 36 +++++++++ lib/SignalLoopInterface.php | 45 +++++++++++ lib/Traits/Loop.php | 80 ++++++++++++++++++ lib/Traits/ResumableLoop.php | 135 +++++++++++++++++++++++++++++++ lib/Traits/SignalLoop.php | 81 +++++++++++++++++++ phpunit.xml.dist | 28 +++++++ psalm.xml | 15 ++++ test/BasicInterface.php | 46 +++++++++++ test/LoopTest.php | 87 ++++++++++++++++++++ test/Traits/Basic.php | 121 +++++++++++++++++++++++++++ test/Traits/BasicResumable.php | 29 +++++++ 23 files changed, 1175 insertions(+) create mode 100644 .gitignore create mode 100644 .php_cs.dist create mode 100644 .travis.yml create mode 100644 LICENSE create mode 100644 composer.json create mode 100644 lib/Generic/GenericLoop.php create mode 100644 lib/Generic/PeriodicLoop.php create mode 100644 lib/Impl/Loop.php create mode 100644 lib/Impl/ResumableLoop.php create mode 100644 lib/Impl/ResumableSignalLoop.php create mode 100644 lib/Impl/SignalLoop.php create mode 100644 lib/LoopInterface.php create mode 100644 lib/ResumableLoopInterface.php create mode 100644 lib/SignalLoopInterface.php create mode 100644 lib/Traits/Loop.php create mode 100644 lib/Traits/ResumableLoop.php create mode 100644 lib/Traits/SignalLoop.php create mode 100644 phpunit.xml.dist create mode 100644 psalm.xml create mode 100644 test/BasicInterface.php create mode 100644 test/LoopTest.php create mode 100644 test/Traits/Basic.php create mode 100644 test/Traits/BasicResumable.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bef310b --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.vscode +build +composer.lock +phpunit.xml +vendor +.php_cs.cache +coverage diff --git a/.php_cs.dist b/.php_cs.dist new file mode 100644 index 0000000..8d02bce --- /dev/null +++ b/.php_cs.dist @@ -0,0 +1,13 @@ +getFinder() + ->in(__DIR__ . '/examples') + ->in(__DIR__ . '/lib') + ->in(__DIR__ . '/test'); + +$cacheDir = getenv('TRAVIS') ? getenv('HOME') . '/.php-cs-fixer' : __DIR__; + +$config->setCacheFile($cacheDir . '/.php_cs.cache'); + +return $config; diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ce8a466 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,40 @@ +sudo: false + +language: php + +php: + - 7.1 + - 7.2 + - 7.3 + - 7.4 + - nightly + +matrix: + allow_failures: + - php: nightly + fast_finish: true + +env: + - AMP_DEBUG=true + +before_install: + - phpenv config-rm xdebug.ini || echo "No xdebug config." + +install: + - composer update -n --prefer-dist + - wget https://github.com/php-coveralls/php-coveralls/releases/download/v1.0.2/coveralls.phar + - chmod +x coveralls.phar + +script: + - vendor/bin/phpunit --coverage-text --coverage-clover build/logs/clover.xml + - PHP_CS_FIXER_IGNORE_ENV=1 php vendor/bin/php-cs-fixer --diff --dry-run -v fix + - vendor/bin/psalm.phar + +after_script: + - ./coveralls.phar -v + +cache: + directories: + - $HOME/.composer/cache + - $HOME/.php-cs-fixer + - $HOME/.local diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2af0406 --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright 2019-2020 Daniil Gentili + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..9d35987 --- /dev/null +++ b/composer.json @@ -0,0 +1,48 @@ +{ + "name": "danog/loop", + "description": "Loop abstraction for AMPHP.", + "keywords": [ + "asynchronous", + "async", + "concurrent", + "multi-threading", + "multi-processing" + ], + "homepage": "https://github.com/danog/loop", + "license": "MIT", + "authors": [ + { + "name": "Daniil Gentili", + "email": "daniil@daniil.it" + } + ], + "require": { + "php": ">=7.1", + "amphp/amp": "^2" + }, + "require-dev": { + "phpunit/phpunit": "^7 | ^8 | ^9", + "amphp/phpunit-util": "^1.3", + "amphp/php-cs-fixer-config": "dev-master", + "vimeo/psalm": "dev-master" + }, + "autoload": { + "psr-4": { + "danog\\Loop\\": "lib" + } + }, + "autoload-dev": { + "psr-4": { + "danog\\Loop\\Test\\": "test" + } + }, + "scripts": { + "check": [ + "@cs", + "@test" + ], + "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" + } +} diff --git a/lib/Generic/GenericLoop.php b/lib/Generic/GenericLoop.php new file mode 100644 index 0000000..82a9384 --- /dev/null +++ b/lib/Generic/GenericLoop.php @@ -0,0 +1,129 @@ + + * @copyright 2016-2020 Daniil Gentili + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace danog\Loop\Generic; + +use Amp\Promise; +use danog\Loop\Impl\ResumableSignalLoop; + +/** + * Generic loop, runs single callable. + * + * The return value of the callable can be: + * * A number - the loop will be paused for the specified number of seconds + * * GenericLoop::STOP - The loop will stop + * * GenericLoop::PAUSE - The loop will pause forever (or until loop is `resumed()` + * from outside the loop) + * * GenericLoop::CONTINUE - Return this if you want to rerun the loop immediately + * + * If the callable does not return anything, + * the loop will behave is if GenericLoop::PAUSE was returned. + * + * The loop can be stopped from the outside by signaling `true`. + * + * @template T as int|float|null + * @template TGenerator as \Generator,mixed,Promise|T> + * @template TPromise as Promise + * + * @template TCallable as T|TPromise|TGenerator + * + * @author Daniil Gentili + */ +class GenericLoop extends ResumableSignalLoop +{ + /** + * Stop the loop. + */ + const STOP = -1; + /** + * Pause the loop. + */ + const PAUSE = null; + /** + * Rerun the loop. + */ + const CONTINUE = 0; + /** + * Callable. + * + * @var callable + * + * @psalm-var callable():TCallable + */ + protected $callable; + /** + * Loop name. + * + * @var string + */ + protected $name; + /** + * Constructor. + * + * @param callable $callable Callable to run + * @param string $name Loop name + * + * @psalm-param callable():TCallable $callable Callable to run + */ + public function __construct(callable $callable, string $name) + { + $this->callable = $callable; + $this->name = $name; + } + /** + * Loop implementation. + * + * @return \Generator + */ + public function loop(): \Generator + { + $callable = $this->callable; + while (true) { + /** @psalm-var TCallable */ + $timeout = $callable(); + if ($timeout instanceof \Generator) { + /** @psalm-var TGenerator */ + $timeout = yield from $timeout; + } elseif ($timeout instanceof Promise) { + /** @psalm-var TPromise */ + $timeout = yield $timeout; + } + if ($timeout === self::PAUSE) { + $this->reportPause(0); + } elseif ($timeout > 0) { + /** @psalm-suppress MixedArgument */ + $this->reportPause($timeout); + } + /** @psalm-suppress MixedArgument */ + if ($timeout === self::STOP || yield $this->waitSignal($this->pause($timeout))) { + return; + } + } + } + /** + * Report pause, can be overriden for logging. + * + * @param integer $timeout Pause duration, 0 = forever + * + * @return void + */ + protected function reportPause(int $timeout): void + { + } + /** + * Get loop name. + * + * @return string + */ + public function __toString(): string + { + return $this->name; + } +} diff --git a/lib/Generic/PeriodicLoop.php b/lib/Generic/PeriodicLoop.php new file mode 100644 index 0000000..837fdfc --- /dev/null +++ b/lib/Generic/PeriodicLoop.php @@ -0,0 +1,83 @@ + + * @copyright 2016-2020 Daniil Gentili + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace danog\Loop\Generic; + +use danog\Loop\Impl\ResumableSignalLoop; + +/** + * Periodic loop. + * + * Runs a callback at a periodic interval. + * + * The loop can be stopped from the outside or + * from the inside by signaling or returning `true`. + * + * @author Daniil Gentili + */ +class PeriodicLoop extends ResumableSignalLoop +{ + /** + * Callback. + * + * @var callable + */ + private $callback; + /** + * Loop name. + * + * @var string + */ + private string $name; + /** + * Loop interval. + * + * @var float|int + */ + private $interval; + /** + * Constructor. + * + * @param callable $callback Callback to call + * @param string $name Loop name + * @param int|float $interval Loop interval + */ + public function __construct(callable $callback, string $name, $interval) + { + $this->callback = $callback; + $this->name = $name; + $this->interval = $interval; + } + /** + * Loop implementation. + * + * @return \Generator + */ + public function loop(): \Generator + { + $callback = $this->callback; + while (true) { + /** @psalm-suppress MixedAssignment */ + $result = yield $this->waitSignal($this->pause($this->interval)); + if ($result) { + return; + } + yield $callback(); + } + } + /** + * Get name of the loop. + * + * @return string + */ + public function __toString(): string + { + return $this->name; + } +} diff --git a/lib/Impl/Loop.php b/lib/Impl/Loop.php new file mode 100644 index 0000000..d38bdb2 --- /dev/null +++ b/lib/Impl/Loop.php @@ -0,0 +1,23 @@ + + * @copyright 2016-2020 Daniil Gentili + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace danog\Loop\Impl; + +use danog\Loop\LoopInterface; +use danog\Loop\Traits\Loop as TraitsLoop; + +/** + * Loop abstract class. + * + * @author Daniil Gentili + */ +abstract class Loop implements LoopInterface +{ + use TraitsLoop; +} diff --git a/lib/Impl/ResumableLoop.php b/lib/Impl/ResumableLoop.php new file mode 100644 index 0000000..805e78b --- /dev/null +++ b/lib/Impl/ResumableLoop.php @@ -0,0 +1,23 @@ + + * @copyright 2016-2020 Daniil Gentili + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace danog\Loop\Impl; + +use danog\Loop\ResumableLoopInterface; +use danog\Loop\Traits\ResumableLoop as TraitsResumableLoop; + +/** + * Resumable loop abstract class. + * + * @author Daniil Gentili + */ +abstract class ResumableLoop implements ResumableLoopInterface +{ + use TraitsResumableLoop; +} diff --git a/lib/Impl/ResumableSignalLoop.php b/lib/Impl/ResumableSignalLoop.php new file mode 100644 index 0000000..7eb9f3f --- /dev/null +++ b/lib/Impl/ResumableSignalLoop.php @@ -0,0 +1,26 @@ + + * @copyright 2016-2020 Daniil Gentili + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace danog\Loop\Impl; + +use danog\Loop\ResumableLoopInterface; +use danog\Loop\SignalLoopInterface; +use danog\Loop\Traits\ResumableLoop; +use danog\Loop\Traits\SignalLoop; + +/** + * Resumable signal loop abstract class. + * + * @author Daniil Gentili + */ +abstract class ResumableSignalLoop implements ResumableLoopInterface, SignalLoopInterface +{ + use ResumableLoop; + use SignalLoop; +} diff --git a/lib/Impl/SignalLoop.php b/lib/Impl/SignalLoop.php new file mode 100644 index 0000000..ace12b9 --- /dev/null +++ b/lib/Impl/SignalLoop.php @@ -0,0 +1,25 @@ + + * @copyright 2016-2020 Daniil Gentili + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace danog\Loop\Impl; + +use danog\Loop\SignalLoopInterface; +use danog\Loop\Traits\Loop; +use danog\Loop\Traits\SignalLoop as TraitsSignalLoop; + +/** + * Signal loop abstract class. + * + * @author Daniil Gentili + */ +abstract class SignalLoop implements SignalLoopInterface +{ + use Loop; + use TraitsSignalLoop; +} diff --git a/lib/LoopInterface.php b/lib/LoopInterface.php new file mode 100644 index 0000000..a9591af --- /dev/null +++ b/lib/LoopInterface.php @@ -0,0 +1,48 @@ + + * @copyright 2016-2020 Daniil Gentili + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace danog\Loop; + +use Amp\Promise; + +/** + * Loop interface. + * + * @author Daniil Gentili + */ +interface LoopInterface +{ + /** + * Start the loop. + * + * Returns false if the loop is already running. + * + * @return bool + */ + public function start(): bool; + /** + * The actual loop function. + * + * @return \Generator + */ + public function loop(): \Generator; + /** + * Get name of the loop. + * + * @return string + */ + public function __toString(): string; + /** + * Check whether loop is running. + * + * @return boolean + */ + public function isRunning(): bool; +} diff --git a/lib/ResumableLoopInterface.php b/lib/ResumableLoopInterface.php new file mode 100644 index 0000000..24c9147 --- /dev/null +++ b/lib/ResumableLoopInterface.php @@ -0,0 +1,36 @@ + + * @copyright 2016-2020 Daniil Gentili + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace danog\Loop; + +use Amp\Promise; + +/** + * Resumable loop interface. + * + * @author Daniil Gentili + */ +interface ResumableLoopInterface extends LoopInterface +{ + /** + * Pause the loop. + * + * @param int|float|null $time For how long to pause the loop, if null will pause forever (until resume is called from outside of the loop) + * + * @return Promise Resolved when the loop is resumed + */ + public function pause($time = null): Promise; + /** + * Resume the loop. + * + * @return Promise Resolved when the loop is paused again + */ + public function resume(): Promise; +} diff --git a/lib/SignalLoopInterface.php b/lib/SignalLoopInterface.php new file mode 100644 index 0000000..f62f4fc --- /dev/null +++ b/lib/SignalLoopInterface.php @@ -0,0 +1,45 @@ + + * @copyright 2016-2020 Daniil Gentili + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace danog\Loop; + +use Amp\Promise; + +/** + * Signal loop interface. + * + * @author Daniil Gentili + */ +interface SignalLoopInterface extends LoopInterface +{ + /** + * Send a signal to the the loop. + * + * @param \Throwable|mixed $data Signal to send + * + * @return void + */ + public function signal($data): void; + /** + * Resolve the promise or return|throw the signal. + * + * @param Promise|\Generator $promise The original promise or generator + * + * @return Promise + * + * @template T + * + * @psalm-param Promise|\Generator,mixed,Promise|T> $promise The original promise or generator + * + * @psalm-return Promise + */ + public function waitSignal($promise): Promise; +} diff --git a/lib/Traits/Loop.php b/lib/Traits/Loop.php new file mode 100644 index 0000000..02f762c --- /dev/null +++ b/lib/Traits/Loop.php @@ -0,0 +1,80 @@ + + * @copyright 2016-2020 Daniil Gentili + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace danog\Loop\Traits; + +use Amp\Promise; + +use function Amp\asyncCall; + +/** + * Loop helper trait. + * + * Wraps the asynchronous generator methods with asynchronous promise-based methods + * + * @author Daniil Gentili + */ +trait Loop +{ + /** + * Whether the loop was started. + * + * @var bool + */ + private $started = false; + /** + * Start the loop. + * + * Returns false if the loop is already running. + * + * @return bool + */ + public function start(): bool + { + if ($this->started) { + return false; + } + asyncCall(function (): \Generator { + $this->startedLoop(); + try { + yield from $this->loop(); + } finally { + $this->exitedLoop(); + } + }); + return true; + } + /** + * Signal that loop has exIited. + * + * @return void + */ + protected function exitedLoop(): void + { + $this->started = false; + } + /** + * Signal that loop has started. + * + * @return void + */ + protected function startedLoop(): void + { + $this->started = true; + } + /** + * Check whether loop is running. + * + * @return boolean + */ + public function isRunning(): bool + { + return $this->started; + } +} diff --git a/lib/Traits/ResumableLoop.php b/lib/Traits/ResumableLoop.php new file mode 100644 index 0000000..7bed5aa --- /dev/null +++ b/lib/Traits/ResumableLoop.php @@ -0,0 +1,135 @@ + + * @copyright 2016-2020 Daniil Gentili + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace danog\Loop\Traits; + +use Amp\Deferred; +use Amp\Loop as AmpLoop; +use Amp\Promise; +use Amp\Success; +use Closure; + +/** + * Resumable loop helper trait. + * + * @author Daniil Gentili + */ +trait ResumableLoop +{ + use Loop { + exitedLoop as parentExitedLoop; + } + /** + * Resume deferred. + * + * @var ?Deferred + */ + private $resume; + /** + * Pause deferred. + * + * @var ?Deferred + */ + private $pause; + /** + * Resume timer ID. + * + * @var ?string + */ + private $resumeTimer; + /** + * Pause the loop. + * + * @param int|float|null $time For how long to pause the loop, if null will pause forever (until resume is called from outside of the loop) + * + * @return Promise Resolved when the loop is resumed + */ + public function pause($time = null): Promise + { + if (!\is_null($time)) { + if ($time <= 0) { + return new Success(0); + } + if ($this->resumeTimer) { + AmpLoop::cancel($this->resumeTimer); + $this->resumeTimer = null; + } + $this->resumeTimer = AmpLoop::delay((int) ($time * 1000), \Closure::fromCallable([$this, 'resumeInternal'])); + } + $this->resume = new Deferred(); + $pause = $this->pause; + $this->pause = new Deferred(); + if ($pause) { + /** + * @psalm-suppress InvalidArgument + */ + AmpLoop::defer([$pause, 'resolve']); + } + return $this->resume->promise(); + } + /** + * Resume the loop. + * + * @return Promise Resolved when the loop is paused again + */ + public function resume(): Promise + { + $this->resumeInternal(); + if (!$this->pause) { + $this->pause = new Deferred; + } + return $this->pause->promise(); + } + /** + * Defer resuming the loop. + * + * @return Promise Resolved when the loop is paused again + */ + public function resumeDefer(): Promise + { + AmpLoop::defer(Closure::fromCallable([$this, 'resumeInternal'])); + if (!$this->pause) { + $this->pause = new Deferred; + } + return $this->pause->promise(); + } + /** + * Internal resume function. + * + * @return void + */ + private function resumeInternal(): void + { + if ($this->resumeTimer) { + $storedWatcherId = $this->resumeTimer; + AmpLoop::cancel($storedWatcherId); + $this->resumeTimer = null; + } + if ($this->resume) { + $resume = $this->resume; + $this->resume = null; + $resume->resolve(); + } + } + + /** + * Signal that loop has exIited. + * + * @return void + */ + protected function exitedLoop(): void + { + $this->parentExitedLoop(); + if ($this->resumeTimer) { + AmpLoop::cancel($this->resumeTimer); + $this->resumeTimer = null; + } + } +} diff --git a/lib/Traits/SignalLoop.php b/lib/Traits/SignalLoop.php new file mode 100644 index 0000000..034596c --- /dev/null +++ b/lib/Traits/SignalLoop.php @@ -0,0 +1,81 @@ + + * @copyright 2016-2020 Daniil Gentili + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace danog\Loop\Traits; + +use Amp\Coroutine; +use Amp\Deferred; +use Amp\Promise; + +/** + * Signal loop helper trait. + * + * @author Daniil Gentili + */ +trait SignalLoop +{ + /** + * Signal deferred. + * + * @var ?Deferred + */ + private $signalDeferred; + /** + * Send signal to loop. + * + * @param mixed|\Throwable $what Data to signal + * + * @return void + */ + public function signal($what): void + { + if ($this->signalDeferred) { + $deferred = $this->signalDeferred; + $this->signalDeferred = null; + if ($what instanceof \Throwable) { + $deferred->fail($what); + } else { + $deferred->resolve($what); + } + } + } + /** + * Resolve the promise or return|throw the signal. + * + * @param Promise|\Generator $promise The original promise or generator + * + * @return Promise + * + * @template T + * + * @psalm-param Promise|\Generator,mixed,Promise|T> $promise The original promise or generator + * + * @psalm-return Promise + */ + public function waitSignal($promise): Promise + { + if ($promise instanceof \Generator) { + $promise = new Coroutine($promise); + } + $this->signalDeferred = new Deferred(); + $combinedPromise = $this->signalDeferred->promise(); + $promise->onResolve( + function () use ($promise) { + if ($this->signalDeferred !== null) { + $deferred = $this->signalDeferred; + $this->signalDeferred = null; + $deferred->resolve($promise); + } + } + ); + return $combinedPromise; + } +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..632555f --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,28 @@ + + + + + test + + + + + lib + + + + + + diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..bfef640 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/test/BasicInterface.php b/test/BasicInterface.php new file mode 100644 index 0000000..6ce670d --- /dev/null +++ b/test/BasicInterface.php @@ -0,0 +1,46 @@ + + * @copyright 2016-2020 Daniil Gentili + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace danog\Loop\Test; + +use danog\Loop\LoopInterface; + +/** + * Basic loop test interface. + * + * @author Daniil Gentili + */ +interface BasicInterface extends LoopInterface +{ + /** + * Check whether the loop inited. + * + * @return boolean + */ + public function inited(): bool; + /** + * Check whether the loop ran. + * + * @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/LoopTest.php b/test/LoopTest.php new file mode 100644 index 0000000..5c947c6 --- /dev/null +++ b/test/LoopTest.php @@ -0,0 +1,87 @@ + + * @copyright 2016-2020 Daniil Gentili + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace danog\Loop\Test; + +use Amp\PHPUnit\AsyncTestCase; +use danog\Loop\Impl\Loop; +use danog\Loop\Impl\ResumableLoop; +use danog\Loop\Impl\ResumableSignalLoop; +use danog\Loop\Impl\SignalLoop; +use danog\Loop\Test\Traits\Basic; +use danog\Loop\Test\Traits\BasicResumable; + +use function Amp\delay; + +class LoopTest extends AsyncTestCase +{ + const LOOP_NAME = 'PONY'; + /** + * Test basic loop. + * + * @param BasicInterface $loop Loop + * + * @return \Generator + * + * @dataProvider provideBasic + */ + public function testLoop(BasicInterface $loop): \Generator + { + $this->assertEquals("$loop", self::LOOP_NAME); + + $this->assertTrue($loop->start()); + $this->assertFalse($loop->start()); + + $this->assertTrue($loop->inited()); + + $this->assertFalse($loop->ran()); + $this->assertTrue($loop->isRunning()); + + $this->assertEquals($loop->startCounter(), 1); + $this->assertEquals($loop->endCounter(), 0); + + yield delay(110); + + $this->assertTrue($loop->ran()); + $this->assertFalse($loop->isRunning()); + + $this->assertEquals($loop->startCounter(), 1); + $this->assertEquals($loop->endCounter(), 1); + } + /** + * Provide loop implementations. + * + * @return array + */ + public function provideBasic(): array + { + return [ + [new class() extends Loop implements BasicInterface { + use Basic; + }], + [new class() extends SignalLoop implements BasicInterface { + use Basic; + }], + [new class() extends ResumableLoop implements BasicInterface { + use Basic; + }], + [new class() extends ResumableSignalLoop implements BasicInterface { + use Basic; + }], + + + [new class() extends ResumableLoop implements BasicInterface { + use BasicResumable; + }], + [new class() extends ResumableSignalLoop implements BasicInterface { + use BasicResumable; + }], + ]; + } +} diff --git a/test/Traits/Basic.php b/test/Traits/Basic.php new file mode 100644 index 0000000..dd54b55 --- /dev/null +++ b/test/Traits/Basic.php @@ -0,0 +1,121 @@ + + * @copyright 2016-2020 Daniil Gentili + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace danog\Loop\Test\Traits; + +use danog\Loop\Test\LoopTest; +use Generator; + +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; + /** + * Check whether the loop inited. + * + * @var bool + */ + private $inited = false; + /** + * Check whether the loop ran. + * + * @var bool + */ + private $ran = false; + /** + * Check whether the loop inited. + * + * @return boolean + */ + public function inited(): bool + { + return $this->inited; + } + /** + * Check whether the loop ran. + * + * @return boolean + */ + public function ran(): bool + { + return $this->ran; + } + /** + * Loop implementation. + * + * @return Generator + */ + public function loop(): Generator + { + $this->inited = true; + yield delay(100); + $this->ran = true; + } + /** + * Get loop name. + * + * @return string + */ + public function __toString(): string + { + 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/BasicResumable.php b/test/Traits/BasicResumable.php new file mode 100644 index 0000000..72ce5fe --- /dev/null +++ b/test/Traits/BasicResumable.php @@ -0,0 +1,29 @@ + + * @copyright 2016-2020 Daniil Gentili + * @license https://opensource.org/licenses/MIT MIT + */ + +namespace danog\Loop\Test\Traits; + +use Generator; + +use function Amp\delay; + +trait BasicResumable +{ + use Basic; + /** + * Loop implementation. + * + * @return Generator + */ + public function loop(): Generator + { + yield $this->pause(0.1); + $this->ran = true; + } +}