2020-05-13 17:15:21 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace Amp\Test;
|
|
|
|
|
|
|
|
use Amp\AsyncGenerator;
|
|
|
|
use Amp\Deferred;
|
2020-05-19 17:49:40 +02:00
|
|
|
use Amp\DisposedException;
|
|
|
|
use Amp\PHPUnit\AsyncTestCase;
|
2020-05-13 17:15:21 +02:00
|
|
|
use Amp\PHPUnit\TestException;
|
2020-09-28 05:19:52 +02:00
|
|
|
use function Amp\async;
|
|
|
|
use function Amp\await;
|
2021-03-26 22:34:32 +01:00
|
|
|
use function Revolt\EventLoop\delay;
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-05-19 17:49:40 +02:00
|
|
|
class AsyncGeneratorTest extends AsyncTestCase
|
2020-05-13 17:15:21 +02:00
|
|
|
{
|
|
|
|
const TIMEOUT = 100;
|
|
|
|
|
2020-09-28 05:19:52 +02:00
|
|
|
public function testNonGeneratorCallable(): void
|
2020-05-13 17:15:21 +02:00
|
|
|
{
|
2020-05-19 00:01:14 +02:00
|
|
|
$this->expectException(\TypeError::class);
|
2020-05-13 17:15:21 +02:00
|
|
|
$this->expectExceptionMessage('The callable did not return a Generator');
|
|
|
|
|
|
|
|
new AsyncGenerator(function () {
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-09-28 05:19:52 +02:00
|
|
|
public function testYield(): void
|
2020-05-13 17:15:21 +02:00
|
|
|
{
|
2020-05-19 17:49:40 +02:00
|
|
|
$value = 1;
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-09-28 05:19:52 +02:00
|
|
|
$generator = new AsyncGenerator(function () use ($value) {
|
|
|
|
yield $value;
|
2020-05-19 17:49:40 +02:00
|
|
|
});
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2021-03-26 22:34:32 +01:00
|
|
|
self::assertSame($value, $generator->continue());
|
|
|
|
self::assertNull($generator->continue());
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
|
|
|
|
2020-09-28 05:19:52 +02:00
|
|
|
public function testSend(): void
|
2020-05-13 17:15:21 +02:00
|
|
|
{
|
2020-05-19 17:49:40 +02:00
|
|
|
$value = 1;
|
|
|
|
$send = 2;
|
2020-09-28 05:19:52 +02:00
|
|
|
$generator = new AsyncGenerator(function () use (&$result, $value) {
|
|
|
|
$result = yield $value;
|
2020-05-13 17:15:21 +02:00
|
|
|
});
|
2020-05-19 17:49:40 +02:00
|
|
|
|
2021-03-26 22:34:32 +01:00
|
|
|
self::assertSame($value, $generator->continue());
|
|
|
|
self::assertNull($generator->send($send));
|
|
|
|
self::assertSame($result, $send);
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
|
|
|
|
2020-09-28 05:19:52 +02:00
|
|
|
public function testSendBeforeYield(): void
|
2020-05-13 17:15:21 +02:00
|
|
|
{
|
2020-05-19 17:49:40 +02:00
|
|
|
$value = 1;
|
|
|
|
$send = 2;
|
2020-09-28 05:19:52 +02:00
|
|
|
$generator = new AsyncGenerator(function () use (&$result, $value) {
|
2020-10-07 06:40:14 +02:00
|
|
|
delay(100); // Wait so send() is called before $yield().
|
2020-09-28 05:19:52 +02:00
|
|
|
$result = yield $value;
|
2020-05-19 17:49:40 +02:00
|
|
|
});
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-09-28 05:19:52 +02:00
|
|
|
$promise1 = async(fn () => $generator->continue());
|
|
|
|
$promise2 = async(fn () => $generator->send($send));
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2021-03-26 22:34:32 +01:00
|
|
|
self::assertSame($value, await($promise1));
|
|
|
|
self::assertNull(await($promise2));
|
|
|
|
self::assertSame($result, $send);
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
|
|
|
|
2020-09-28 05:19:52 +02:00
|
|
|
public function testThrow(): void
|
2020-05-13 17:15:21 +02:00
|
|
|
{
|
2020-05-19 17:49:40 +02:00
|
|
|
$value = 1;
|
|
|
|
$exception = new \Exception;
|
2020-09-28 05:19:52 +02:00
|
|
|
$generator = new AsyncGenerator(function () use (&$result, $value) {
|
2020-05-19 17:49:40 +02:00
|
|
|
try {
|
2020-09-28 05:19:52 +02:00
|
|
|
$result = yield $value;
|
2020-05-19 17:49:40 +02:00
|
|
|
} catch (\Throwable $exception) {
|
|
|
|
$result = $exception;
|
|
|
|
}
|
|
|
|
});
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-09-28 05:19:52 +02:00
|
|
|
$promise1 = async(fn () => $generator->continue());
|
|
|
|
$promise2 = async(fn () => $generator->throw($exception));
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2021-03-26 22:34:32 +01:00
|
|
|
self::assertSame($value, await($promise1));
|
|
|
|
self::assertNull(await($promise2));
|
|
|
|
self::assertSame($result, $exception);
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
|
|
|
|
2020-09-28 05:19:52 +02:00
|
|
|
public function testThrowBeforeYield(): void
|
2020-05-13 17:15:21 +02:00
|
|
|
{
|
2020-05-19 17:49:40 +02:00
|
|
|
$value = 1;
|
|
|
|
$exception = new \Exception;
|
2020-09-28 05:19:52 +02:00
|
|
|
$generator = new AsyncGenerator(function () use (&$result, $value) {
|
2020-10-07 06:40:14 +02:00
|
|
|
delay(100); // Wait so throw() is called before $yield().
|
2020-05-19 17:49:40 +02:00
|
|
|
try {
|
2020-09-28 05:19:52 +02:00
|
|
|
$result = yield $value;
|
2020-05-19 17:49:40 +02:00
|
|
|
} catch (\Throwable $exception) {
|
|
|
|
$result = $exception;
|
|
|
|
}
|
2020-05-13 17:15:21 +02:00
|
|
|
});
|
2020-05-19 17:49:40 +02:00
|
|
|
|
2021-03-26 22:34:32 +01:00
|
|
|
self::assertSame($value, $generator->continue());
|
|
|
|
self::assertNull($generator->throw($exception));
|
|
|
|
self::assertSame($result, $exception);
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
|
|
|
|
2020-09-28 05:19:52 +02:00
|
|
|
public function testInitialSend(): void
|
2020-05-13 17:15:21 +02:00
|
|
|
{
|
2020-09-28 05:19:52 +02:00
|
|
|
$generator = new AsyncGenerator(function () {
|
|
|
|
yield 0;
|
2020-05-19 17:49:40 +02:00
|
|
|
});
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-05-19 17:49:40 +02:00
|
|
|
$this->expectException(\Error::class);
|
|
|
|
$this->expectExceptionMessage('Must initialize async generator by calling continue() first');
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-09-28 05:19:52 +02:00
|
|
|
$generator->send(0);
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
|
|
|
|
2020-09-28 05:19:52 +02:00
|
|
|
public function testInitialThrow(): void
|
2020-05-13 17:15:21 +02:00
|
|
|
{
|
2020-09-28 05:19:52 +02:00
|
|
|
$generator = new AsyncGenerator(function () {
|
|
|
|
yield 0;
|
2020-05-19 17:49:40 +02:00
|
|
|
});
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-05-19 17:49:40 +02:00
|
|
|
$this->expectException(\Error::class);
|
|
|
|
$this->expectExceptionMessage('Must initialize async generator by calling continue() first');
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-09-28 05:19:52 +02:00
|
|
|
$generator->throw(new \Exception);
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
|
|
|
|
2020-09-28 05:19:52 +02:00
|
|
|
public function testGetResult(): void
|
2020-05-13 17:15:21 +02:00
|
|
|
{
|
2020-05-19 17:49:40 +02:00
|
|
|
$value = 1;
|
2020-09-28 05:19:52 +02:00
|
|
|
$generator = new AsyncGenerator(function () use ($value) {
|
|
|
|
yield 0;
|
2020-05-19 17:49:40 +02:00
|
|
|
return $value;
|
2020-05-13 17:15:21 +02:00
|
|
|
});
|
2020-05-19 17:49:40 +02:00
|
|
|
|
2021-03-26 22:34:32 +01:00
|
|
|
self::assertSame(0, $generator->continue());
|
|
|
|
self::assertNull($generator->continue());
|
|
|
|
self::assertSame($value, $generator->getReturn());
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @depends testYield
|
|
|
|
*/
|
2020-09-28 05:19:52 +02:00
|
|
|
public function testFailingPromise(): void
|
2020-05-13 17:15:21 +02:00
|
|
|
{
|
|
|
|
$exception = new TestException;
|
2020-09-28 05:19:52 +02:00
|
|
|
$deferred = new Deferred;
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-09-28 05:19:52 +02:00
|
|
|
$generator = new AsyncGenerator(function () use ($deferred) {
|
|
|
|
yield await($deferred->promise());
|
2020-05-19 17:49:40 +02:00
|
|
|
});
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-05-19 17:49:40 +02:00
|
|
|
$deferred->fail($exception);
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-05-19 17:49:40 +02:00
|
|
|
try {
|
2020-09-28 05:19:52 +02:00
|
|
|
$generator->continue();
|
2021-03-26 22:34:32 +01:00
|
|
|
self::fail("Awaiting a failed promise should fail the pipeline");
|
2020-05-19 17:49:40 +02:00
|
|
|
} catch (TestException $reason) {
|
2021-03-26 22:34:32 +01:00
|
|
|
self::assertSame($reason, $exception);
|
2020-05-19 17:49:40 +02:00
|
|
|
}
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @depends testYield
|
|
|
|
*/
|
2020-09-28 05:19:52 +02:00
|
|
|
public function testBackPressure(): void
|
2020-05-13 17:15:21 +02:00
|
|
|
{
|
|
|
|
$output = '';
|
|
|
|
$yields = 5;
|
|
|
|
|
2020-09-28 05:19:52 +02:00
|
|
|
$generator = new AsyncGenerator(function () use (&$time, $yields) {
|
2020-05-19 17:49:40 +02:00
|
|
|
$time = \microtime(true);
|
|
|
|
for ($i = 0; $i < $yields; ++$i) {
|
2020-09-28 05:19:52 +02:00
|
|
|
yield $i;
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
2020-05-19 17:49:40 +02:00
|
|
|
$time = \microtime(true) - $time;
|
2020-05-13 17:15:21 +02:00
|
|
|
});
|
|
|
|
|
2020-09-28 05:19:52 +02:00
|
|
|
while (null !== $yielded = $generator->continue()) {
|
2020-05-21 17:11:22 +02:00
|
|
|
$output .= $yielded;
|
2020-10-07 06:40:14 +02:00
|
|
|
delay(self::TIMEOUT);
|
2020-05-19 17:49:40 +02:00
|
|
|
}
|
|
|
|
|
2020-05-13 17:15:21 +02:00
|
|
|
$expected = \implode('', \range(0, $yields - 1));
|
|
|
|
|
2021-03-26 22:34:32 +01:00
|
|
|
self::assertSame($expected, $output);
|
|
|
|
self::assertGreaterThan(self::TIMEOUT * ($yields - 1), $time * 1000);
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @depends testYield
|
|
|
|
*/
|
2020-09-28 05:19:52 +02:00
|
|
|
public function testAsyncGeneratorCoroutineThrows(): void
|
2020-05-13 17:15:21 +02:00
|
|
|
{
|
|
|
|
$exception = new TestException;
|
|
|
|
|
|
|
|
try {
|
2020-09-28 05:19:52 +02:00
|
|
|
$generator = new AsyncGenerator(function () use ($exception) {
|
|
|
|
yield 1;
|
2020-05-19 17:49:40 +02:00
|
|
|
throw $exception;
|
2020-05-13 17:15:21 +02:00
|
|
|
});
|
2020-05-19 17:49:40 +02:00
|
|
|
|
2021-03-26 22:34:32 +01:00
|
|
|
while ($generator->continue()) {
|
|
|
|
;
|
|
|
|
}
|
|
|
|
self::fail("The exception thrown from the generator should fail the pipeline");
|
2020-05-13 17:15:21 +02:00
|
|
|
} catch (TestException $caught) {
|
2021-03-26 22:34:32 +01:00
|
|
|
self::assertSame($exception, $caught);
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
|
|
|
}
|
2020-05-19 17:49:40 +02:00
|
|
|
|
2020-09-28 05:19:52 +02:00
|
|
|
public function testDisposal(): void
|
2020-05-19 17:49:40 +02:00
|
|
|
{
|
2020-09-28 05:19:52 +02:00
|
|
|
$invoked = false;
|
|
|
|
$generator = new AsyncGenerator(function () use (&$invoked) {
|
2020-05-19 17:49:40 +02:00
|
|
|
try {
|
2020-09-28 05:19:52 +02:00
|
|
|
yield 0;
|
|
|
|
} finally {
|
|
|
|
$invoked = true;
|
2020-05-19 17:49:40 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2020-09-28 05:19:52 +02:00
|
|
|
$promise = async(static fn () => $generator->getReturn());
|
2020-05-19 17:49:40 +02:00
|
|
|
|
2021-03-26 22:34:32 +01:00
|
|
|
self::assertSame(0, $generator->continue());
|
2020-05-19 17:49:40 +02:00
|
|
|
|
2021-03-26 22:34:32 +01:00
|
|
|
self::assertFalse($invoked);
|
2020-05-19 17:49:40 +02:00
|
|
|
|
2020-09-28 05:19:52 +02:00
|
|
|
$generator->dispose();
|
2020-05-19 17:49:40 +02:00
|
|
|
|
2020-09-28 05:19:52 +02:00
|
|
|
try {
|
2021-03-26 22:34:32 +01:00
|
|
|
self::assertSame(1, await($promise));
|
|
|
|
self::fail("Pipeline should have been disposed");
|
2020-09-28 05:19:52 +02:00
|
|
|
} catch (DisposedException $exception) {
|
2021-03-26 22:34:32 +01:00
|
|
|
self::assertTrue($invoked);
|
2020-09-28 05:19:52 +02:00
|
|
|
}
|
2020-05-19 17:49:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @depends testDisposal
|
|
|
|
*/
|
2020-09-28 05:19:52 +02:00
|
|
|
public function testYieldAfterDisposal(): void
|
2020-05-19 17:49:40 +02:00
|
|
|
{
|
2020-09-28 05:19:52 +02:00
|
|
|
$generator = new AsyncGenerator(function () use (&$exception) {
|
2020-05-19 17:49:40 +02:00
|
|
|
try {
|
2020-09-28 05:19:52 +02:00
|
|
|
yield 0;
|
2020-05-19 17:49:40 +02:00
|
|
|
} catch (\Throwable $exception) {
|
2020-09-28 05:19:52 +02:00
|
|
|
yield 1;
|
2020-05-19 17:49:40 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2020-09-28 05:19:52 +02:00
|
|
|
$generator->continue();
|
2020-05-19 17:49:40 +02:00
|
|
|
|
|
|
|
$generator->dispose();
|
|
|
|
|
|
|
|
$this->expectException(DisposedException::class);
|
|
|
|
|
2020-09-28 05:19:52 +02:00
|
|
|
$generator->getReturn();
|
2020-05-19 17:49:40 +02:00
|
|
|
}
|
2020-07-16 20:43:11 +02:00
|
|
|
|
2020-09-28 05:19:52 +02:00
|
|
|
public function testGeneratorStartsBeforeCallingContinue(): void
|
2020-07-16 20:43:11 +02:00
|
|
|
{
|
|
|
|
$invoked = false;
|
2020-09-28 05:19:52 +02:00
|
|
|
$generator = new AsyncGenerator(function () use (&$invoked) {
|
2020-07-16 20:43:11 +02:00
|
|
|
$invoked = true;
|
2020-09-28 05:19:52 +02:00
|
|
|
yield 0;
|
2020-07-16 20:43:11 +02:00
|
|
|
});
|
|
|
|
|
2021-03-26 22:34:32 +01:00
|
|
|
self::assertTrue($invoked);
|
2020-07-16 20:43:11 +02:00
|
|
|
|
2021-03-26 22:34:32 +01:00
|
|
|
self::assertSame(0, $generator->continue());
|
|
|
|
self::assertNull($generator->continue());
|
2020-07-16 20:43:11 +02:00
|
|
|
}
|
2020-11-10 19:05:47 +01:00
|
|
|
|
|
|
|
public function testTraversable(): void
|
|
|
|
{
|
|
|
|
$values = [];
|
|
|
|
|
|
|
|
$generator = new AsyncGenerator(function () {
|
|
|
|
yield 1;
|
|
|
|
yield 2;
|
|
|
|
yield 3;
|
|
|
|
});
|
|
|
|
|
|
|
|
foreach ($generator as $value) {
|
|
|
|
$values[] = $value;
|
|
|
|
}
|
|
|
|
|
2021-03-26 22:34:32 +01:00
|
|
|
self::assertSame([1, 2, 3], $values);
|
2020-11-10 19:05:47 +01:00
|
|
|
}
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|