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

First commit

This commit is contained in:
Daniil Gentili 2020-07-21 18:06:19 +02:00
commit 81a524f4d2
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
23 changed files with 1175 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
.vscode
build
composer.lock
phpunit.xml
vendor
.php_cs.cache
coverage

13
.php_cs.dist Normal file
View File

@ -0,0 +1,13 @@
<?php
$config = new Amp\CodeStyle\Config();
$config->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;

40
.travis.yml Normal file
View File

@ -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

7
LICENSE Normal file
View File

@ -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.

48
composer.json Normal file
View File

@ -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"
}
}

129
lib/Generic/GenericLoop.php Normal file
View File

@ -0,0 +1,129 @@
<?php
/**
* Generic loop.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
* @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|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>
*/
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;
}
}

View File

@ -0,0 +1,83 @@
<?php
/**
* Periodic loop.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
* @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 <daniil@daniil.it>
*/
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;
}
}

23
lib/Impl/Loop.php Normal file
View File

@ -0,0 +1,23 @@
<?php
/**
* Loop class.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
* @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 <daniil@daniil.it>
*/
abstract class Loop implements LoopInterface
{
use TraitsLoop;
}

View File

@ -0,0 +1,23 @@
<?php
/**
* Resumable loop class.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
* @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 <daniil@daniil.it>
*/
abstract class ResumableLoop implements ResumableLoopInterface
{
use TraitsResumableLoop;
}

View File

@ -0,0 +1,26 @@
<?php
/**
* Resumable signal loop class.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
* @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 <daniil@daniil.it>
*/
abstract class ResumableSignalLoop implements ResumableLoopInterface, SignalLoopInterface
{
use ResumableLoop;
use SignalLoop;
}

25
lib/Impl/SignalLoop.php Normal file
View File

@ -0,0 +1,25 @@
<?php
/**
* Signal loop class.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
* @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 <daniil@daniil.it>
*/
abstract class SignalLoop implements SignalLoopInterface
{
use Loop;
use TraitsSignalLoop;
}

48
lib/LoopInterface.php Normal file
View File

@ -0,0 +1,48 @@
<?php
/**
* Loop 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;
use Amp\Promise;
/**
* Loop interface.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
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;
}

View File

@ -0,0 +1,36 @@
<?php
/**
* Resumable loop 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;
use Amp\Promise;
/**
* Resumable loop interface.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
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;
}

View File

@ -0,0 +1,45 @@
<?php
/**
* Signal loop 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;
use Amp\Promise;
/**
* Signal loop interface.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
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<T>|\Generator<mixed,Promise|array<array-key,
* Promise>,mixed,Promise<T>|T> $promise The original promise or generator
*
* @psalm-return Promise<T|mixed>
*/
public function waitSignal($promise): Promise;
}

80
lib/Traits/Loop.php Normal file
View File

@ -0,0 +1,80 @@
<?php
/**
* Loop helper 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\Traits;
use Amp\Promise;
use function Amp\asyncCall;
/**
* Loop helper trait.
*
* Wraps the asynchronous generator methods with asynchronous promise-based methods
*
* @author Daniil Gentili <daniil@daniil.it>
*/
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;
}
}

View File

@ -0,0 +1,135 @@
<?php
/**
* Loop helper 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\Traits;
use Amp\Deferred;
use Amp\Loop as AmpLoop;
use Amp\Promise;
use Amp\Success;
use Closure;
/**
* Resumable loop helper trait.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
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;
}
}
}

81
lib/Traits/SignalLoop.php Normal file
View File

@ -0,0 +1,81 @@
<?php
/**
* Loop helper 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\Traits;
use Amp\Coroutine;
use Amp\Deferred;
use Amp\Promise;
/**
* Signal loop helper trait.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
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<T>|\Generator<mixed,Promise|array<array-key,
* Promise>,mixed,Promise<T>|T> $promise The original promise or generator
*
* @psalm-return Promise<T|mixed>
*/
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;
}
}

28
phpunit.xml.dist Normal file
View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
>
<testsuites>
<testsuite name="Amp Concurrent">
<directory>test</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">lib</directory>
</whitelist>
</filter>
<logging>
<log type="coverage-html" target="build/coverage"/>
</logging>
</phpunit>

15
psalm.xml Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0"?>
<psalm
errorLevel="1"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="lib" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
</psalm>

46
test/BasicInterface.php Normal file
View File

@ -0,0 +1,46 @@
<?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;
use danog\Loop\LoopInterface;
/**
* Basic loop test interface.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
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;
}

87
test/LoopTest.php Normal file
View File

@ -0,0 +1,87 @@
<?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 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;
}],
];
}
}

121
test/Traits/Basic.php Normal file
View File

@ -0,0 +1,121 @@
<?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 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;
}
}

View File

@ -0,0 +1,29 @@
<?php
/**
* Resumable 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 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;
}
}