p2 project
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

433 lines
14 KiB

<?php
declare(strict_types=1);
namespace GuzzleHttp\Promise\Tests;
use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\EachPromise;
use GuzzleHttp\Promise\FulfilledPromise;
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\RejectedPromise;
use PHPUnit\Framework\TestCase;
/**
* @covers \GuzzleHttp\Promise\EachPromise
*/
class EachPromiseTest extends TestCase
{
public function testReturnsSameInstance(): void
{
$each = new EachPromise([], ['concurrency' => 100]);
$this->assertSame($each->promise(), $each->promise());
}
public function testResolvesInCaseOfAnEmptyList(): void
{
$promises = [];
$each = new EachPromise($promises);
$p = $each->promise();
$this->assertNull($p->wait());
$this->assertTrue(P\Is::fulfilled($p));
}
public function testResolvesInCaseOfAnEmptyListAndInvokesFulfilled(): void
{
$promises = [];
$each = new EachPromise($promises);
$p = $each->promise();
$onFulfilledCalled = false;
$onRejectedCalled = false;
$p->then(
function () use (&$onFulfilledCalled): void {
$onFulfilledCalled = true;
},
function () use (&$onRejectedCalled): void {
$onRejectedCalled = true;
}
);
$this->assertNull($p->wait());
$this->assertTrue(P\Is::fulfilled($p));
$this->assertTrue($onFulfilledCalled);
$this->assertFalse($onRejectedCalled);
}
public function testInvokesAllPromises(): void
{
$promises = [new Promise(), new Promise(), new Promise()];
$called = [];
$each = new EachPromise($promises, [
'fulfilled' => function ($value) use (&$called): void {
$called[] = $value;
},
]);
$p = $each->promise();
$promises[0]->resolve('a');
$promises[1]->resolve('c');
$promises[2]->resolve('b');
P\Utils::queue()->run();
$this->assertSame(['a', 'c', 'b'], $called);
$this->assertTrue(P\Is::fulfilled($p));
}
public function testIsWaitable(): void
{
$a = $this->createSelfResolvingPromise('a');
$b = $this->createSelfResolvingPromise('b');
$called = [];
$each = new EachPromise([$a, $b], [
'fulfilled' => function ($value) use (&$called): void { $called[] = $value; },
]);
$p = $each->promise();
$this->assertNull($p->wait());
$this->assertTrue(P\Is::fulfilled($p));
$this->assertSame(['a', 'b'], $called);
}
public function testCanResolveBeforeConsumingAll(): void
{
$called = 0;
$a = $this->createSelfResolvingPromise('a');
$b = new Promise(function (): void { $this->fail(); });
$each = new EachPromise([$a, $b], [
'fulfilled' => function ($value, $idx, Promise $aggregate) use (&$called): void {
$this->assertSame($idx, 0);
$this->assertSame('a', $value);
$aggregate->resolve(null);
++$called;
},
'rejected' => function (\Exception $reason): void {
$this->fail($reason->getMessage());
},
]);
$p = $each->promise();
$p->wait();
$this->assertNull($p->wait());
$this->assertSame(1, $called);
$this->assertTrue(P\Is::fulfilled($a));
$this->assertTrue(P\Is::pending($b));
// Resolving $b has no effect on the aggregate promise.
$b->resolve('foo');
$this->assertSame(1, $called);
}
public function testLimitsPendingPromises(): void
{
$pending = [new Promise(), new Promise(), new Promise(), new Promise()];
$promises = new \ArrayIterator($pending);
$each = new EachPromise($promises, ['concurrency' => 2]);
$p = $each->promise();
$this->assertCount(2, PropertyHelper::get($each, 'pending'));
$pending[0]->resolve('a');
$this->assertCount(2, PropertyHelper::get($each, 'pending'));
$this->assertTrue($promises->valid());
$pending[1]->resolve('b');
P\Utils::queue()->run();
$this->assertCount(2, PropertyHelper::get($each, 'pending'));
$this->assertTrue($promises->valid());
$promises[2]->resolve('c');
P\Utils::queue()->run();
$this->assertCount(1, PropertyHelper::get($each, 'pending'));
$this->assertTrue(P\Is::pending($p));
$promises[3]->resolve('d');
P\Utils::queue()->run();
$this->assertNull(PropertyHelper::get($each, 'pending'));
$this->assertTrue(P\Is::fulfilled($p));
$this->assertFalse($promises->valid());
}
public function testDynamicallyLimitsPendingPromises(): void
{
$calls = [];
$pendingFn = function ($count) use (&$calls) {
$calls[] = $count;
return 2;
};
$pending = [new Promise(), new Promise(), new Promise(), new Promise()];
$promises = new \ArrayIterator($pending);
$each = new EachPromise($promises, ['concurrency' => $pendingFn]);
$p = $each->promise();
$this->assertCount(2, PropertyHelper::get($each, 'pending'));
$pending[0]->resolve('a');
$this->assertCount(2, PropertyHelper::get($each, 'pending'));
$this->assertTrue($promises->valid());
$pending[1]->resolve('b');
$this->assertCount(2, PropertyHelper::get($each, 'pending'));
P\Utils::queue()->run();
$this->assertTrue($promises->valid());
$promises[2]->resolve('c');
P\Utils::queue()->run();
$this->assertCount(1, PropertyHelper::get($each, 'pending'));
$this->assertTrue(P\Is::pending($p));
$promises[3]->resolve('d');
P\Utils::queue()->run();
$this->assertNull(PropertyHelper::get($each, 'pending'));
$this->assertTrue(P\Is::fulfilled($p));
$this->assertSame([0, 1, 1, 1], $calls);
$this->assertFalse($promises->valid());
}
public function testClearsReferencesWhenResolved(): void
{
$called = false;
$a = new Promise(function () use (&$a, &$called): void {
$a->resolve('a');
$called = true;
});
$each = new EachPromise([$a], [
'concurrency' => function () { return 1; },
'fulfilled' => function (): void {},
'rejected' => function (): void {},
]);
$each->promise()->wait();
$this->assertNull(PropertyHelper::get($each, 'onFulfilled'));
$this->assertNull(PropertyHelper::get($each, 'onRejected'));
$this->assertNull(PropertyHelper::get($each, 'iterable'));
$this->assertNull(PropertyHelper::get($each, 'pending'));
$this->assertNull(PropertyHelper::get($each, 'concurrency'));
$this->assertTrue($called);
}
public function testCanBeCancelled(): void
{
$called = false;
$a = new FulfilledPromise('a');
$b = new Promise(function () use (&$called): void { $called = true; });
$each = new EachPromise([$a, $b], [
'fulfilled' => function ($value, $idx, Promise $aggregate): void {
$aggregate->cancel();
},
'rejected' => function ($reason) use (&$called): void {
$called = true;
},
]);
$p = $each->promise();
$p->wait(false);
$this->assertTrue(P\Is::fulfilled($a));
$this->assertTrue(P\Is::pending($b));
$this->assertTrue(P\Is::rejected($p));
$this->assertFalse($called);
}
public function testDoesNotBlowStackWithFulfilledPromises(): void
{
$pending = [];
for ($i = 0; $i < 100; ++$i) {
$pending[] = new FulfilledPromise($i);
}
$values = [];
$each = new EachPromise($pending, [
'fulfilled' => function ($value) use (&$values): void {
$values[] = $value;
},
]);
$called = false;
$each->promise()->then(function () use (&$called): void {
$called = true;
});
$this->assertFalse($called);
P\Utils::queue()->run();
$this->assertTrue($called);
$this->assertSame(range(0, 99), $values);
}
public function testDoesNotBlowStackWithRejectedPromises(): void
{
$pending = [];
for ($i = 0; $i < 100; ++$i) {
$pending[] = new RejectedPromise($i);
}
$values = [];
$each = new EachPromise($pending, [
'rejected' => function ($value) use (&$values): void {
$values[] = $value;
},
]);
$called = false;
$each->promise()->then(
function () use (&$called): void { $called = true; },
function (): void { $this->fail('Should not have rejected.'); }
);
$this->assertFalse($called);
P\Utils::queue()->run();
$this->assertTrue($called);
$this->assertSame(range(0, 99), $values);
}
public function testReturnsPromiseForWhatever(): void
{
$called = [];
$arr = ['a', 'b'];
$each = new EachPromise($arr, [
'fulfilled' => function ($v) use (&$called): void { $called[] = $v; },
]);
$p = $each->promise();
$this->assertNull($p->wait());
$this->assertSame(['a', 'b'], $called);
}
public function testRejectsAggregateWhenNextThrows(): void
{
$iter = function () {
yield 'a';
throw new \Exception('Failure');
};
$each = new EachPromise($iter());
$p = $each->promise();
$e = null;
$received = null;
$p->then(null, function ($reason) use (&$e): void { $e = $reason; });
P\Utils::queue()->run();
$this->assertInstanceOf(\Exception::class, $e);
$this->assertSame('Failure', $e->getMessage());
}
public function testDoesNotCallNextOnIteratorUntilNeededWhenWaiting(): void
{
$results = [];
$values = [10];
$remaining = 9;
$iter = function () use (&$values) {
while ($value = array_pop($values)) {
yield $value;
}
};
$each = new EachPromise($iter(), [
'concurrency' => 1,
'fulfilled' => function ($r) use (&$results, &$values, &$remaining): void {
$results[] = $r;
if ($remaining > 0) {
$values[] = $remaining--;
}
},
]);
$each->promise()->wait();
$this->assertSame(range(10, 1), $results);
}
public function testDoesNotCallNextOnIteratorUntilNeededWhenAsync(): void
{
$firstPromise = new Promise();
$pending = [$firstPromise];
$values = [$firstPromise];
$results = [];
$remaining = 9;
$iter = function () use (&$values) {
while ($value = array_pop($values)) {
yield $value;
}
};
$each = new EachPromise($iter(), [
'concurrency' => 1,
'fulfilled' => function ($r) use (&$results, &$values, &$remaining, &$pending): void {
$results[] = $r;
if ($remaining-- > 0) {
$pending[] = $values[] = new Promise();
}
},
]);
$i = 0;
$each->promise();
while ($promise = array_pop($pending)) {
$promise->resolve($i++);
P\Utils::queue()->run();
}
$this->assertSame(range(0, 9), $results);
}
private function createSelfResolvingPromise($value)
{
$p = new Promise(function () use (&$p, $value): void {
$p->resolve($value);
});
$trickCsFixer = true;
return $p;
}
public function testMutexPreventsGeneratorRecursion(): void
{
if (defined('HHVM_VERSION')) {
$this->markTestIncomplete('Broken on HHVM.');
}
$results = $promises = [];
for ($i = 0; $i < 20; ++$i) {
$p = $this->createSelfResolvingPromise($i);
$pending[] = $p;
$promises[] = $p;
}
$iter = function () use (&$promises, &$pending) {
foreach ($promises as $promise) {
// Resolve a promises, which will trigger the then() function,
// which would cause the EachPromise to try to add more
// promises to the queue. Without a lock, this would trigger
// a "Cannot resume an already running generator" fatal error.
if ($p = array_pop($pending)) {
$p->wait();
}
yield $promise;
}
};
$each = new EachPromise($iter(), [
'concurrency' => 5,
'fulfilled' => function ($r) use (&$results, &$pending): void {
$results[] = $r;
},
]);
$each->promise()->wait();
$this->assertCount(20, $results);
}
public function testIteratorWithSameKey(): void
{
if (defined('HHVM_VERSION')) {
$this->markTestIncomplete('Broken on HHVM.');
}
$iter = function () {
yield 'foo' => $this->createSelfResolvingPromise(1);
yield 'foo' => $this->createSelfResolvingPromise(2);
yield 1 => $this->createSelfResolvingPromise(3);
yield 1 => $this->createSelfResolvingPromise(4);
};
$called = 0;
$each = new EachPromise($iter(), [
'fulfilled' => function ($value, $idx, Promise $aggregate) use (&$called): void {
++$called;
if ($value < 3) {
$this->assertSame('foo', $idx);
} else {
$this->assertSame(1, $idx);
}
},
]);
$each->promise()->wait();
$this->assertSame(4, $called);
}
public function testIsWaitableWhenLimited(): void
{
$promises = [
$this->createSelfResolvingPromise('a'),
$this->createSelfResolvingPromise('c'),
$this->createSelfResolvingPromise('b'),
$this->createSelfResolvingPromise('d'),
];
$called = [];
$each = new EachPromise($promises, [
'concurrency' => 2,
'fulfilled' => function ($value) use (&$called): void {
$called[] = $value;
},
]);
$p = $each->promise();
$this->assertNull($p->wait());
$this->assertSame(['a', 'c', 'b', 'd'], $called);
$this->assertTrue(P\Is::fulfilled($p));
}
}