Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 205 additions & 3 deletions tests/Executor/Promise/AmpFutureAdapterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,78 @@ static function ($value) use (&$result): void {
self::assertSame(1, $result);
}

public function testThenUnwrapsFutureReturnedByFulfillmentCallback(): void
{
$ampAdapter = new AmpFutureAdapter();
$deferred = new DeferredFuture();
$promise = $ampAdapter->convertThenable(Future::complete(1));

$resultPromise = $ampAdapter->then($promise, static function ($value) use ($deferred): Future {
self::assertSame(1, $value);

return $deferred->getFuture();
});

$deferred->complete(2);

self::assertSame(2, $resultPromise->adoptedPromise->await());
}

public function testThenDoesNotInvokeRejectionCallbackForFulfillmentCallbackException(): void
{
$ampAdapter = new AmpFutureAdapter();
$promise = $ampAdapter->convertThenable(Future::complete());

$resultPromise = $ampAdapter->then(
$promise,
static function ($value): void {
self::assertNull($value);

throw new \RuntimeException('fulfillment failed');
},
static function (\Throwable $reason): void {
self::fail('The rejection callback must not run for a fulfillment callback exception.');
}
);

$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('fulfillment failed');

$resultPromise->adoptedPromise->await();
}

public function testThenRejectsWhenNoRejectionCallbackIsProvided(): void
{
$ampAdapter = new AmpFutureAdapter();
$promise = $ampAdapter->convertThenable(Future::error(new \RuntimeException('failed')));
$resultPromise = $ampAdapter->then($promise);

$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('failed');

$resultPromise->adoptedPromise->await();
}

public function testThenRejectsWhenRejectionCallbackThrows(): void
{
$ampAdapter = new AmpFutureAdapter();
$promise = $ampAdapter->convertThenable(Future::error(new \RuntimeException('failed')));
$resultPromise = $ampAdapter->then(
$promise,
null,
static function (\Throwable $reason): void {
self::assertSame('failed', $reason->getMessage());

throw new \RuntimeException('rejection failed');
}
);

$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('rejection failed');

$resultPromise->adoptedPromise->await();
}

public function testCreate(): void
{
$ampAdapter = new AmpFutureAdapter();
Expand All @@ -96,6 +168,31 @@ public function testCreate(): void
self::assertSame(1, $result);
}

public function testCreateUnwrapsPromise(): void
{
$ampAdapter = new AmpFutureAdapter();
$nestedPromise = $ampAdapter->createFulfilled(1);
$promise = $ampAdapter->create(static function ($resolve) use ($nestedPromise): void {
$resolve($nestedPromise);
});

self::assertSame(1, $promise->adoptedPromise->await());
}

public function testCreateRejectsWhenNestedFutureFails(): void
{
$ampAdapter = new AmpFutureAdapter();
$future = Future::error(new \RuntimeException('failed'));
$promise = $ampAdapter->create(static function ($resolve) use ($future): void {
$resolve($future);
});

$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('failed');

$promise->adoptedPromise->await();
}

public function testCreateFulfilled(): void
{
$ampAdapter = new AmpFutureAdapter();
Expand Down Expand Up @@ -140,6 +237,27 @@ static function ($error) use (&$exception): void {
self::assertSame('I am a bad promise', $exception->getMessage());
}

public function testThenUnwrapsFutureReturnedByRejectionCallback(): void
{
$ampAdapter = new AmpFutureAdapter();
$deferred = new DeferredFuture();
$promise = $ampAdapter->convertThenable(Future::error(new \RuntimeException('failed')));

$resultPromise = $ampAdapter->then(
$promise,
null,
static function (\Throwable $reason) use ($deferred): Future {
self::assertSame('failed', $reason->getMessage());

return $deferred->getFuture();
}
);

$deferred->complete('recovered');

self::assertSame('recovered', $resultPromise->adoptedPromise->await());
}

public function testAll(): void
{
$ampAdapter = new AmpFutureAdapter();
Expand All @@ -154,11 +272,16 @@ public function testAll(): void
self::assertSame([1, 2, 3], $result);
}

public function testAllShouldPreserveTheOrderOfTheArrayWhenResolvingAsyncPromises(): void
public function testAllShouldPreserveKeysWhenResolvingAsyncPromises(): void
{
$ampAdapter = new AmpFutureAdapter();
$deferred = new DeferredFuture();
$promises = [Future::complete(1), 2, $deferred->getFuture(), Future::complete(4)];
$promises = [
'first' => Future::complete(1),
'second' => 2,
'third' => $deferred->getFuture(),
'fourth' => Future::complete(4),
];

$allPromise = $ampAdapter->all($promises);

Expand All @@ -168,6 +291,85 @@ public function testAllShouldPreserveTheOrderOfTheArrayWhenResolvingAsyncPromise
self::assertInstanceOf(Future::class, $allFuture);
$result = $allFuture->await();

self::assertSame([1, 2, 3, 4], $result);
self::assertSame([
'first' => 1,
'second' => 2,
'third' => 3,
'fourth' => 4,
], $result);
}

public function testAllAcceptsPromisesInIterables(): void
{
$ampAdapter = new AmpFutureAdapter();
$promise = $ampAdapter->createFulfilled(1);
$promises = (static function () use ($promise): \Generator {
yield 'promise' => $promise;
yield 'future' => Future::complete(2);
})();

$allPromise = $ampAdapter->all($promises);

self::assertSame(['promise' => 1, 'future' => 2], $allPromise->adoptedPromise->await());
}

public function testAllRejectsWhenOneFutureFails(): void
{
$ampAdapter = new AmpFutureAdapter();
$deferred = new DeferredFuture();
$allPromise = $ampAdapter->all([
'resolved' => Future::complete(1),
'rejected' => $deferred->getFuture(),
]);

$deferred->error(new \RuntimeException('failed'));

$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('failed');

$allPromise->adoptedPromise->await();
}

public function testAllRemainsRejectedWhenAnotherFutureResolves(): void
{
$ampAdapter = new AmpFutureAdapter();
$rejected = new DeferredFuture();
$resolved = new DeferredFuture();
$allPromise = $ampAdapter->all([$rejected->getFuture(), $resolved->getFuture()]);

$rejected->error(new \RuntimeException('failed'));
$resolved->complete('resolved');

$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('failed');

$allPromise->adoptedPromise->await();
}

public function testAllIgnoresOtherFuturesAfterRejection(): void
{
$ampAdapter = new AmpFutureAdapter();
$rejected = new DeferredFuture();
$resolved = new DeferredFuture();
$alsoRejected = new DeferredFuture();
$allPromise = $ampAdapter->all([
$rejected->getFuture(),
$resolved->getFuture(),
$alsoRejected->getFuture(),
]);

$rejected->error(new \RuntimeException('failed'));

try {
$allPromise->adoptedPromise->await();
self::fail('Expected aggregate rejection.');
} catch (\RuntimeException $exception) {
self::assertSame('failed', $exception->getMessage());
}

$resolved->complete('resolved');
$alsoRejected->error(new \RuntimeException('also failed'));

async(static function (): void {})->await();
}
}
Loading