123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593 |
- <?php
- namespace GuzzleHttp\Promise\Tests;
- use GuzzleHttp\Promise\CancellationException;
- use GuzzleHttp\Promise as P;
- use GuzzleHttp\Promise\FulfilledPromise;
- use GuzzleHttp\Promise\Promise;
- use GuzzleHttp\Promise\RejectedPromise;
- use GuzzleHttp\Promise\RejectionException;
- use PHPUnit\Framework\TestCase;
- /**
- * @covers GuzzleHttp\Promise\Promise
- */
- class PromiseTest extends TestCase
- {
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage The promise is already fulfilled
- */
- public function testCannotResolveNonPendingPromise()
- {
- $p = new Promise();
- $p->resolve('foo');
- $p->resolve('bar');
- $this->assertEquals('foo', $p->wait());
- }
- public function testCanResolveWithSameValue()
- {
- $p = new Promise();
- $p->resolve('foo');
- $p->resolve('foo');
- }
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Cannot change a fulfilled promise to rejected
- */
- public function testCannotRejectNonPendingPromise()
- {
- $p = new Promise();
- $p->resolve('foo');
- $p->reject('bar');
- $this->assertEquals('foo', $p->wait());
- }
- public function testCanRejectWithSameValue()
- {
- $p = new Promise();
- $p->reject('foo');
- $p->reject('foo');
- }
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Cannot change a fulfilled promise to rejected
- */
- public function testCannotRejectResolveWithSameValue()
- {
- $p = new Promise();
- $p->resolve('foo');
- $p->reject('foo');
- }
- public function testInvokesWaitFunction()
- {
- $p = new Promise(function () use (&$p) { $p->resolve('10'); });
- $this->assertEquals('10', $p->wait());
- }
- /**
- * @expectedException \GuzzleHttp\Promise\RejectionException
- */
- public function testRejectsAndThrowsWhenWaitFailsToResolve()
- {
- $p = new Promise(function () {});
- $p->wait();
- }
- /**
- * @expectedException \GuzzleHttp\Promise\RejectionException
- * @expectedExceptionMessage The promise was rejected with reason: foo
- */
- public function testThrowsWhenUnwrapIsRejectedWithNonException()
- {
- $p = new Promise(function () use (&$p) { $p->reject('foo'); });
- $p->wait();
- }
- /**
- * @expectedException \UnexpectedValueException
- * @expectedExceptionMessage foo
- */
- public function testThrowsWhenUnwrapIsRejectedWithException()
- {
- $e = new \UnexpectedValueException('foo');
- $p = new Promise(function () use (&$p, $e) { $p->reject($e); });
- $p->wait();
- }
- public function testDoesNotUnwrapExceptionsWhenDisabled()
- {
- $p = new Promise(function () use (&$p) { $p->reject('foo'); });
- $this->assertEquals('pending', $p->getState());
- $p->wait(false);
- $this->assertEquals('rejected', $p->getState());
- }
- public function testRejectsSelfWhenWaitThrows()
- {
- $e = new \UnexpectedValueException('foo');
- $p = new Promise(function () use ($e) { throw $e; });
- try {
- $p->wait();
- $this->fail();
- } catch (\UnexpectedValueException $e) {
- $this->assertEquals('rejected', $p->getState());
- }
- }
- public function testWaitsOnNestedPromises()
- {
- $p = new Promise(function () use (&$p) { $p->resolve('_'); });
- $p2 = new Promise(function () use (&$p2) { $p2->resolve('foo'); });
- $p3 = $p->then(function () use ($p2) { return $p2; });
- $this->assertSame('foo', $p3->wait());
- }
- /**
- * @expectedException \GuzzleHttp\Promise\RejectionException
- */
- public function testThrowsWhenWaitingOnPromiseWithNoWaitFunction()
- {
- $p = new Promise();
- $p->wait();
- }
- public function testThrowsWaitExceptionAfterPromiseIsResolved()
- {
- $p = new Promise(function () use (&$p) {
- $p->reject('Foo!');
- throw new \Exception('Bar?');
- });
- try {
- $p->wait();
- $this->fail();
- } catch (\Exception $e) {
- $this->assertEquals('Bar?', $e->getMessage());
- }
- }
- public function testGetsActualWaitValueFromThen()
- {
- $p = new Promise(function () use (&$p) { $p->reject('Foo!'); });
- $p2 = $p->then(null, function ($reason) {
- return new RejectedPromise([$reason]);
- });
- try {
- $p2->wait();
- $this->fail('Should have thrown');
- } catch (RejectionException $e) {
- $this->assertEquals(['Foo!'], $e->getReason());
- }
- }
- public function testWaitBehaviorIsBasedOnLastPromiseInChain()
- {
- $p3 = new Promise(function () use (&$p3) { $p3->resolve('Whoop'); });
- $p2 = new Promise(function () use (&$p2, $p3) { $p2->reject($p3); });
- $p = new Promise(function () use (&$p, $p2) { $p->reject($p2); });
- $this->assertEquals('Whoop', $p->wait());
- }
- public function testWaitsOnAPromiseChainEvenWhenNotUnwrapped()
- {
- $p2 = new Promise(function () use (&$p2) {
- $p2->reject('Fail');
- });
- $p = new Promise(function () use ($p2, &$p) {
- $p->resolve($p2);
- });
- $p->wait(false);
- $this->assertSame(Promise::REJECTED, $p2->getState());
- }
- public function testCannotCancelNonPending()
- {
- $p = new Promise();
- $p->resolve('foo');
- $p->cancel();
- $this->assertEquals('fulfilled', $p->getState());
- }
- /**
- * @expectedException \GuzzleHttp\Promise\CancellationException
- */
- public function testCancelsPromiseWhenNoCancelFunction()
- {
- $p = new Promise();
- $p->cancel();
- $this->assertEquals('rejected', $p->getState());
- $p->wait();
- }
- public function testCancelsPromiseWithCancelFunction()
- {
- $called = false;
- $p = new Promise(null, function () use (&$called) { $called = true; });
- $p->cancel();
- $this->assertEquals('rejected', $p->getState());
- $this->assertTrue($called);
- }
- public function testCancelsUppermostPendingPromise()
- {
- $called = false;
- $p1 = new Promise(null, function () use (&$called) { $called = true; });
- $p2 = $p1->then(function () {});
- $p3 = $p2->then(function () {});
- $p4 = $p3->then(function () {});
- $p3->cancel();
- $this->assertEquals('rejected', $p1->getState());
- $this->assertEquals('rejected', $p2->getState());
- $this->assertEquals('rejected', $p3->getState());
- $this->assertEquals('pending', $p4->getState());
- $this->assertTrue($called);
- try {
- $p3->wait();
- $this->fail();
- } catch (CancellationException $e) {
- $this->assertContains('cancelled', $e->getMessage());
- }
- try {
- $p4->wait();
- $this->fail();
- } catch (CancellationException $e) {
- $this->assertContains('cancelled', $e->getMessage());
- }
- $this->assertEquals('rejected', $p4->getState());
- }
- public function testCancelsChildPromises()
- {
- $called1 = $called2 = $called3 = false;
- $p1 = new Promise(null, function () use (&$called1) { $called1 = true; });
- $p2 = new Promise(null, function () use (&$called2) { $called2 = true; });
- $p3 = new Promise(null, function () use (&$called3) { $called3 = true; });
- $p4 = $p2->then(function () use ($p3) { return $p3; });
- $p5 = $p4->then(function () { $this->fail(); });
- $p4->cancel();
- $this->assertEquals('pending', $p1->getState());
- $this->assertEquals('rejected', $p2->getState());
- $this->assertEquals('rejected', $p4->getState());
- $this->assertEquals('pending', $p5->getState());
- $this->assertFalse($called1);
- $this->assertTrue($called2);
- $this->assertFalse($called3);
- }
- public function testRejectsPromiseWhenCancelFails()
- {
- $called = false;
- $p = new Promise(null, function () use (&$called) {
- $called = true;
- throw new \Exception('e');
- });
- $p->cancel();
- $this->assertEquals('rejected', $p->getState());
- $this->assertTrue($called);
- try {
- $p->wait();
- $this->fail();
- } catch (\Exception $e) {
- $this->assertEquals('e', $e->getMessage());
- }
- }
- public function testCreatesPromiseWhenFulfilledAfterThen()
- {
- $p = new Promise();
- $carry = null;
- $p2 = $p->then(function ($v) use (&$carry) { $carry = $v; });
- $this->assertNotSame($p, $p2);
- $p->resolve('foo');
- P\queue()->run();
- $this->assertEquals('foo', $carry);
- }
- public function testCreatesPromiseWhenFulfilledBeforeThen()
- {
- $p = new Promise();
- $p->resolve('foo');
- $carry = null;
- $p2 = $p->then(function ($v) use (&$carry) { $carry = $v; });
- $this->assertNotSame($p, $p2);
- $this->assertNull($carry);
- \GuzzleHttp\Promise\queue()->run();
- $this->assertEquals('foo', $carry);
- }
- public function testCreatesPromiseWhenFulfilledWithNoCallback()
- {
- $p = new Promise();
- $p->resolve('foo');
- $p2 = $p->then();
- $this->assertNotSame($p, $p2);
- $this->assertInstanceOf(FulfilledPromise::class, $p2);
- }
- public function testCreatesPromiseWhenRejectedAfterThen()
- {
- $p = new Promise();
- $carry = null;
- $p2 = $p->then(null, function ($v) use (&$carry) { $carry = $v; });
- $this->assertNotSame($p, $p2);
- $p->reject('foo');
- P\queue()->run();
- $this->assertEquals('foo', $carry);
- }
- public function testCreatesPromiseWhenRejectedBeforeThen()
- {
- $p = new Promise();
- $p->reject('foo');
- $carry = null;
- $p2 = $p->then(null, function ($v) use (&$carry) { $carry = $v; });
- $this->assertNotSame($p, $p2);
- $this->assertNull($carry);
- P\queue()->run();
- $this->assertEquals('foo', $carry);
- }
- public function testCreatesPromiseWhenRejectedWithNoCallback()
- {
- $p = new Promise();
- $p->reject('foo');
- $p2 = $p->then();
- $this->assertNotSame($p, $p2);
- $this->assertInstanceOf(RejectedPromise::class, $p2);
- }
- public function testInvokesWaitFnsForThens()
- {
- $p = new Promise(function () use (&$p) { $p->resolve('a'); });
- $p2 = $p
- ->then(function ($v) { return $v . '-1-'; })
- ->then(function ($v) { return $v . '2'; });
- $this->assertEquals('a-1-2', $p2->wait());
- }
- public function testStacksThenWaitFunctions()
- {
- $p1 = new Promise(function () use (&$p1) { $p1->resolve('a'); });
- $p2 = new Promise(function () use (&$p2) { $p2->resolve('b'); });
- $p3 = new Promise(function () use (&$p3) { $p3->resolve('c'); });
- $p4 = $p1
- ->then(function () use ($p2) { return $p2; })
- ->then(function () use ($p3) { return $p3; });
- $this->assertEquals('c', $p4->wait());
- }
- public function testForwardsFulfilledDownChainBetweenGaps()
- {
- $p = new Promise();
- $r = $r2 = null;
- $p->then(null, null)
- ->then(function ($v) use (&$r) { $r = $v; return $v . '2'; })
- ->then(function ($v) use (&$r2) { $r2 = $v; });
- $p->resolve('foo');
- P\queue()->run();
- $this->assertEquals('foo', $r);
- $this->assertEquals('foo2', $r2);
- }
- public function testForwardsRejectedPromisesDownChainBetweenGaps()
- {
- $p = new Promise();
- $r = $r2 = null;
- $p->then(null, null)
- ->then(null, function ($v) use (&$r) { $r = $v; return $v . '2'; })
- ->then(function ($v) use (&$r2) { $r2 = $v; });
- $p->reject('foo');
- P\queue()->run();
- $this->assertEquals('foo', $r);
- $this->assertEquals('foo2', $r2);
- }
- public function testForwardsThrownPromisesDownChainBetweenGaps()
- {
- $e = new \Exception();
- $p = new Promise();
- $r = $r2 = null;
- $p->then(null, null)
- ->then(null, function ($v) use (&$r, $e) {
- $r = $v;
- throw $e;
- })
- ->then(
- null,
- function ($v) use (&$r2) { $r2 = $v; }
- );
- $p->reject('foo');
- P\queue()->run();
- $this->assertEquals('foo', $r);
- $this->assertSame($e, $r2);
- }
- public function testForwardsReturnedRejectedPromisesDownChainBetweenGaps()
- {
- $p = new Promise();
- $rejected = new RejectedPromise('bar');
- $r = $r2 = null;
- $p->then(null, null)
- ->then(null, function ($v) use (&$r, $rejected) {
- $r = $v;
- return $rejected;
- })
- ->then(
- null,
- function ($v) use (&$r2) { $r2 = $v; }
- );
- $p->reject('foo');
- P\queue()->run();
- $this->assertEquals('foo', $r);
- $this->assertEquals('bar', $r2);
- try {
- $p->wait();
- } catch (RejectionException $e) {
- $this->assertEquals('foo', $e->getReason());
- }
- }
- public function testForwardsHandlersToNextPromise()
- {
- $p = new Promise();
- $p2 = new Promise();
- $resolved = null;
- $p
- ->then(function ($v) use ($p2) { return $p2; })
- ->then(function ($value) use (&$resolved) { $resolved = $value; });
- $p->resolve('a');
- $p2->resolve('b');
- P\queue()->run();
- $this->assertEquals('b', $resolved);
- }
- public function testRemovesReferenceFromChildWhenParentWaitedUpon()
- {
- $r = null;
- $p = new Promise(function () use (&$p) { $p->resolve('a'); });
- $p2 = new Promise(function () use (&$p2) { $p2->resolve('b'); });
- $pb = $p->then(
- function ($v) use ($p2, &$r) {
- $r = $v;
- return $p2;
- })
- ->then(function ($v) { return $v . '.'; });
- $this->assertEquals('a', $p->wait());
- $this->assertEquals('b', $p2->wait());
- $this->assertEquals('b.', $pb->wait());
- $this->assertEquals('a', $r);
- }
- public function testForwardsHandlersWhenFulfilledPromiseIsReturned()
- {
- $res = [];
- $p = new Promise();
- $p2 = new Promise();
- $p2->resolve('foo');
- $p2->then(function ($v) use (&$res) { $res[] = 'A:' . $v; });
- // $res is A:foo
- $p
- ->then(function () use ($p2, &$res) { $res[] = 'B'; return $p2; })
- ->then(function ($v) use (&$res) { $res[] = 'C:' . $v; });
- $p->resolve('a');
- $p->then(function ($v) use (&$res) { $res[] = 'D:' . $v; });
- P\queue()->run();
- $this->assertEquals(['A:foo', 'B', 'D:a', 'C:foo'], $res);
- }
- public function testForwardsHandlersWhenRejectedPromiseIsReturned()
- {
- $res = [];
- $p = new Promise();
- $p2 = new Promise();
- $p2->reject('foo');
- $p2->then(null, function ($v) use (&$res) { $res[] = 'A:' . $v; });
- $p->then(null, function () use ($p2, &$res) { $res[] = 'B'; return $p2; })
- ->then(null, function ($v) use (&$res) { $res[] = 'C:' . $v; });
- $p->reject('a');
- $p->then(null, function ($v) use (&$res) { $res[] = 'D:' . $v; });
- P\queue()->run();
- $this->assertEquals(['A:foo', 'B', 'D:a', 'C:foo'], $res);
- }
- public function testDoesNotForwardRejectedPromise()
- {
- $res = [];
- $p = new Promise();
- $p2 = new Promise();
- $p2->cancel();
- $p2->then(function ($v) use (&$res) { $res[] = "B:$v"; return $v; });
- $p->then(function ($v) use ($p2, &$res) { $res[] = "B:$v"; return $p2; })
- ->then(function ($v) use (&$res) { $res[] = 'C:' . $v; });
- $p->resolve('a');
- $p->then(function ($v) use (&$res) { $res[] = 'D:' . $v; });
- P\queue()->run();
- $this->assertEquals(['B:a', 'D:a'], $res);
- }
- public function testRecursivelyForwardsWhenOnlyThennable()
- {
- $res = [];
- $p = new Promise();
- $p2 = new Thennable();
- $p2->resolve('foo');
- $p2->then(function ($v) use (&$res) { $res[] = 'A:' . $v; });
- $p->then(function () use ($p2, &$res) { $res[] = 'B'; return $p2; })
- ->then(function ($v) use (&$res) { $res[] = 'C:' . $v; });
- $p->resolve('a');
- $p->then(function ($v) use (&$res) { $res[] = 'D:' . $v; });
- P\queue()->run();
- $this->assertEquals(['A:foo', 'B', 'D:a', 'C:foo'], $res);
- }
- public function testRecursivelyForwardsWhenNotInstanceOfPromise()
- {
- $res = [];
- $p = new Promise();
- $p2 = new NotPromiseInstance();
- $p2->then(function ($v) use (&$res) { $res[] = 'A:' . $v; });
- $p->then(function () use ($p2, &$res) { $res[] = 'B'; return $p2; })
- ->then(function ($v) use (&$res) { $res[] = 'C:' . $v; });
- $p->resolve('a');
- $p->then(function ($v) use (&$res) { $res[] = 'D:' . $v; });
- P\queue()->run();
- $this->assertEquals(['B', 'D:a'], $res);
- $p2->resolve('foo');
- P\queue()->run();
- $this->assertEquals(['B', 'D:a', 'A:foo', 'C:foo'], $res);
- }
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Cannot fulfill or reject a promise with itself
- */
- public function testCannotResolveWithSelf()
- {
- $p = new Promise();
- $p->resolve($p);
- }
- /**
- * @expectedException \LogicException
- * @expectedExceptionMessage Cannot fulfill or reject a promise with itself
- */
- public function testCannotRejectWithSelf()
- {
- $p = new Promise();
- $p->reject($p);
- }
- public function testDoesNotBlowStackWhenWaitingOnNestedThens()
- {
- $inner = new Promise(function () use (&$inner) { $inner->resolve(0); });
- $prev = $inner;
- for ($i = 1; $i < 100; $i++) {
- $prev = $prev->then(function ($i) { return $i + 1; });
- }
- $parent = new Promise(function () use (&$parent, $prev) {
- $parent->resolve($prev);
- });
- $this->assertEquals(99, $parent->wait());
- }
- public function testOtherwiseIsSugarForRejections()
- {
- $p = new Promise();
- $p->reject('foo');
- $p->otherwise(function ($v) use (&$c) { $c = $v; });
- P\queue()->run();
- $this->assertEquals($c, 'foo');
- }
- }
|