1
0
mirror of https://github.com/danog/amp.git synced 2024-12-11 17:09:40 +01:00
amp/test/PromiseTest.php

400 lines
11 KiB
PHP
Raw Normal View History

<?php
namespace Amp\Test;
2020-09-28 05:19:52 +02:00
use Amp\Deferred;
2017-03-11 06:40:30 +01:00
use Amp\Loop;
2020-09-28 05:19:52 +02:00
use Amp\PHPUnit\AsyncTestCase;
use Amp\Promise;
use function Amp\delay;
2017-03-11 06:40:30 +01:00
2020-09-28 05:19:52 +02:00
class PromiseTest extends AsyncTestCase
2018-06-18 20:00:01 +02:00
{
2016-10-24 17:08:08 +02:00
/**
2017-03-12 17:05:52 +01:00
* A Promise to use for a test with resolution methods.
2018-06-18 20:00:01 +02:00
* Note that the callables shall take care of the Promise being resolved in any case. Example: The actual
* implementation delays resolution to the next loop tick. The callables then must run one tick of the loop in
* order to ensure resolution.
2016-10-24 17:08:08 +02:00
*
2018-06-18 20:00:01 +02:00
* @return array(Promise, callable, callable) where the last two callables are resolving the Promise with a result
* or a Throwable/Exception respectively
2016-10-24 17:08:08 +02:00
*/
2020-09-28 05:19:52 +02:00
public function promise(): array
2018-06-18 20:00:01 +02:00
{
2020-09-28 05:19:52 +02:00
$deferred = new Deferred;
return [
2020-09-28 05:19:52 +02:00
$deferred->promise(),
[$deferred, 'resolve'],
[$deferred, 'fail'],
];
}
2020-09-28 05:19:52 +02:00
public function provideSuccessValues(): array
2018-06-18 20:00:01 +02:00
{
2016-10-24 17:08:08 +02:00
return [
["string"],
[0],
2016-12-30 21:29:21 +01:00
[~PHP_INT_MAX],
2016-10-24 17:08:08 +02:00
[-1.0],
[true],
[false],
[[]],
[null],
[new \StdClass],
2017-01-07 11:50:21 +01:00
[new \Exception],
2016-10-24 17:08:08 +02:00
];
}
2020-09-28 05:19:52 +02:00
public function testPromiseImplementsPromise(): void
2018-06-18 20:00:01 +02:00
{
list($promise) = $this->promise();
$this->assertInstanceOf(Promise::class, $promise);
}
2016-10-24 17:08:08 +02:00
/** @dataProvider provideSuccessValues */
2020-09-28 05:19:52 +02:00
public function testPromiseSucceed(mixed $value): void
2018-06-18 20:00:01 +02:00
{
2016-12-23 13:14:11 +01:00
list($promise, $succeeder) = $this->promise();
$promise->onResolve(function ($e, $v) use (&$invoked, $value) {
$this->assertNull($e);
2016-10-24 17:08:08 +02:00
$this->assertSame($value, $v);
$invoked = true;
});
2020-09-28 05:19:52 +02:00
2016-10-24 17:08:08 +02:00
$succeeder($value);
2020-09-28 05:19:52 +02:00
delay(0); // Tick event loop to invoke onResolve callback.
2020-09-28 05:19:52 +02:00
2016-10-24 17:08:08 +02:00
$this->assertTrue($invoked);
}
/** @dataProvider provideSuccessValues */
2020-09-28 05:19:52 +02:00
public function testOnResolveOnSucceededPromise(mixed $value): void
2018-06-18 20:00:01 +02:00
{
2016-12-23 13:14:11 +01:00
list($promise, $succeeder) = $this->promise();
2016-10-24 17:08:08 +02:00
$succeeder($value);
$promise->onResolve(function ($e, $v) use (&$invoked, $value) {
$this->assertNull($e);
2016-10-24 17:08:08 +02:00
$this->assertSame($value, $v);
$invoked = true;
});
2020-09-28 05:19:52 +02:00
delay(0); // Tick event loop to invoke onResolve callback.
2020-09-28 05:19:52 +02:00
2016-10-24 17:08:08 +02:00
$this->assertTrue($invoked);
}
2017-01-07 11:47:58 +01:00
2020-09-28 05:19:52 +02:00
public function testSuccessAllOnResolvesExecuted(): void
2018-06-18 20:00:01 +02:00
{
2016-12-23 13:14:11 +01:00
list($promise, $succeeder) = $this->promise();
2016-10-24 17:08:08 +02:00
$invoked = 0;
2017-01-07 11:47:58 +01:00
$promise->onResolve(function ($e, $v) use (&$invoked) {
$this->assertNull($e);
$this->assertTrue($v);
2016-10-24 17:08:08 +02:00
$invoked++;
});
$promise->onResolve(function ($e, $v) use (&$invoked) {
$this->assertNull($e);
$this->assertTrue($v);
2016-10-24 17:08:08 +02:00
$invoked++;
});
$succeeder(true);
$promise->onResolve(function ($e, $v) use (&$invoked) {
$this->assertNull($e);
$this->assertTrue($v);
2016-10-24 17:08:08 +02:00
$invoked++;
});
$promise->onResolve(function ($e, $v) use (&$invoked) {
$this->assertNull($e);
$this->assertTrue($v);
2016-10-24 17:08:08 +02:00
$invoked++;
});
2017-01-07 11:47:58 +01:00
delay(0); // Tick event loop to invoke onResolve callback.
2020-09-28 05:19:52 +02:00
2016-10-24 17:08:08 +02:00
$this->assertSame(4, $invoked);
}
2020-09-28 05:19:52 +02:00
public function testPromiseExceptionFailure(): void
2018-06-18 20:00:01 +02:00
{
2016-12-23 13:14:11 +01:00
list($promise, , $failer) = $this->promise();
$promise->onResolve(function ($e) use (&$invoked) {
$this->assertSame(\get_class($e), "RuntimeException");
2016-10-24 17:08:08 +02:00
$invoked = true;
});
$failer(new \RuntimeException);
2020-09-28 05:19:52 +02:00
delay(0); // Tick event loop to invoke onResolve callback.
2020-09-28 05:19:52 +02:00
2016-10-24 17:08:08 +02:00
$this->assertTrue($invoked);
}
2020-09-28 05:19:52 +02:00
public function testOnResolveOnExceptionFailedPromise(): void
2018-06-18 20:00:01 +02:00
{
2016-12-23 13:14:11 +01:00
list($promise, , $failer) = $this->promise();
2016-10-24 17:08:08 +02:00
$failer(new \RuntimeException);
$promise->onResolve(function ($e) use (&$invoked) {
$this->assertSame(\get_class($e), "RuntimeException");
2016-10-24 17:08:08 +02:00
$invoked = true;
});
2020-09-28 05:19:52 +02:00
delay(0); // Tick event loop to invoke onResolve callback.
2020-09-28 05:19:52 +02:00
2016-10-24 17:08:08 +02:00
$this->assertTrue($invoked);
}
2017-01-07 11:47:58 +01:00
2020-09-28 05:19:52 +02:00
public function testFailureAllOnResolvesExecuted(): void
2018-06-18 20:00:01 +02:00
{
2016-12-23 13:14:11 +01:00
list($promise, , $failer) = $this->promise();
2016-10-24 17:08:08 +02:00
$invoked = 0;
$promise->onResolve(function ($e) use (&$invoked) {
$this->assertSame(\get_class($e), "RuntimeException");
2016-10-24 17:08:08 +02:00
$invoked++;
});
$promise->onResolve(function ($e) use (&$invoked) {
$this->assertSame(\get_class($e), "RuntimeException");
2016-10-24 17:08:08 +02:00
$invoked++;
});
$failer(new \RuntimeException);
$promise->onResolve(function ($e) use (&$invoked) {
$this->assertSame(\get_class($e), "RuntimeException");
2016-10-24 17:08:08 +02:00
$invoked++;
});
$promise->onResolve(function ($e) use (&$invoked) {
$this->assertSame(\get_class($e), "RuntimeException");
2016-10-24 17:08:08 +02:00
$invoked++;
});
delay(0); // Tick event loop to invoke onResolve callback.
2020-09-28 05:19:52 +02:00
2016-10-24 17:08:08 +02:00
$this->assertSame(4, $invoked);
}
2020-09-28 05:19:52 +02:00
public function testPromiseErrorFailure(): void
2018-06-18 20:00:01 +02:00
{
2016-10-24 17:08:08 +02:00
if (PHP_VERSION_ID < 70000) {
$this->markTestSkipped("Error only exists on PHP 7+");
}
2016-12-23 13:14:11 +01:00
list($promise, , $failer) = $this->promise();
$promise->onResolve(function ($e) use (&$invoked) {
$this->assertSame(\get_class($e), "Error");
2016-10-24 17:08:08 +02:00
$invoked = true;
});
$failer(new \Error);
2020-09-28 05:19:52 +02:00
delay(0); // Tick event loop to invoke onResolve callback.
2020-09-28 05:19:52 +02:00
2016-10-24 17:08:08 +02:00
$this->assertTrue($invoked);
}
2020-09-28 05:19:52 +02:00
public function testOnResolveOnErrorFailedPromise(): void
2018-06-18 20:00:01 +02:00
{
2016-10-24 17:08:08 +02:00
if (PHP_VERSION_ID < 70000) {
$this->markTestSkipped("Error only exists on PHP 7+");
}
2016-12-23 13:14:11 +01:00
list($promise, , $failer) = $this->promise();
2016-10-24 17:08:08 +02:00
$failer(new \Error);
$promise->onResolve(function ($e) use (&$invoked) {
$this->assertSame(\get_class($e), "Error");
2016-10-24 17:08:08 +02:00
$invoked = true;
});
2020-09-28 05:19:52 +02:00
delay(0); // Tick event loop to invoke onResolve callback.
2020-09-28 05:19:52 +02:00
2016-10-24 17:08:08 +02:00
$this->assertTrue($invoked);
}
2017-03-12 17:05:52 +01:00
/** Implementations MAY fail upon resolution with a Promise, but they definitely MUST NOT return a Promise */
2020-09-28 05:19:52 +02:00
public function testPromiseResolutionWithPromise(): void
2018-06-18 20:00:01 +02:00
{
2016-12-23 13:14:11 +01:00
list($success, $succeeder) = $this->promise();
2016-10-24 17:08:08 +02:00
$succeeder(true);
2016-12-23 13:14:11 +01:00
list($promise, $succeeder) = $this->promise();
2016-10-24 17:08:08 +02:00
$ex = false;
try {
$succeeder($success);
} catch (\Throwable $e) {
$ex = true;
} catch (\Exception $e) {
$ex = true;
}
if (!$ex) {
$promise->onResolve(function ($e, $v) use (&$invoked) {
2016-10-24 17:08:08 +02:00
$invoked = true;
2018-11-12 21:04:12 +01:00
$this->assertNotInstanceOf(Promise::class, $v);
2016-10-24 17:08:08 +02:00
});
2020-09-28 05:19:52 +02:00
delay(0); // Tick event loop to invoke onResolve callback.
2020-09-28 05:19:52 +02:00
2016-10-24 17:08:08 +02:00
$this->assertTrue($invoked);
}
}
2020-09-28 05:19:52 +02:00
public function testThrowingInCallback(): void
2018-06-18 20:00:01 +02:00
{
2020-09-28 05:19:52 +02:00
$invoked = 0;
2020-09-28 05:19:52 +02:00
Loop::setErrorHandler(function () use (&$invoked) {
$invoked++;
});
2016-10-24 17:08:08 +02:00
2020-09-28 05:19:52 +02:00
list($promise, $succeeder) = $this->promise();
$succeeder(true);
$promise->onResolve(function ($e, $v) use (&$invoked, $promise) {
$this->assertNull($e);
$this->assertTrue($v);
$invoked++;
2016-12-23 13:14:11 +01:00
2020-09-28 05:19:52 +02:00
throw new \Exception;
});
2016-12-23 13:14:11 +01:00
2020-09-28 05:19:52 +02:00
list($promise, $succeeder) = $this->promise();
$promise->onResolve(function ($e, $v) use (&$invoked, $promise) {
$this->assertNull($e);
$this->assertTrue($v);
$invoked++;
2016-12-23 13:14:11 +01:00
2020-09-28 05:19:52 +02:00
throw new \Exception;
2017-03-11 06:40:30 +01:00
});
2020-09-28 05:19:52 +02:00
$succeeder(true);
delay(0); // Tick event loop to invoke onResolve callback.
2020-09-28 05:19:52 +02:00
$this->assertSame(4, $invoked);
}
2020-09-28 05:19:52 +02:00
public function testThrowingInCallbackContinuesOtherOnResolves(): void
2018-06-18 20:00:01 +02:00
{
2020-09-28 05:19:52 +02:00
$invoked = 0;
2020-09-28 05:19:52 +02:00
Loop::setErrorHandler(function () use (&$invoked) {
$invoked++;
});
2020-09-28 05:19:52 +02:00
list($promise, $succeeder) = $this->promise();
$promise->onResolve(function ($e, $v) use (&$invoked, $promise) {
$this->assertNull($e);
$this->assertTrue($v);
$invoked++;
2020-09-28 05:19:52 +02:00
throw new \Exception;
2017-03-11 06:40:30 +01:00
});
2020-09-28 05:19:52 +02:00
$promise->onResolve(function ($e, $v) use (&$invoked, $promise) {
$this->assertNull($e);
$this->assertTrue($v);
$invoked++;
});
$succeeder(true);
delay(5); // Tick event loop a few times to invoke onResolve callback.
2020-09-28 05:19:52 +02:00
$this->assertSame(3, $invoked);
2016-10-24 17:08:08 +02:00
}
2016-12-29 18:57:42 +01:00
2020-09-28 05:19:52 +02:00
public function testThrowingInCallbackOnFailure(): void
2018-06-18 20:00:01 +02:00
{
2020-09-28 05:19:52 +02:00
$invoked = 0;
Loop::setErrorHandler(function () use (&$invoked) {
$invoked++;
});
2020-09-28 05:19:52 +02:00
list($promise, , $failer) = $this->promise();
$exception = new \Exception;
$failer($exception);
$promise->onResolve(function ($e, $v) use (&$invoked, $exception) {
$this->assertSame($exception, $e);
$this->assertNull($v);
$invoked++;
2020-09-28 05:19:52 +02:00
throw $e;
});
2020-09-28 05:19:52 +02:00
list($promise, , $failer) = $this->promise();
$exception = new \Exception;
$promise->onResolve(function ($e, $v) use (&$invoked, $exception) {
$this->assertSame($exception, $e);
$this->assertNull($v);
$invoked++;
2020-09-28 05:19:52 +02:00
throw $e;
2017-03-11 06:40:30 +01:00
});
2020-09-28 05:19:52 +02:00
$failer($exception);
delay(0); // Tick event loop to invoke onResolve callback.
2020-09-28 05:19:52 +02:00
$this->assertSame(4, $invoked);
}
2020-09-28 05:19:52 +02:00
public function testWeakTypes(): void
2018-06-18 20:00:01 +02:00
{
2016-12-29 18:57:42 +01:00
$invoked = 0;
list($promise, $succeeder) = $this->promise();
$expectedData = "15.24";
$promise->onResolve(function ($e, int $v) use (&$invoked, $expectedData) {
2016-12-29 18:57:42 +01:00
$invoked++;
$this->assertSame((int) $expectedData, $v);
});
$succeeder($expectedData);
$promise->onResolve(function ($e, int $v) use (&$invoked, $expectedData) {
2016-12-29 18:57:42 +01:00
$invoked++;
$this->assertSame((int) $expectedData, $v);
});
delay(0); // Tick event loop to invoke onResolve callback.
2020-09-28 05:19:52 +02:00
$this->assertSame(2, $invoked);
2016-12-29 18:57:42 +01:00
}
2020-09-28 05:19:52 +02:00
public function testResolvedQueueUnrolling(): void
2018-06-18 20:00:01 +02:00
{
$count = 50;
$invoked = false;
2020-09-28 05:19:52 +02:00
$deferred = new Deferred;
$promise = $deferred->promise();
2018-06-18 20:00:01 +02:00
$promise->onResolve(function () {
});
$promise->onResolve(function () {
});
$promise->onResolve(function () use (&$invoked) {
$invoked = true;
$this->assertLessThan(30, \count(\debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)));
});
2020-09-28 05:19:52 +02:00
$f = function () use (&$f, &$count, &$deferred) {
$d = new Deferred;
$p = $d->promise();
2018-06-18 20:00:01 +02:00
$p->onResolve(function () {
});
$p->onResolve(function () {
});
2020-09-28 05:19:52 +02:00
$deferred->resolve($p);
$deferred = $d;
if (--$count > 0) {
$f();
}
};
$f();
2020-09-28 05:19:52 +02:00
$deferred->resolve();
delay(0); // Tick event loop to invoke onResolve callback.
$this->assertTrue($invoked);
}
}