1
0
mirror of https://github.com/danog/amp.git synced 2024-12-11 00:49:48 +01:00
amp/test/PipelineSourceTest.php

369 lines
9.9 KiB
PHP
Raw Normal View History

2020-05-13 17:15:21 +02:00
<?php
namespace Amp\Test;
use Amp\DisposedException;
2020-05-21 17:11:22 +02:00
use Amp\PHPUnit\AsyncTestCase;
2020-08-23 16:18:28 +02:00
use Amp\PipelineSource;
2020-05-13 17:15:21 +02:00
use Amp\Promise;
use Amp\Success;
2020-09-28 05:19:52 +02:00
use function Amp\async;
use function Amp\await;
use function Amp\defer;
use function Amp\delay;
2020-05-13 17:15:21 +02:00
2020-08-23 16:18:28 +02:00
class PipelineSourceTest extends AsyncTestCase
2020-05-13 17:15:21 +02:00
{
2020-08-23 16:18:28 +02:00
/** @var PipelineSource */
2020-09-28 05:19:52 +02:00
private PipelineSource $source;
2020-05-13 17:15:21 +02:00
2020-09-28 05:19:52 +02:00
public function setUp(): void
2020-05-13 17:15:21 +02:00
{
2020-05-21 17:11:22 +02:00
parent::setUp();
2020-08-23 16:18:28 +02:00
$this->source = new PipelineSource;
2020-05-13 17:15:21 +02:00
}
2020-09-28 05:19:52 +02:00
public function testEmit(): void
2020-05-13 17:15:21 +02:00
{
2020-05-28 19:59:55 +02:00
$value = 'Emited Value';
2020-05-13 17:15:21 +02:00
2020-05-28 19:59:55 +02:00
$promise = $this->source->emit($value);
2020-08-23 16:18:28 +02:00
$pipeline = $this->source->pipe();
2020-05-13 17:15:21 +02:00
2020-09-28 05:19:52 +02:00
$this->assertSame($value, $pipeline->continue());
2020-09-28 05:19:52 +02:00
$continue = async(fn () => $pipeline->continue()); // Promise will not resolve until another value is emitted or pipeline completed.
2020-05-21 17:11:22 +02:00
$this->assertInstanceOf(Promise::class, $promise);
2020-09-28 05:19:52 +02:00
$this->assertNull(await($promise));
2020-05-21 17:11:22 +02:00
$this->assertFalse($this->source->isComplete());
2020-05-21 17:11:22 +02:00
$this->source->complete();
2020-05-13 17:15:21 +02:00
$this->assertTrue($this->source->isComplete());
2020-09-28 05:19:52 +02:00
$this->assertNull(await($continue));
2020-05-13 17:15:21 +02:00
}
2020-09-28 05:19:52 +02:00
public function testFail(): void
{
$this->assertFalse($this->source->isComplete());
$this->source->fail($exception = new \Exception);
$this->assertTrue($this->source->isComplete());
2020-08-23 16:18:28 +02:00
$pipeline = $this->source->pipe();
try {
2020-09-28 05:19:52 +02:00
$pipeline->continue();
} catch (\Exception $caught) {
$this->assertSame($exception, $caught);
}
}
2020-05-13 17:15:21 +02:00
/**
2020-05-28 19:59:55 +02:00
* @depends testEmit
2020-05-13 17:15:21 +02:00
*/
2020-09-28 05:19:52 +02:00
public function testEmitAfterComplete(): void
2020-05-13 17:15:21 +02:00
{
$this->expectException(\Error::class);
2020-08-23 16:18:28 +02:00
$this->expectExceptionMessage('Pipelines cannot emit values after calling complete');
2020-05-13 17:15:21 +02:00
$this->source->complete();
2020-05-28 19:59:55 +02:00
$this->source->emit(1);
2020-05-13 17:15:21 +02:00
}
/**
2020-05-28 19:59:55 +02:00
* @depends testEmit
2020-05-13 17:15:21 +02:00
*/
2020-09-28 05:19:52 +02:00
public function testEmittingNull(): void
2020-05-13 17:15:21 +02:00
{
2020-05-21 17:11:22 +02:00
$this->expectException(\TypeError::class);
2020-08-23 16:18:28 +02:00
$this->expectExceptionMessage('Pipelines cannot emit NULL');
2020-05-21 17:11:22 +02:00
2020-05-28 19:59:55 +02:00
$this->source->emit(null);
2020-05-13 17:15:21 +02:00
}
/**
2020-05-28 19:59:55 +02:00
* @depends testEmit
2020-05-13 17:15:21 +02:00
*/
2020-09-28 05:19:52 +02:00
public function testEmittingPromise(): void
2020-05-13 17:15:21 +02:00
{
2020-05-21 17:11:22 +02:00
$this->expectException(\TypeError::class);
2020-08-23 16:18:28 +02:00
$this->expectExceptionMessage('Pipelines cannot emit promises');
2020-05-13 17:15:21 +02:00
2020-05-28 19:59:55 +02:00
$this->source->emit(new Success);
2020-05-13 17:15:21 +02:00
}
2020-09-28 05:19:52 +02:00
public function testDoubleComplete(): void
2020-05-13 17:15:21 +02:00
{
$this->expectException(\Error::class);
2020-08-23 16:18:28 +02:00
$this->expectExceptionMessage('Pipeline has already been completed');
2020-05-13 17:15:21 +02:00
$this->source->complete();
$this->source->complete();
}
2020-09-28 05:19:52 +02:00
public function testDoubleFail(): void
2020-05-13 17:15:21 +02:00
{
$this->expectException(\Error::class);
2020-08-23 16:18:28 +02:00
$this->expectExceptionMessage('Pipeline has already been completed');
2020-05-13 17:15:21 +02:00
$this->source->fail(new \Exception);
$this->source->fail(new \Exception);
}
2020-09-28 05:19:52 +02:00
public function testDoubleStart(): void
2020-05-13 17:15:21 +02:00
{
2020-08-23 16:18:28 +02:00
$pipeline = $this->source->pipe();
2020-05-13 17:15:21 +02:00
$this->expectException(\Error::class);
2020-08-23 16:18:28 +02:00
$this->expectExceptionMessage('A pipeline may be started only once');
2020-05-13 17:15:21 +02:00
2020-08-23 16:18:28 +02:00
$pipeline = $this->source->pipe();
2020-05-13 17:15:21 +02:00
}
2020-09-28 05:19:52 +02:00
public function testEmitAfterContinue(): void
2020-05-13 17:15:21 +02:00
{
2020-05-28 19:59:55 +02:00
$value = 'Emited Value';
2020-05-21 17:11:22 +02:00
2020-08-23 16:18:28 +02:00
$pipeline = $this->source->pipe();
2020-05-13 17:15:21 +02:00
2020-09-28 05:19:52 +02:00
$promise = async(fn () => $pipeline->continue());
2020-05-13 17:15:21 +02:00
2020-05-28 19:59:55 +02:00
$backPressure = $this->source->emit($value);
2020-05-13 17:15:21 +02:00
2020-09-28 05:19:52 +02:00
$this->assertSame($value, await($promise));
2020-05-13 17:15:21 +02:00
2020-09-28 05:19:52 +02:00
$promise = async(fn () => $pipeline->continue());
2020-05-21 17:11:22 +02:00
2020-09-28 05:19:52 +02:00
$this->assertNull(await($backPressure));
$this->source->complete();
2020-05-13 17:15:21 +02:00
}
2020-09-28 05:19:52 +02:00
public function testContinueAfterComplete(): void
2020-05-13 17:15:21 +02:00
{
2020-08-23 16:18:28 +02:00
$pipeline = $this->source->pipe();
2020-05-13 17:15:21 +02:00
2020-05-21 17:11:22 +02:00
$this->source->complete();
2020-05-13 17:15:21 +02:00
2020-09-28 05:19:52 +02:00
$this->assertNull($pipeline->continue());
2020-05-13 17:15:21 +02:00
}
2020-09-28 05:19:52 +02:00
public function testContinueAfterFail(): void
2020-05-13 17:15:21 +02:00
{
2020-08-23 16:18:28 +02:00
$pipeline = $this->source->pipe();
2020-05-13 17:15:21 +02:00
2020-08-23 16:18:28 +02:00
$this->source->fail(new \Exception('Pipeline failed'));
2020-05-13 17:15:21 +02:00
2020-05-21 17:11:22 +02:00
$this->expectException(\Exception::class);
2020-08-23 16:18:28 +02:00
$this->expectExceptionMessage('Pipeline failed');
2020-05-13 17:15:21 +02:00
2020-09-28 05:19:52 +02:00
$pipeline->continue();
2020-05-13 17:15:21 +02:00
}
2020-09-28 05:19:52 +02:00
public function testCompleteAfterContinue(): void
2020-05-13 17:15:21 +02:00
{
2020-08-23 16:18:28 +02:00
$pipeline = $this->source->pipe();
2020-05-13 17:15:21 +02:00
2020-09-28 05:19:52 +02:00
$promise = async(fn () => $pipeline->continue());
2020-05-21 17:11:22 +02:00
$this->assertInstanceOf(Promise::class, $promise);
2020-05-13 17:15:21 +02:00
2020-05-21 17:11:22 +02:00
$this->source->complete();
2020-05-13 17:15:21 +02:00
2020-09-28 05:19:52 +02:00
$this->assertNull(await($promise));
2020-05-13 17:15:21 +02:00
}
2020-09-28 05:19:52 +02:00
public function testDestroyingPipelineRelievesBackPressure(): void
2020-05-13 17:15:21 +02:00
{
2020-08-23 16:18:28 +02:00
$pipeline = $this->source->pipe();
2020-05-13 17:15:21 +02:00
$invoked = 0;
$onResolved = function () use (&$invoked) {
$invoked++;
};
foreach (\range(1, 5) as $value) {
2020-05-28 19:59:55 +02:00
$promise = $this->source->emit($value);
2020-05-13 17:15:21 +02:00
$promise->onResolve($onResolved);
}
$this->assertSame(0, $invoked);
2020-08-23 16:18:28 +02:00
unset($pipeline); // Should relieve all back-pressure.
2020-05-13 17:15:21 +02:00
2020-11-17 00:20:13 +01:00
delay(5); // Tick event loop to invoke promise callbacks.
2020-09-28 05:19:52 +02:00
2020-05-13 17:15:21 +02:00
$this->assertSame(5, $invoked);
$this->source->complete(); // Should not throw.
$this->expectException(\Error::class);
2020-08-23 16:18:28 +02:00
$this->expectExceptionMessage('Pipeline has already been completed');
2020-05-13 17:15:21 +02:00
$this->source->complete(); // Should throw.
}
2020-09-28 05:19:52 +02:00
public function testOnDisposal(): void
{
$invoked = false;
$this->source->onDisposal(function () use (&$invoked) {
$invoked = true;
});
$this->assertFalse($invoked);
2020-08-23 16:18:28 +02:00
$pipeline = $this->source->pipe();
$pipeline->dispose();
delay(0);
$this->assertTrue($invoked);
$this->source->onDisposal($this->createCallback(1));
}
2020-09-28 05:19:52 +02:00
public function testOnDisposalAfterCompletion(): void
{
$invoked = false;
$this->source->onDisposal(function () use (&$invoked) {
$invoked = true;
});
$this->assertFalse($invoked);
$this->source->complete();
2020-08-23 16:18:28 +02:00
$pipeline = $this->source->pipe();
$pipeline->dispose();
$this->assertFalse($invoked);
$this->source->onDisposal($this->createCallback(0));
delay(0);
}
2020-09-28 05:19:52 +02:00
public function testEmitAfterDisposal(): void
2020-05-13 17:15:21 +02:00
{
2020-08-23 16:18:28 +02:00
$pipeline = $this->source->pipe();
2020-07-29 17:29:57 +02:00
$this->source->onDisposal($this->createCallback(1));
2020-08-23 16:18:28 +02:00
$pipeline->dispose();
2020-07-29 17:29:57 +02:00
$this->source->onDisposal($this->createCallback(1));
2020-07-17 18:22:13 +02:00
$this->assertTrue($this->source->isDisposed());
2020-11-17 00:20:13 +01:00
$this->expectException(DisposedException::class);
2020-09-28 05:19:52 +02:00
await($this->source->emit(1));
}
2020-09-28 05:19:52 +02:00
public function testEmitAfterAutomaticDisposal(): void
{
2020-11-17 00:20:13 +01:00
$pipeline = $this->source->pipe();
$this->source->onDisposal($this->createCallback(1));
unset($pipeline); // Trigger automatic disposal.
$this->source->onDisposal($this->createCallback(1));
$this->assertTrue($this->source->isDisposed());
$this->expectException(DisposedException::class);
2020-11-17 00:20:13 +01:00
await($this->source->emit(1));
}
public function testEmitAfterAutomaticDisposalAfterDelay(): void
{
$pipeline = $this->source->pipe();
$this->source->onDisposal($this->createCallback(1));
unset($pipeline); // Trigger automatic disposal.
$this->source->onDisposal($this->createCallback(1));
$this->assertTrue($this->source->isDisposed());
2020-11-17 00:20:13 +01:00
delay(10);
$this->expectException(DisposedException::class);
2020-09-28 05:19:52 +02:00
await($this->source->emit(1));
}
2020-09-28 05:19:52 +02:00
public function testEmitAfterAutomaticDisposalWithPendingContinuePromise(): void
{
$pipeline = $this->source->pipe();
2020-09-28 05:19:52 +02:00
$promise = async(fn () => $pipeline->continue());
$this->source->onDisposal($this->createCallback(1));
unset($pipeline); // Trigger automatic disposal.
$this->source->onDisposal($this->createCallback(1));
$this->assertFalse($this->source->isDisposed());
2020-09-28 05:19:52 +02:00
$this->source->emit(1);
$this->assertSame(1, await($promise));
$this->assertTrue($this->source->isDisposed());
$this->expectException(DisposedException::class);
2020-09-28 05:19:52 +02:00
await($this->source->emit(2));
}
2020-09-28 05:19:52 +02:00
public function testEmitAfterExplicitDisposalWithPendingContinuePromise(): void
{
$pipeline = $this->source->pipe();
2020-09-28 05:19:52 +02:00
$promise = async(fn () => $pipeline->continue());
$this->source->onDisposal($this->createCallback(1));
$pipeline->dispose();
$this->source->onDisposal($this->createCallback(1));
$this->assertTrue($this->source->isDisposed());
$this->expectException(DisposedException::class);
2020-09-28 05:19:52 +02:00
$this->assertSame(1, await($promise));
}
2020-09-28 05:19:52 +02:00
public function testEmitAfterDestruct(): void
{
$this->expectException(DisposedException::class);
2020-08-23 16:18:28 +02:00
$pipeline = $this->source->pipe();
2020-05-28 19:59:55 +02:00
$promise = $this->source->emit(1);
2020-07-29 17:29:57 +02:00
$this->source->onDisposal($this->createCallback(1));
2020-08-23 16:18:28 +02:00
unset($pipeline);
2020-07-29 17:29:57 +02:00
$this->source->onDisposal($this->createCallback(1));
2020-07-17 18:22:13 +02:00
$this->assertTrue($this->source->isDisposed());
2020-09-28 05:19:52 +02:00
$this->assertNull(await($promise));
await($this->source->emit(1));
2020-05-13 17:15:21 +02:00
}
2020-09-28 05:19:52 +02:00
public function testFailWithDisposedException(): void
{
// Using DisposedException, but should be treated as fail, not disposal.
$this->source->fail(new DisposedException);
$this->expectException(\Error::class);
$this->expectExceptionMessage('Pipeline has already been completed');
$this->source->complete();
}
public function testTraversable(): void
{
defer(function (): void {
try {
$this->source->yield(1);
$this->source->yield(2);
$this->source->yield(3);
$this->source->complete();
} catch (\Throwable $exception) {
$this->source->fail($exception);
}
});
$values = [];
foreach ($this->source->pipe() as $value) {
$values[] = $value;
}
$this->assertSame([1, 2, 3], $values);
}
2020-05-13 17:15:21 +02:00
}