2021-08-29 18:47:21 +02:00
|
|
|
<?php
|
|
|
|
|
2021-09-06 06:47:06 +02:00
|
|
|
namespace Amp\Future;
|
2021-08-29 18:47:21 +02:00
|
|
|
|
2021-08-30 06:28:25 +02:00
|
|
|
use Amp\CancelledException;
|
2021-12-02 22:24:56 +01:00
|
|
|
use Amp\DeferredCancellation;
|
2021-12-02 22:29:45 +01:00
|
|
|
use Amp\DeferredFuture;
|
2021-08-29 18:47:21 +02:00
|
|
|
use Amp\Future;
|
2021-09-19 18:54:02 +02:00
|
|
|
use Amp\PHPUnit\AsyncTestCase;
|
|
|
|
use Amp\PHPUnit\TestException;
|
2021-12-05 17:33:49 +01:00
|
|
|
use Amp\PHPUnit\UnhandledException;
|
2021-12-02 22:24:56 +01:00
|
|
|
use Amp\TimeoutCancellation;
|
2021-10-15 00:50:40 +02:00
|
|
|
use Revolt\EventLoop;
|
2021-12-02 20:44:10 +01:00
|
|
|
use function Amp\async;
|
2021-09-19 06:04:20 +02:00
|
|
|
use function Amp\delay;
|
2021-08-29 18:47:21 +02:00
|
|
|
|
2021-09-19 18:54:02 +02:00
|
|
|
class FutureTest extends AsyncTestCase
|
2021-08-29 18:47:21 +02:00
|
|
|
{
|
|
|
|
public function testIterate(): void
|
|
|
|
{
|
|
|
|
$this->expectOutputString('a=1 b=0 c=2 ');
|
|
|
|
|
|
|
|
$a = $this->delay(0.1, 'a');
|
|
|
|
$b = $this->delay(0.2, 'b');
|
|
|
|
$c = $this->delay(0.3, 'c');
|
|
|
|
|
|
|
|
foreach (Future::iterate([$b, $a, $c]) as $index => $future) {
|
2021-09-19 06:05:16 +02:00
|
|
|
print $future->await() . '=' . $index . ' ';
|
2021-08-29 18:47:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-02 23:10:12 +01:00
|
|
|
public function testIterateCancelPending(): void
|
|
|
|
{
|
|
|
|
$this->expectOutputString('');
|
|
|
|
|
|
|
|
$a = $this->delay(0.1, 'a');
|
|
|
|
|
|
|
|
$this->expectException(CancelledException::class);
|
|
|
|
|
|
|
|
foreach (Future::iterate([$a], new TimeoutCancellation(0.01)) as $index => $future) {
|
|
|
|
print $future->await() . '=' . $index . ' ';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testIterateCancelNonPending(): void
|
|
|
|
{
|
|
|
|
$this->expectOutputString('a=0 ');
|
|
|
|
|
|
|
|
$a = $this->delay(0.1, 'a');
|
|
|
|
$b = $this->delay(0.2, 'b');
|
|
|
|
|
|
|
|
$this->expectException(CancelledException::class);
|
|
|
|
|
|
|
|
$deferredCancellation = new DeferredCancellation;
|
|
|
|
foreach (Future::iterate([$a, $b], $deferredCancellation->getCancellation()) as $index => $future) {
|
|
|
|
$deferredCancellation->cancel();
|
|
|
|
print $future->await() . '=' . $index . ' ';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-29 18:47:21 +02:00
|
|
|
public function testIterateGenerator(): void
|
|
|
|
{
|
|
|
|
$this->expectOutputString('a=1 ');
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var \Generator<int, Future<string>, void, void>
|
|
|
|
*/
|
|
|
|
$iterator = (function () {
|
2021-12-02 22:29:45 +01:00
|
|
|
yield (new DeferredFuture)->getFuture();
|
2021-08-29 18:47:21 +02:00
|
|
|
yield $this->delay(0.1, 'a');
|
|
|
|
|
|
|
|
// Never joins
|
2021-12-02 22:29:45 +01:00
|
|
|
(new DeferredFuture)->getFuture()->await();
|
2021-08-29 18:47:21 +02:00
|
|
|
})();
|
|
|
|
|
|
|
|
foreach (Future::iterate($iterator) as $index => $future) {
|
2021-09-19 06:05:16 +02:00
|
|
|
print $future->await() . '=' . $index . ' ';
|
2021-08-29 18:47:21 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testComplete(): void
|
|
|
|
{
|
2021-12-02 22:29:45 +01:00
|
|
|
$deferred = new DeferredFuture;
|
2021-08-29 18:47:21 +02:00
|
|
|
$future = $deferred->getFuture();
|
|
|
|
|
|
|
|
$deferred->complete('result');
|
|
|
|
|
2021-09-19 06:05:16 +02:00
|
|
|
self::assertSame('result', $future->await());
|
2021-08-29 18:47:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function testCompleteAsync(): void
|
|
|
|
{
|
2021-12-02 22:29:45 +01:00
|
|
|
$deferred = new DeferredFuture;
|
2021-08-29 18:47:21 +02:00
|
|
|
$future = $deferred->getFuture();
|
|
|
|
|
2021-11-14 18:35:07 +01:00
|
|
|
EventLoop::delay(0.01, fn () => $deferred->complete('result'));
|
2021-08-29 18:47:21 +02:00
|
|
|
|
2021-09-19 06:05:16 +02:00
|
|
|
self::assertSame('result', $future->await());
|
2021-08-29 18:47:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function testCompleteImmediate(): void
|
|
|
|
{
|
|
|
|
$future = Future::complete('result');
|
|
|
|
|
2021-09-19 06:05:16 +02:00
|
|
|
self::assertSame('result', $future->await());
|
2021-08-29 18:47:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function testError(): void
|
|
|
|
{
|
2021-12-02 22:29:45 +01:00
|
|
|
$deferred = new DeferredFuture;
|
2021-08-29 18:47:21 +02:00
|
|
|
$future = $deferred->getFuture();
|
|
|
|
|
|
|
|
$deferred->error(new \Exception('foo'));
|
|
|
|
|
|
|
|
$this->expectException(\Exception::class);
|
|
|
|
$this->expectExceptionMessage('foo');
|
|
|
|
|
2021-09-19 06:05:16 +02:00
|
|
|
$future->await();
|
2021-08-29 18:47:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function testErrorAsync(): void
|
|
|
|
{
|
2021-12-02 22:29:45 +01:00
|
|
|
$deferred = new DeferredFuture;
|
2021-08-29 18:47:21 +02:00
|
|
|
$future = $deferred->getFuture();
|
|
|
|
|
2021-11-14 18:35:07 +01:00
|
|
|
EventLoop::delay(0.01, fn () => $deferred->error(new \Exception('foo')));
|
2021-08-29 18:47:21 +02:00
|
|
|
|
|
|
|
$this->expectException(\Exception::class);
|
|
|
|
$this->expectExceptionMessage('foo');
|
|
|
|
|
2021-09-19 06:05:16 +02:00
|
|
|
$future->await();
|
2021-08-29 18:47:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function testErrorImmediate(): void
|
|
|
|
{
|
|
|
|
$future = Future::error(new \Exception('foo'));
|
|
|
|
|
|
|
|
$this->expectException(\Exception::class);
|
|
|
|
$this->expectExceptionMessage('foo');
|
|
|
|
|
2021-09-19 06:05:16 +02:00
|
|
|
$future->await();
|
2021-08-29 18:47:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function testCompleteWithFuture(): void
|
|
|
|
{
|
2021-12-02 22:29:45 +01:00
|
|
|
$deferred = new DeferredFuture;
|
2021-08-29 18:47:21 +02:00
|
|
|
|
|
|
|
$this->expectException(\Error::class);
|
|
|
|
$this->expectExceptionMessage('Cannot complete with an instance of');
|
|
|
|
|
2021-11-07 10:49:33 +01:00
|
|
|
$deferred->complete(Future::complete());
|
2021-08-29 18:47:21 +02:00
|
|
|
}
|
|
|
|
|
2021-08-30 06:28:25 +02:00
|
|
|
public function testCancellation(): void
|
|
|
|
{
|
|
|
|
$future = $this->delay(0.02, true);
|
|
|
|
|
2021-12-03 01:02:05 +01:00
|
|
|
$cancellation = new TimeoutCancellation(0.01);
|
2021-08-30 06:28:25 +02:00
|
|
|
|
|
|
|
$this->expectException(CancelledException::class);
|
|
|
|
|
2021-12-03 01:02:05 +01:00
|
|
|
$future->await($cancellation);
|
2021-08-30 06:28:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function testCompleteBeforeCancellation(): void
|
|
|
|
{
|
|
|
|
$future = $this->delay(0.01, true);
|
|
|
|
|
2021-12-03 01:02:05 +01:00
|
|
|
$cancellation = new TimeoutCancellation(0.02);
|
2021-08-30 06:28:25 +02:00
|
|
|
|
2021-12-03 01:02:05 +01:00
|
|
|
self::assertTrue($future->await($cancellation));
|
2021-08-30 06:28:25 +02:00
|
|
|
}
|
|
|
|
|
2021-09-17 03:54:19 +02:00
|
|
|
public function testCompleteThenCancelJoin(): void
|
|
|
|
{
|
2021-12-02 22:29:45 +01:00
|
|
|
$deferred = new DeferredFuture;
|
2021-12-02 22:24:56 +01:00
|
|
|
$source = new DeferredCancellation;
|
2021-09-17 03:54:19 +02:00
|
|
|
$future = $deferred->getFuture();
|
|
|
|
|
2021-10-17 20:24:55 +02:00
|
|
|
EventLoop::queue(function () use ($future, $source): void {
|
2021-12-02 22:24:56 +01:00
|
|
|
self::assertSame(1, $future->await($source->getCancellation()));
|
2021-09-17 03:54:19 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
$deferred->complete(1);
|
|
|
|
$source->cancel();
|
2021-09-19 18:54:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function testUnhandledError(): void
|
|
|
|
{
|
2021-12-02 22:29:45 +01:00
|
|
|
$deferred = new DeferredFuture;
|
2021-09-19 18:54:02 +02:00
|
|
|
$deferred->error(new TestException);
|
|
|
|
unset($deferred);
|
|
|
|
|
2021-12-05 17:33:49 +01:00
|
|
|
$this->expectException(UnhandledException::class);
|
2021-09-19 18:54:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function testUnhandledErrorFromFutureError(): void
|
|
|
|
{
|
|
|
|
$future = Future::error(new TestException);
|
|
|
|
unset($future);
|
|
|
|
|
2021-12-05 17:33:49 +01:00
|
|
|
$this->expectException(UnhandledException::class);
|
2021-09-19 18:54:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function testIgnoringUnhandledErrors(): void
|
|
|
|
{
|
2021-12-02 22:29:45 +01:00
|
|
|
$deferred = new DeferredFuture;
|
2021-09-19 18:54:02 +02:00
|
|
|
$deferred->getFuture()->ignore();
|
|
|
|
$deferred->error(new TestException);
|
|
|
|
unset($deferred);
|
|
|
|
|
2021-12-03 00:47:30 +01:00
|
|
|
EventLoop::setErrorHandler($this->createCallback(0));
|
2021-09-19 18:54:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function testIgnoreUnhandledErrorFromFutureError(): void
|
|
|
|
{
|
|
|
|
$future = Future::error(new TestException);
|
|
|
|
$future->ignore();
|
|
|
|
unset($future);
|
2021-09-17 03:54:19 +02:00
|
|
|
|
2021-12-03 00:47:30 +01:00
|
|
|
EventLoop::setErrorHandler($this->createCallback(0));
|
2021-09-17 03:54:19 +02:00
|
|
|
}
|
2021-08-30 06:28:25 +02:00
|
|
|
|
2021-11-30 01:36:07 +01:00
|
|
|
public function testMapWithCompleteFuture(): void
|
|
|
|
{
|
|
|
|
$future = Future::complete(1);
|
|
|
|
$future = $future->map(static fn (int $value) => $value + 1);
|
|
|
|
self::assertSame(2, $future->await());
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testMapWithSuspendInCallback(): void
|
|
|
|
{
|
|
|
|
$future = Future::complete(1);
|
|
|
|
$future = $future->map(static fn (int $value) => $value + Future::complete(1)->await());
|
|
|
|
self::assertSame(2, $future->await());
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testMapWithPendingFuture(): void
|
|
|
|
{
|
2021-12-02 22:29:45 +01:00
|
|
|
$deferred = new DeferredFuture;
|
2021-11-30 01:36:07 +01:00
|
|
|
|
|
|
|
$future = $deferred->getFuture();
|
|
|
|
$future = $future->map(static fn (int $value) => $value + 1);
|
|
|
|
|
|
|
|
EventLoop::delay(0.1, static fn () => $deferred->complete(1));
|
|
|
|
|
|
|
|
self::assertSame(2, $future->await());
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testMapWithErrorFuture(): void
|
|
|
|
{
|
|
|
|
$future = Future::error($exception = new TestException());
|
|
|
|
$future = $future->map($this->createCallback(0));
|
|
|
|
$this->expectExceptionObject($exception);
|
|
|
|
$future->await();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testMapWithThrowingCallback(): void
|
|
|
|
{
|
|
|
|
$exception = new TestException();
|
|
|
|
$future = Future::complete(1);
|
|
|
|
$future = $future->map(static fn () => throw $exception);
|
|
|
|
$this->expectExceptionObject($exception);
|
|
|
|
$future->await();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testCatchWithCompleteFuture(): void
|
|
|
|
{
|
|
|
|
$future = Future::complete(1);
|
|
|
|
$future = $future->catch($this->createCallback(0));
|
|
|
|
self::assertSame(1, $future->await());
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testCatchWithSuspendInCallback(): void
|
|
|
|
{
|
|
|
|
$future = Future::error(new TestException());
|
|
|
|
$future = $future->catch(static fn (\Throwable $exception) => Future::complete(1)->await());
|
|
|
|
self::assertSame(1, $future->await());
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testCatchWithPendingFuture(): void
|
|
|
|
{
|
2021-12-02 22:29:45 +01:00
|
|
|
$deferred = new DeferredFuture;
|
2021-11-30 01:36:07 +01:00
|
|
|
|
|
|
|
$future = $deferred->getFuture();
|
|
|
|
$future = $future->catch(static fn (\Throwable $exception) => 1);
|
|
|
|
|
|
|
|
EventLoop::delay(0.1, static fn () => $deferred->error(new TestException));
|
|
|
|
|
|
|
|
self::assertSame(1, $future->await());
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testCatchWithThrowingCallback(): void
|
|
|
|
{
|
|
|
|
$exception = new TestException();
|
|
|
|
$future = Future::error(new \Error());
|
|
|
|
$future = $future->catch(static fn () => throw $exception);
|
|
|
|
$this->expectExceptionObject($exception);
|
|
|
|
$future->await();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testFinallyWithCompleteFuture(): void
|
|
|
|
{
|
|
|
|
$future = Future::complete(1);
|
|
|
|
$future = $future->finally($this->createCallback(1));
|
|
|
|
self::assertSame(1, $future->await());
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testFinallyWithErrorFuture(): void
|
|
|
|
{
|
|
|
|
$exception = new TestException();
|
|
|
|
$future = Future::error($exception);
|
|
|
|
$future = $future->finally($this->createCallback(1));
|
|
|
|
$this->expectExceptionObject($exception);
|
|
|
|
$future->await();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testFinallyWithSuspendInCallback(): void
|
|
|
|
{
|
|
|
|
$future = Future::complete(1);
|
|
|
|
$future = $future->finally(static fn () => Future::complete()->await());
|
|
|
|
self::assertSame(1, $future->await());
|
|
|
|
}
|
2021-12-02 18:40:51 +01:00
|
|
|
|
2021-11-30 01:36:07 +01:00
|
|
|
public function testFinallyWithPendingFuture(): void
|
|
|
|
{
|
2021-12-02 22:29:45 +01:00
|
|
|
$deferred = new DeferredFuture;
|
2021-11-30 01:36:07 +01:00
|
|
|
|
|
|
|
$future = $deferred->getFuture();
|
|
|
|
$future = $future->finally($this->createCallback(1));
|
|
|
|
|
|
|
|
EventLoop::delay(0.1, static fn () => $deferred->complete(1));
|
|
|
|
|
|
|
|
self::assertSame(1, $future->await());
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testFinallyWithThrowingCallback(): void
|
|
|
|
{
|
|
|
|
$exception = new TestException();
|
|
|
|
$future = Future::complete(1);
|
|
|
|
$future = $future->finally(static fn () => throw $exception);
|
|
|
|
$this->expectExceptionObject($exception);
|
|
|
|
$future->await();
|
|
|
|
}
|
|
|
|
|
2021-08-29 18:47:21 +02:00
|
|
|
/**
|
|
|
|
* @template T
|
|
|
|
*
|
|
|
|
* @param float $seconds
|
2021-12-02 22:24:56 +01:00
|
|
|
* @param T $value
|
2021-08-29 18:47:21 +02:00
|
|
|
*
|
|
|
|
* @return Future<T>
|
|
|
|
*/
|
|
|
|
private function delay(float $seconds, mixed $value): Future
|
|
|
|
{
|
2021-12-02 20:44:10 +01:00
|
|
|
return async(
|
2021-12-02 18:40:51 +01:00
|
|
|
/**
|
|
|
|
* @return T
|
|
|
|
*/
|
2021-09-19 06:04:20 +02:00
|
|
|
static function () use ($seconds, $value): mixed {
|
2021-08-29 18:47:21 +02:00
|
|
|
delay($seconds);
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|