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

Finish docs

This commit is contained in:
Daniil Gentili 2020-07-24 19:24:04 +02:00
parent 21eeab8680
commit 85af229ec6
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
11 changed files with 425 additions and 266 deletions

434
README.md
View File

@ -12,244 +12,202 @@ It's a more flexible and powerful alternative to [AMPHP](https://amphp.org)'s [r
composer require danog/loop composer require danog/loop
``` ```
## Examples ## API
* Basic
* [GenericLoop](#GenericLoop)
* [PeriodicLoop](#PeriodicLoop)
* Advanced
* [Loop](#Loop)
* [ResumableLoop](#ResumableLoop)
* [SignalLoop](#SignalLoop)
* [ResumableSignalLoop](#ResumableSignalLoop)
All loop APIs are defined by a set of [interfaces](https://github.com/danog/loop/tree/master/lib/Interfaces): however, to use them, you would usually have to extend only one of the [abstract class implementations](https://github.com/danog/loop/tree/master/lib). All loop APIs are defined by a set of [interfaces](https://github.com/danog/loop/tree/master/lib/Interfaces): however, to use them, you would usually have to extend only one of the [abstract class implementations](https://github.com/danog/loop/tree/master/lib).
### Loop ### Loop
[Interface](https://github.com/danog/loop/blob/master/lib/Interfaces/LoopInterface.php)
[Full example](https://github.com/danog/loop/blob/master/examples/2.%20Advanced/Loop.php)
A basic loop, capable of running in background (asynchronously) the code contained in the `loop` function. A basic loop, capable of running in background (asynchronously) the code contained in the `loop` function.
[Interface](https://github.com/danog/loop/blob/master/lib/Interfaces/LoopInterface.php): API:
```php ```php
namespace danog\Loop\Interfaces; namespace danog\Loop;
interface LoopInterface abstract class Loop
{
abstract public function loop(): \Generator;
abstract public function __toString(): string;
public function start(): bool;
public function isRunning(): bool;
protected function startedLoop(): void;
protected function exitedLoop(): void;
}
```
#### loop()
The `loop` [async coroutine](https://amphp.org/amp/coroutines/) will be run only once, every time the `start` method is called.
#### __toString()
This method should return the loop's name.
It's useful for implementing the [log methods](#startedLoop).
#### start()
Asynchronously starts the `loop` methods once in background.
Multiple calls to `start` will be ignored, returning `false` instead of `true`.
#### isRunning()
You can use the `isRunning` method to check if the loop is already running.
#### startedLoop()
Optionally, you can override this method to detect and log when the loop is started.
Make sure to always call the parent `startedLoop()` method to avoid issues.
You can use directly `$this` as loop name when logging, thanks to the custom [__toString](#__toString) method.
#### exitedLoop()
Optionally, you can override this method to detect and log when the loop is ended.
Make sure to always call the parent `exitedLoop()` method to avoid issues.
You can use directly `$this` as loop name when logging, thanks to the custom [__toString](#__toString) method.
### ResumableLoop
[Interface](https://github.com/danog/loop/blob/master/lib/Interfaces/ResumableLoopInterface.php)
[Full example](https://github.com/danog/loop/blob/master/examples/2.%20Advanced/ResumableLoop.php)
A way more useful loop that exposes APIs to pause and resume the execution of the loop, both from outside of the loop, and in a cron-like manner from inside of the loop.
```php
namespace danog\Loop;
abstract class ResumableLoop extends Loop
{
public function pause(?int $time = null): Promise;
public function resume(): Promise;
}
```
All methods from [Loop](#loop), plus:
#### pause()
Pauses the loop for the specified number of milliseconds, or forever if `null` is provided.
#### resume()
Forcefully resume the loop from the outside.
Returns a promise that is resolved when the loop is paused again.
### SignalLoop
[Interface](https://github.com/danog/loop/blob/master/lib/Interfaces/SignalLoopInterface.php)
[Full example](https://github.com/danog/loop/blob/master/examples/2.%20Advanced/SignalLoop.php)
Yet another loop interface that exposes APIs to send signals to the loop, useful to force the termination of a loop from the outside, or to send data into it.
```php
namespace danog\Loop;
abstract class SignalLoop extends Loop
{
public function signal($what): voi d;
public function waitSignal($promise): Promise;
}
```
All methods from [Loop](#loop), plus:
#### signal()
Sends a signal to the loop: can be anything, but typically `true` is often used as termination signal.
Signaling can be used as a message exchange mechanism a-la IPC, and can also be used to throw exceptions inside the loop.
#### waitSignal()
Resolve the provided promise or return|throw passed signal.
### ResumableSignalLoop
[Interface](https://github.com/danog/loop/blob/master/lib/Interfaces/ResumableSignalLoopInterface.php)
[Full example](https://github.com/danog/loop/blob/master/examples/2.%20Advanced/ResumableSignalLoop.php)
This is what you would usually use to build a full async loop.
All loop interfaces and loop implementations are combined into one class you can extend.
```php
namespace danog\Loop;
abstract class ResumableSignalLoop extends SignalLoop, ResumableSignalLoop
{
}
```
The class is actually composited using traits to feature all methods from [SignalLoop](#SignalLoop) and [ResumableSignalLoop](#ResumableSignalLoop).
### GenericLoop
[Class](https://github.com/danog/loop/blob/master/lib/Generic/GenericLoop.php)
[Full example](https://github.com/danog/loop/blob/master/examples/1.%20Basic/GenericLoop.php)
If you want a simpler way to use the `ResumableSignalLoop`, you can use the GenericLoop.
The constructor accepts three parameters:
```php
namespace danog\Loop\Generic;
class GenericLoop extends ResumableSignalLoop
{ {
/** /**
* Start the loop. * Stop the loop.
*
* Returns false if the loop is already running.
*
* @return bool
*/ */
public function start(): bool; const STOP = -1;
/** /**
* The actual loop function. * Pause the loop.
*
* @return \Generator
*/ */
public function loop(): \Generator; const PAUSE = null;
/** /**
* Get name of the loop. * Rerun the loop.
*/
const CONTINUE = 0;
/**
* Constructor.
*
* If possible, the callable will be bound to the current instance of the loop.
*
* @param callable $callable Callable to run
* @param string $name Loop name
*/
public function __construct(callable $callable, string $name)
/**
* 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, provided to constructor.
* *
* @return string * @return string
*/ */
public function __toString(): string; public function __toString(): string;
/**
* Check whether loop is running.
*
* @return boolean
*/
public function isRunning(): bool;
} }
``` ```
Usually one would extend the [Loop implementation](https://github.com/danog/loop/blob/master/lib/Loop.php), you have to define only the `loop` function. The callback will be bound to the `GenericLoop` instance: this means that you will be able to use `$this` as if the callback were actually the `loop` function (you can get the loop name by casting `$this` to a string, use the pause/waitSignal methods & so on).
The `loop` function will be run only once, every time the `start` method is called.
Multiple calls to `start` will be ignored, returning `false` instead of `true`.
You can use the `isRunning` method to check if the loop is already running.
The `__toString` method still has to be implemented in order to get the name of the loop, that will be used by the MadelineProto logging mechanism every time the loop starts/exits/fails to start.
```php
use danog\Loop\Loop;
class MyLoop extends Loop
{
private $callable;
public function __construct($API, $callable)
{
$this->API = $API;
$this->callable = $callable;
}
public function loop()
{
$MadelineProto = $this->API;
$logger = &$MadelineProto->logger;
$callable = $this->callable;
$result = null;
while (true) {
$params = yield $callable($result);
$result = yield $MadelineProto->messages->sendMessage($params);
}
}
public function __toString(): string
{
return "my custom loop";
}
}
```
The loop can be instantiated and `start()`ed, and this will automatically run the code in the loop in background.
If, however, only your loop is started (without an event handling loop), you have to pass the promise returned by `$loop->start()` to `$MadelineProto->loop`.
Do NOT do this if you've already started `$MadelineProto->loop()`.
```php
$loop = new MyLoop;
$MadelineProto->loop($loop->start());
```
### ResumableLoop
A way more useful loop that exposes APIs to pause and resume the execution of the loop, both from outside of the loop, and in a cron-like manner from inside of the loop.
[Interface](https://github.com/danog/MadelineProto/blob/master/src/danog/MadelineProto/Loop/ResumableLoopInterface.php):
```php
namespace danog\Loop;
interface ResumableLoopInterface extends LoopInterface
{
/**
* Pause the loop.
*
* @param int $time For how long to pause the loop, if null will pause forever (until resume is called from outside of the loop)
*
* @return Promise
*/
public function pause($time = null): Promise;
/**
* Resume the loop.
*
* @return void
*/
public function resume();
}
```
Usually one would extend the [ResumableSignalLoop implementation](https://github.com/danog/MadelineProto/blob/master/src/danog/MadelineProto/Loop/Impl/ResumableSignalLoop.php).
An example implementation can be seen in the [ResumableSignalLoop section of this page](#resumablesignalloop).
### SignalLoop
Yet another loop interface that exposes APIs to send signals to the loop, useful to force the termination of a loop from the outside, or to send data into it.
[Interface](https://github.com/danog/MadelineProto/blob/master/src/danog/MadelineProto/Loop/SignalLoopInterface.php):
```php
namespace danog\Loop;
interface SignalLoopInterface extends LoopInterface
{
/**
* Resolve the promise or return|throw the signal.
*
* @param Promise $promise The origin promise
*
* @return Promise
*/
public function waitSignal($promise): Promise;
/**
* Send a signal to the the loop.
*
* @param Exception|any $data Signal to send
*
* @return void
*/
public function signal($data);
}
```
Usually one would extend the [ResumableSignalLoop implementation](https://github.com/danog/MadelineProto/blob/master/src/danog/MadelineProto/Loop/Impl/ResumableSignalLoop.php).
An example implementation can be seen in the [ResumableSignalLoop section of this page](#resumablesignalloop).
If you want, you can also extend only the [SignalLoop implementation](https://github.com/danog/MadelineProto/blob/master/src/danog/MadelineProto/Loop/Impl/SignalLoop.php), but usually a combination of the SignalLoop and ResumableLoop implementations is used, so read on to find out how to do that.
### ResumableSignalLoop
This is what you would usually use to build a full async loop.
All loop interfaces and loop implementations are combined into one single abstract class you can extend.
```php
use danog\ResumableSignalLoop;
class MySuperLoop extends ResumableSignalLoop
{
private $timeout;
public function __construct($API, $timeout)
{
$this->API = $API;
$this->timeout = $timeout;
}
public function loop()
{
$MadelineProto = $this->API;
$logger = &$MadelineProto->logger;
while (true) {
$t = time();
$result = yield $this->waitSignal($this->pause($this->timeout));
if ($result <= 0) {
return;
} else if ($result > 0) {
$this->timeout = $result;
}
$t = time() - $t;
$result = yield $MadelineProto->messages->sendMessage(
[
'peer' => '...',
'message' => "Resumed after $t seconds of timeout"
]
);
}
}
public function __toString(): string
{
return "my cron signal loop";
}
}
```
[ResumableSignalLoop implementation](https://github.com/danog/MadelineProto/blob/master/src/danog/MadelineProto/Loop/Impl/ResumableSignalLoop.php).
As with the [Loop](#loop).
The difference now is that you can use the `pause` method to pause execution of the loop for a certain period of time (in seconds, supports decimals).
If `null` is passed, execution will be suspended forever (or until `resume` is called from outside of the loop).
If the promise returned by `pause` (or by any other async method) is passed to `waitSignal`, and the result is yielded, execution will be suspended for the specified amount of time|forever, or until a signal is received through the `signal` method.
The passed signal will then be returned as result of the `waitSignal` method, and can be used to stop the loop, or simply as a message exchange mechanism.
### GenericLoop
If you want a simpler way to use the `ResumableSignalLoop`, you can use the [GenericLoop](https://github.com/danog/MadelineProto/blob/master/src/danog/MadelineProto/Loop/Generic/GenericLoop.php).
The constructor accepts three parameters:
```php
/**
* Constructor
*
* @param \danog\API $API Instance of MadelineProto
* @param callback $callback Callback to run
* @param string $name Fetcher name
*/
public function __construct($API, $callback, $name) { // ...
```
Example:
```php
use danog\Loop\Generic\GenericLoop;
$loop = new GenericLoop(
$MadelineProto,
function () {
yield $this->API->messages->sendMessage(['peer' => '...', 'message' => 'Hi every 2 seconds']);
return 2;
},
"My super loop"
);
$loop->start();
```
The callback will be bound to the GenericLoop instance: this means that you will be able to use `$this` as if the callback were actually the `loop` function (you can access the API property, use the pause/waitSignal methods & so on).
The return value of the callable can be: The return value of the callable can be:
* A number - the loop will be paused for the specified number of seconds * A number - the loop will be paused for the specified number of seconds
* `GenericLoop::STOP` - The loop will stop * `GenericLoop::STOP` - The loop will stop
@ -260,32 +218,34 @@ If the callable does not return anything, the loop will behave is if `GenericLoo
### PeriodicLoop ### PeriodicLoop
[Class](https://github.com/danog/loop/blob/master/lib/Generic/PeriodicLoop.php)
[Full example](https://github.com/danog/loop/blob/master/examples/1.%20Basic/PeriodicLoop.php)
If you simply want to execute an action every N seconds, [PeriodicLoop](https://github.com/danog/MadelineProto/blob/master/src/danog/MadelineProto/Loop/Generic/PeriodicLoop.php) is the way to go. If you simply want to execute an action every N seconds, [PeriodicLoop](https://github.com/danog/MadelineProto/blob/master/src/danog/MadelineProto/Loop/Generic/PeriodicLoop.php) is the way to go.
The constructor accepts four parameters:
```php ```php
namespace danog\Loop\Generic;
class PeriodicLoop extends ResumableSignalLoop
{
/** /**
* Constructor. * Constructor.
* *
* @param \danog\API $API Instance of MTProto class * If possible, the callable will be bound to the current instance of the loop.
* @param callable $callback Callback to call *
* @param string $name Loop name * @param callable $callback Callback to call
* @param int|float $timeout Loop timeout * @param string $name Loop name
* @param ?int $interval Loop interval
*/ */
public function __construct($API, callable $callback, string $name, $timeout) { // ... public function __construct(callable $callback, string $name, ?int $interval)
/**
* Get name of the loop, passed to the constructor.
*
* @return string
*/
public function __toString(): string
}
``` ```
Example: `PeriodicLoop` runs a callback at a periodic interval.
```php The callback will be bound to the `GenericLoop` instance: this means that you will be able to use `$this` as if the callback were actually the `loop` function (you can get the loop name by , use the pause/waitSignal methods & so on).
use danog\Loop\Generic\PeriodicLoop; The loop can be stopped from the outside or from the inside by signaling or returning `true`.
$loop = new PeriodicLoop(
$MadelineProto,
function () use (&$loop) {
yield $this->API->messages->sendMessage(['peer' => '...', 'message' => 'Hi every 2 seconds']);
},
"My super loop",
2
);
$loop->start();
```
Unlike `GenericLoop`, the callback will **not** be bound to the GenericLoop instance.
You can still command the loop by using the pause/waitSignal methods from the outside or by capturing the loop instance in a closure like shown above.

View File

@ -11,7 +11,13 @@ Loop::run(function () {
/** @var GenericLoop[] */ /** @var GenericLoop[] */
$loops = []; $loops = [];
for ($x = 0; $x < 10; $x++) { for ($x = 0; $x < 10; $x++) {
$loop = new GenericLoop("Loop number $x"); $callable = function () {
static $number = 0;
echo "$this: $number".PHP_EOL;
$number++;
return $number < 10 ? 1000 : GenericLoop::STOP;
};
$loop = new GenericLoop($callable, "Loop number $x");
$loop->start(); $loop->start();
yield delay(100); yield delay(100);
$loops []= $loop; $loops []= $loop;
@ -24,7 +30,5 @@ Loop::run(function () {
echo "OK done, waiting 5 more seconds!".PHP_EOL; echo "OK done, waiting 5 more seconds!".PHP_EOL;
yield delay(5000); yield delay(5000);
echo "Closing all loops!".PHP_EOL; echo "Closing all loops!".PHP_EOL;
foreach ($loops as $loop) { yield delay(10);
$loop->signal(true);
}
}); });

View File

@ -0,0 +1,34 @@
<?php
require 'vendor/autoload.php';
use Amp\Loop;
use danog\Loop\Generic\PeriodicLoop;
use function Amp\delay;
Loop::run(function () {
/** @var PeriodicLoop[] */
$loops = [];
for ($x = 0; $x < 10; $x++) {
$callable = function () {
static $number = 0;
echo "$this: $number".PHP_EOL;
$number++;
return $number == 10;
};
$loop = new PeriodicLoop($callable, "Loop number $x", 1000);
$loop->start();
yield delay(100);
$loops []= $loop;
}
yield delay(5000);
echo "Resuming prematurely all loops!".PHP_EOL;
foreach ($loops as $loop) {
$loop->resume();
}
echo "OK done, waiting 5 more seconds!".PHP_EOL;
yield delay(5000);
echo "Closing all loops!".PHP_EOL;
yield delay(10);
});

View File

@ -33,7 +33,7 @@ class MyLoop extends ResumableLoop
{ {
$number = 0; $number = 0;
while (true) { while (true) {
yield $this->pause(1); yield $this->pause(1000);
echo "$this: $number".PHP_EOL; echo "$this: $number".PHP_EOL;
$number++; $number++;
} }

View File

@ -33,7 +33,7 @@ class ResSigLoop extends ResumableSignalLoop
{ {
$number = 0; $number = 0;
while (true) { while (true) {
if (yield $this->waitSignal($this->pause(1))) { if (yield $this->waitSignal($this->pause(1000))) {
echo "Got exit signal in $this!".PHP_EOL; echo "Got exit signal in $this!".PHP_EOL;
return; return;
} }

View File

@ -67,6 +67,8 @@ class GenericLoop extends ResumableSignalLoop
/** /**
* Constructor. * Constructor.
* *
* If possible, the callable will be bound to the current instance of the loop.
*
* @param callable $callable Callable to run * @param callable $callable Callable to run
* @param string $name Loop name * @param string $name Loop name
* *
@ -74,6 +76,13 @@ class GenericLoop extends ResumableSignalLoop
*/ */
public function __construct(callable $callable, string $name) public function __construct(callable $callable, string $name)
{ {
if ($callable instanceof \Closure) {
try {
$callable = $callable->bindTo($this);
} catch (\Throwable $e) {
// Might cause an error for wrapped object methods
}
}
$this->callable = $callable; $this->callable = $callable;
$this->name = $name; $this->name = $name;
} }
@ -112,14 +121,12 @@ class GenericLoop extends ResumableSignalLoop
* @param integer $timeout Pause duration, 0 = forever * @param integer $timeout Pause duration, 0 = forever
* *
* @return void * @return void
*
* @codeCoverageIgnore
*/ */
protected function reportPause(int $timeout): void protected function reportPause(int $timeout): void
{ {
} }
/** /**
* Get loop name. * Get loop name, provided to constructor.
* *
* @return string * @return string
*/ */

View File

@ -53,6 +53,8 @@ class PeriodicLoop extends ResumableSignalLoop
/** /**
* Constructor. * Constructor.
* *
* If possible, the callable will be bound to the current instance of the loop.
*
* @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
@ -61,6 +63,13 @@ class PeriodicLoop extends ResumableSignalLoop
*/ */
public function __construct(callable $callback, string $name, ?int $interval) public function __construct(callable $callback, string $name, ?int $interval)
{ {
if ($callback instanceof \Closure) {
try {
$callback = $callback->bindTo($this);
} catch (\Throwable $e) {
// Might cause an error for wrapped object methods
}
}
$this->callback = $callback; $this->callback = $callback;
$this->name = $name; $this->name = $name;
$this->interval = $interval; $this->interval = $interval;
@ -93,7 +102,7 @@ class PeriodicLoop extends ResumableSignalLoop
} }
} }
/** /**
* Get name of the loop. * Get name of the loop, passed to the constructor.
* *
* @return string * @return string
*/ */

View File

@ -24,7 +24,7 @@ use Closure;
trait ResumableLoop trait ResumableLoop
{ {
use Loop { use Loop {
exitedLoop as parentExitedLoop; exitedLoop as private parentExitedLoop;
} }
/** /**
* Resume deferred. * Resume deferred.

View File

@ -35,11 +35,32 @@ class GenericTest extends AsyncTestCase
{ {
$runCount = 0; $runCount = 0;
$pauseTime = GenericLoop::PAUSE; $pauseTime = GenericLoop::PAUSE;
$callable = function () use (&$runCount, &$pauseTime) { $callable = function () use (&$runCount, &$pauseTime, &$zis) {
$zis = $this;
$runCount++; $runCount++;
return $pauseTime; return $pauseTime;
}; };
yield from $this->fixtureAssertions($callable, $runCount, $pauseTime, $stopSig); yield from $this->fixtureAssertions($callable, $runCount, $pauseTime, $stopSig, $zis, true);
$obj = new class() {
public $pauseTime = GenericLoop::PAUSE;
public $runCount = 0;
public function run()
{
$this->runCount++;
return $this->pauseTime;
}
};
yield from $this->fixtureAssertions([$obj, 'run'], $obj->runCount, $obj->pauseTime, $stopSig, $zisNew, false);
$obj = new class() {
public $pauseTime = GenericLoop::PAUSE;
public $runCount = 0;
public function run()
{
$this->runCount++;
return $this->pauseTime;
}
};
yield from $this->fixtureAssertions(\Closure::fromCallable([$obj, 'run']), $obj->runCount, $obj->pauseTime, $stopSig, $zisNew, false);
} }
/** /**
* Test generator loop. * Test generator loop.
@ -54,12 +75,35 @@ class GenericTest extends AsyncTestCase
{ {
$runCount = 0; $runCount = 0;
$pauseTime = GenericLoop::PAUSE; $pauseTime = GenericLoop::PAUSE;
$callable = function () use (&$runCount, &$pauseTime): \Generator { $callable = function () use (&$runCount, &$pauseTime, &$zis): \Generator {
$zis = $this;
yield delay(1); yield delay(1);
$runCount++; $runCount++;
return $pauseTime; return $pauseTime;
}; };
yield from $this->fixtureAssertions($callable, $runCount, $pauseTime, $stopSig); yield from $this->fixtureAssertions($callable, $runCount, $pauseTime, $stopSig, $zis, true);
$obj = new class() {
public $pauseTime = GenericLoop::PAUSE;
public $runCount = 0;
public function run(): \Generator
{
yield delay(1);
$this->runCount++;
return $this->pauseTime;
}
};
yield from $this->fixtureAssertions([$obj, 'run'], $obj->runCount, $obj->pauseTime, $stopSig, $zisNew, false);
$obj = new class() {
public $pauseTime = GenericLoop::PAUSE;
public $runCount = 0;
public function run(): \Generator
{
yield delay(1);
$this->runCount++;
return $this->pauseTime;
}
};
yield from $this->fixtureAssertions(\Closure::fromCallable([$obj, 'run']), $obj->runCount, $obj->pauseTime, $stopSig, $zisNew, false);
} }
/** /**
* Test promise loop. * Test promise loop.
@ -74,11 +118,32 @@ class GenericTest extends AsyncTestCase
{ {
$runCount = 0; $runCount = 0;
$pauseTime = GenericLoop::PAUSE; $pauseTime = GenericLoop::PAUSE;
$callable = function () use (&$runCount, &$pauseTime): Promise { $callable = function () use (&$runCount, &$pauseTime, &$zis): Promise {
$zis = $this;
$runCount++; $runCount++;
return new Success($pauseTime); return new Success($pauseTime);
}; };
yield from $this->fixtureAssertions($callable, $runCount, $pauseTime, $stopSig); yield from $this->fixtureAssertions($callable, $runCount, $pauseTime, $stopSig, $zis, true);
$obj = new class() {
public $pauseTime = GenericLoop::PAUSE;
public $runCount = 0;
public function run(): Promise
{
$this->runCount++;
return new Success($this->pauseTime);
}
};
yield from $this->fixtureAssertions([$obj, 'run'], $obj->runCount, $obj->pauseTime, $stopSig, $zisNew, false);
$obj = new class() {
public $pauseTime = GenericLoop::PAUSE;
public $runCount = 0;
public function run(): Promise
{
$this->runCount++;
return new Success($this->pauseTime);
}
};
yield from $this->fixtureAssertions(\Closure::fromCallable([$obj, 'run']), $obj->runCount, $obj->pauseTime, $stopSig, $zisNew, false);
} }
/** /**
* Fixture assertions for started loop. * Fixture assertions for started loop.
@ -96,14 +161,16 @@ class GenericTest extends AsyncTestCase
/** /**
* Run fixture assertions. * Run fixture assertions.
* *
* @param \Closure $closure Closure * @param callable $closure Closure
* @param integer $runCount Run count * @param integer $runCount Run count
* @param ?integer $pauseTime Pause time * @param ?integer $pauseTime Pause time
* @param bool $stopSig Whether to stop with signal * @param bool $stopSig Whether to stop with signal
* @param bool $zis Reference to closure's this
* @param bool $checkZis Whether to check zis
* *
* @return \Generator * @return \Generator
*/ */
private function fixtureAssertions(\Closure $closure, int &$runCount, ?int &$pauseTime, bool $stopSig = false): \Generator private function fixtureAssertions(callable $closure, int &$runCount, ?int &$pauseTime, bool $stopSig, &$zis, bool $checkZis): \Generator
{ {
$loop = new class($closure, Fixtures::LOOP_NAME) extends GenericLoop implements LoggingPauseInterface { $loop = new class($closure, Fixtures::LOOP_NAME) extends GenericLoop implements LoggingPauseInterface {
use LoggingPause; use LoggingPause;
@ -119,6 +186,11 @@ class GenericTest extends AsyncTestCase
$loop->start(); $loop->start();
yield delay(2); yield delay(2);
if ($checkZis) {
$this->assertEquals($loop, $zis);
} else {
$this->assertNull($zis);
}
$this->fixtureStarted($loop); $this->fixtureStarted($loop);
$this->assertEquals(1, $runCount); $this->assertEquals(1, $runCount);

View File

@ -35,11 +35,32 @@ class PeriodicTest extends AsyncTestCase
{ {
$runCount = 0; $runCount = 0;
$retValue = false; $retValue = false;
$callable = function () use (&$runCount, &$retValue) { $callable = function () use (&$runCount, &$retValue, &$zis) {
$zis = $this;
$runCount++; $runCount++;
return $retValue; return $retValue;
}; };
yield from $this->fixtureAssertions($callable, $runCount, $retValue, $stopSig); yield from $this->fixtureAssertions($callable, $runCount, $retValue, $stopSig, $zis, true);
$obj = new class() {
public $retValue = false;
public $runCount = 0;
public function run()
{
$this->runCount++;
return $this->retValue;
}
};
yield from $this->fixtureAssertions([$obj, 'run'], $obj->runCount, $obj->retValue, $stopSig, $zisNew, false);
$obj = new class() {
public $retValue = false;
public $runCount = 0;
public function run()
{
$this->runCount++;
return $this->retValue;
}
};
yield from $this->fixtureAssertions(\Closure::fromCallable([$obj, 'run']), $obj->runCount, $obj->retValue, $stopSig, $zisNew, false);
} }
/** /**
* Test generator loop. * Test generator loop.
@ -54,12 +75,35 @@ class PeriodicTest extends AsyncTestCase
{ {
$runCount = 0; $runCount = 0;
$retValue = false; $retValue = false;
$callable = function () use (&$runCount, &$retValue): \Generator { $callable = function () use (&$runCount, &$retValue, &$zis): \Generator {
$zis = $this;
yield delay(1); yield delay(1);
$runCount++; $runCount++;
return $retValue; return $retValue;
}; };
yield from $this->fixtureAssertions($callable, $runCount, $retValue, $stopSig); yield from $this->fixtureAssertions($callable, $runCount, $retValue, $stopSig, $zis, true);
$obj = new class() {
public $retValue = false;
public $runCount = 0;
public function run(): \Generator
{
yield delay(1);
$this->runCount++;
return $this->retValue;
}
};
yield from $this->fixtureAssertions([$obj, 'run'], $obj->runCount, $obj->retValue, $stopSig, $zisNew, false);
$obj = new class() {
public $retValue = false;
public $runCount = 0;
public function run(): \Generator
{
yield delay(1);
$this->runCount++;
return $this->retValue;
}
};
yield from $this->fixtureAssertions(\Closure::fromCallable([$obj, 'run']), $obj->runCount, $obj->retValue, $stopSig, $zisNew, false);
} }
/** /**
* Test promise loop. * Test promise loop.
@ -74,11 +118,32 @@ class PeriodicTest extends AsyncTestCase
{ {
$runCount = 0; $runCount = 0;
$retValue = false; $retValue = false;
$callable = function () use (&$runCount, &$retValue): Promise { $callable = function () use (&$runCount, &$retValue, &$zis): Promise {
$zis = $this;
$runCount++; $runCount++;
return new Success($retValue); return new Success($retValue);
}; };
yield from $this->fixtureAssertions($callable, $runCount, $retValue, $stopSig); yield from $this->fixtureAssertions($callable, $runCount, $retValue, $stopSig, $zis, true);
$obj = new class() {
public $retValue = false;
public $runCount = 0;
public function run(): Promise
{
$this->runCount++;
return new Success($this->retValue);
}
};
yield from $this->fixtureAssertions([$obj, 'run'], $obj->runCount, $obj->retValue, $stopSig, $zisNew, false);
$obj = new class() {
public $retValue = false;
public $runCount = 0;
public function run(): Promise
{
$this->runCount++;
return new Success($this->retValue);
}
};
yield from $this->fixtureAssertions(\Closure::fromCallable([$obj, 'run']), $obj->runCount, $obj->retValue, $stopSig, $zisNew, false);
} }
/** /**
* Fixture assertions for started loop. * Fixture assertions for started loop.
@ -96,14 +161,16 @@ class PeriodicTest extends AsyncTestCase
/** /**
* Run fixture assertions. * Run fixture assertions.
* *
* @param \Closure $closure Closure * @param callable $closure Closure
* @param integer $runCount Run count * @param integer $runCount Run count
* @param bool $retValue Pause time * @param bool $retValue Pause time
* @param bool $stopSig Whether to stop with signal * @param bool $stopSig Whether to stop with signal
* @param bool $zis Reference to closure's this
* @param bool $checkZis Whether to check zis
* *
* @return \Generator * @return \Generator
*/ */
private function fixtureAssertions(\Closure $closure, int &$runCount, bool &$retValue, bool $stopSig = false): \Generator private function fixtureAssertions(callable $closure, int &$runCount, bool &$retValue, bool $stopSig, &$zis, bool $checkZis): \Generator
{ {
$loop = new class($closure, Fixtures::LOOP_NAME, 100) extends PeriodicLoop implements LoggingInterface { $loop = new class($closure, Fixtures::LOOP_NAME, 100) extends PeriodicLoop implements LoggingInterface {
use Logging; use Logging;
@ -118,6 +185,11 @@ class PeriodicTest extends AsyncTestCase
$loop->start(); $loop->start();
yield delay(2); yield delay(2);
if ($checkZis) {
$this->assertEquals($loop, $zis);
} else {
$this->assertNull($zis);
}
$this->fixtureStarted($loop); $this->fixtureStarted($loop);
$this->assertEquals(1, $runCount); $this->assertEquals(1, $runCount);

View File

@ -54,6 +54,7 @@ trait LoggingPause
*/ */
protected function reportPause(int $timeout): void protected function reportPause(int $timeout): void
{ {
parent::reportPause($timeout);
$this->pauseCount++; $this->pauseCount++;
$this->lastPause= $timeout; $this->lastPause= $timeout;
} }