PromiseTest.php 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. <?php
  2. namespace React\Promise;
  3. use React\Promise\PromiseAdapter\CallbackPromiseAdapter;
  4. class PromiseTest extends TestCase
  5. {
  6. use PromiseTest\FullTestTrait;
  7. public function getPromiseTestAdapter(callable $canceller = null)
  8. {
  9. $resolveCallback = $rejectCallback = $progressCallback = null;
  10. $promise = new Promise(function ($resolve, $reject, $progress) use (&$resolveCallback, &$rejectCallback, &$progressCallback) {
  11. $resolveCallback = $resolve;
  12. $rejectCallback = $reject;
  13. $progressCallback = $progress;
  14. }, $canceller);
  15. return new CallbackPromiseAdapter([
  16. 'promise' => function () use ($promise) {
  17. return $promise;
  18. },
  19. 'resolve' => $resolveCallback,
  20. 'reject' => $rejectCallback,
  21. 'notify' => $progressCallback,
  22. 'settle' => $resolveCallback,
  23. ]);
  24. }
  25. /** @test */
  26. public function shouldRejectIfResolverThrowsException()
  27. {
  28. $exception = new \Exception('foo');
  29. $promise = new Promise(function () use ($exception) {
  30. throw $exception;
  31. });
  32. $mock = $this->createCallableMock();
  33. $mock
  34. ->expects($this->once())
  35. ->method('__invoke')
  36. ->with($this->identicalTo($exception));
  37. $promise
  38. ->then($this->expectCallableNever(), $mock);
  39. }
  40. /** @test */
  41. public function shouldResolveWithoutCreatingGarbageCyclesIfResolverResolvesWithException()
  42. {
  43. gc_collect_cycles();
  44. $promise = new Promise(function ($resolve) {
  45. $resolve(new \Exception('foo'));
  46. });
  47. unset($promise);
  48. $this->assertSame(0, gc_collect_cycles());
  49. }
  50. /** @test */
  51. public function shouldRejectWithoutCreatingGarbageCyclesIfResolverThrowsExceptionWithoutResolver()
  52. {
  53. gc_collect_cycles();
  54. $promise = new Promise(function () {
  55. throw new \Exception('foo');
  56. });
  57. unset($promise);
  58. $this->assertSame(0, gc_collect_cycles());
  59. }
  60. /** @test */
  61. public function shouldRejectWithoutCreatingGarbageCyclesIfResolverRejectsWithException()
  62. {
  63. gc_collect_cycles();
  64. $promise = new Promise(function ($resolve, $reject) {
  65. $reject(new \Exception('foo'));
  66. });
  67. unset($promise);
  68. $this->assertSame(0, gc_collect_cycles());
  69. }
  70. /** @test */
  71. public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerRejectsWithException()
  72. {
  73. gc_collect_cycles();
  74. $promise = new Promise(function ($resolve, $reject) { }, function ($resolve, $reject) {
  75. $reject(new \Exception('foo'));
  76. });
  77. $promise->cancel();
  78. unset($promise);
  79. $this->assertSame(0, gc_collect_cycles());
  80. }
  81. /** @test */
  82. public function shouldRejectWithoutCreatingGarbageCyclesIfParentCancellerRejectsWithException()
  83. {
  84. gc_collect_cycles();
  85. $promise = new Promise(function ($resolve, $reject) { }, function ($resolve, $reject) {
  86. $reject(new \Exception('foo'));
  87. });
  88. $promise->then()->then()->then()->cancel();
  89. unset($promise);
  90. $this->assertSame(0, gc_collect_cycles());
  91. }
  92. /** @test */
  93. public function shouldRejectWithoutCreatingGarbageCyclesIfResolverThrowsException()
  94. {
  95. gc_collect_cycles();
  96. $promise = new Promise(function ($resolve, $reject) {
  97. throw new \Exception('foo');
  98. });
  99. unset($promise);
  100. $this->assertSame(0, gc_collect_cycles());
  101. }
  102. /**
  103. * Test that checks number of garbage cycles after throwing from a canceller
  104. * that explicitly uses a reference to the promise. This is rather synthetic,
  105. * actual use cases often have implicit (hidden) references which ought not
  106. * to be stored in the stack trace.
  107. *
  108. * Reassigned arguments only show up in the stack trace in PHP 7, so we can't
  109. * avoid this on legacy PHP. As an alternative, consider explicitly unsetting
  110. * any references before throwing.
  111. *
  112. * @test
  113. * @requires PHP 7
  114. */
  115. public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerWithReferenceThrowsException()
  116. {
  117. gc_collect_cycles();
  118. $promise = new Promise(function () {}, function () use (&$promise) {
  119. throw new \Exception('foo');
  120. });
  121. $promise->cancel();
  122. unset($promise);
  123. $this->assertSame(0, gc_collect_cycles());
  124. }
  125. /**
  126. * @test
  127. * @requires PHP 7
  128. * @see self::shouldRejectWithoutCreatingGarbageCyclesIfCancellerWithReferenceThrowsException
  129. */
  130. public function shouldRejectWithoutCreatingGarbageCyclesIfResolverWithReferenceThrowsException()
  131. {
  132. gc_collect_cycles();
  133. $promise = new Promise(function () use (&$promise) {
  134. throw new \Exception('foo');
  135. });
  136. unset($promise);
  137. $this->assertSame(0, gc_collect_cycles());
  138. }
  139. /**
  140. * @test
  141. * @requires PHP 7
  142. * @see self::shouldRejectWithoutCreatingGarbageCyclesIfCancellerWithReferenceThrowsException
  143. */
  144. public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerHoldsReferenceAndResolverThrowsException()
  145. {
  146. gc_collect_cycles();
  147. $promise = new Promise(function () {
  148. throw new \Exception('foo');
  149. }, function () use (&$promise) { });
  150. unset($promise);
  151. $this->assertSame(0, gc_collect_cycles());
  152. }
  153. /** @test */
  154. public function shouldIgnoreNotifyAfterReject()
  155. {
  156. $promise = new Promise(function () { }, function ($resolve, $reject, $notify) {
  157. $reject(new \Exception('foo'));
  158. $notify(42);
  159. });
  160. $promise->then(null, null, $this->expectCallableNever());
  161. $promise->cancel();
  162. }
  163. /** @test */
  164. public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromise()
  165. {
  166. gc_collect_cycles();
  167. $promise = new Promise(function () { });
  168. unset($promise);
  169. $this->assertSame(0, gc_collect_cycles());
  170. }
  171. /** @test */
  172. public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithThenFollowers()
  173. {
  174. gc_collect_cycles();
  175. $promise = new Promise(function () { });
  176. $promise->then()->then()->then();
  177. unset($promise);
  178. $this->assertSame(0, gc_collect_cycles());
  179. }
  180. /** @test */
  181. public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithDoneFollowers()
  182. {
  183. gc_collect_cycles();
  184. $promise = new Promise(function () { });
  185. $promise->done();
  186. unset($promise);
  187. $this->assertSame(0, gc_collect_cycles());
  188. }
  189. /** @test */
  190. public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithOtherwiseFollowers()
  191. {
  192. gc_collect_cycles();
  193. $promise = new Promise(function () { });
  194. $promise->otherwise(function () { });
  195. unset($promise);
  196. $this->assertSame(0, gc_collect_cycles());
  197. }
  198. /** @test */
  199. public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithAlwaysFollowers()
  200. {
  201. gc_collect_cycles();
  202. $promise = new Promise(function () { });
  203. $promise->always(function () { });
  204. unset($promise);
  205. $this->assertSame(0, gc_collect_cycles());
  206. }
  207. /** @test */
  208. public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithProgressFollowers()
  209. {
  210. gc_collect_cycles();
  211. $promise = new Promise(function () { });
  212. $promise->then(null, null, function () { });
  213. unset($promise);
  214. $this->assertSame(0, gc_collect_cycles());
  215. }
  216. /** @test */
  217. public function shouldFulfillIfFullfilledWithSimplePromise()
  218. {
  219. $adapter = $this->getPromiseTestAdapter();
  220. $mock = $this->createCallableMock();
  221. $mock
  222. ->expects($this->once())
  223. ->method('__invoke')
  224. ->with($this->identicalTo('foo'));
  225. $adapter->promise()
  226. ->then($mock);
  227. $adapter->resolve(new SimpleFulfilledTestPromise());
  228. }
  229. /** @test */
  230. public function shouldRejectIfRejectedWithSimplePromise()
  231. {
  232. $adapter = $this->getPromiseTestAdapter();
  233. $mock = $this->createCallableMock();
  234. $mock
  235. ->expects($this->once())
  236. ->method('__invoke')
  237. ->with($this->identicalTo('foo'));
  238. $adapter->promise()
  239. ->then($this->expectCallableNever(), $mock);
  240. $adapter->resolve(new SimpleRejectedTestPromise());
  241. }
  242. }