TimeoutConnectorTest.php 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. <?php
  2. namespace React\Tests\Socket;
  3. use React\EventLoop\Loop;
  4. use React\Promise\Deferred;
  5. use React\Promise\Promise;
  6. use React\Socket\TimeoutConnector;
  7. class TimeoutConnectorTest extends TestCase
  8. {
  9. public function testConstructWithoutLoopAssignsLoopAutomatically()
  10. {
  11. $base = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
  12. $connector = new TimeoutConnector($base, 0.01);
  13. $ref = new \ReflectionProperty($connector, 'loop');
  14. $ref->setAccessible(true);
  15. $loop = $ref->getValue($connector);
  16. $this->assertInstanceOf('React\EventLoop\LoopInterface', $loop);
  17. }
  18. public function testRejectsPromiseWithoutStartingTimerWhenWrappedConnectorReturnsRejectedPromise()
  19. {
  20. $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
  21. $loop->expects($this->never())->method('addTimer');
  22. $loop->expects($this->never())->method('cancelTimer');
  23. $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
  24. $connector->expects($this->once())->method('connect')->with('example.com:80')->willReturn(\React\Promise\reject(new \RuntimeException('Failed', 42)));
  25. $timeout = new TimeoutConnector($connector, 5.0, $loop);
  26. $promise = $timeout->connect('example.com:80');
  27. $exception = null;
  28. $promise->then(null, function ($reason) use (&$exception) {
  29. $exception = $reason;
  30. });
  31. assert($exception instanceof \RuntimeException);
  32. $this->assertEquals('Failed', $exception->getMessage());
  33. $this->assertEquals(42, $exception->getCode());
  34. }
  35. public function testRejectsPromiseAfterCancellingTimerWhenWrappedConnectorReturnsPendingPromiseThatRejects()
  36. {
  37. $timer = $this->getMockBuilder('React\EventLoop\TimerInterface')->getMock();
  38. $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
  39. $loop->expects($this->once())->method('addTimer')->with(5.0, $this->anything())->willReturn($timer);
  40. $loop->expects($this->once())->method('cancelTimer')->with($timer);
  41. $deferred = new Deferred();
  42. $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
  43. $connector->expects($this->once())->method('connect')->with('example.com:80')->willReturn($deferred->promise());
  44. $timeout = new TimeoutConnector($connector, 5.0, $loop);
  45. $promise = $timeout->connect('example.com:80');
  46. $deferred->reject(new \RuntimeException('Failed', 42));
  47. $exception = null;
  48. $promise->then(null, function ($reason) use (&$exception) {
  49. $exception = $reason;
  50. });
  51. assert($exception instanceof \RuntimeException);
  52. $this->assertEquals('Failed', $exception->getMessage());
  53. $this->assertEquals(42, $exception->getCode());
  54. }
  55. public function testResolvesPromiseWithoutStartingTimerWhenWrappedConnectorReturnsResolvedPromise()
  56. {
  57. $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
  58. $loop->expects($this->never())->method('addTimer');
  59. $loop->expects($this->never())->method('cancelTimer');
  60. $connection = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
  61. $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
  62. $connector->expects($this->once())->method('connect')->with('example.com:80')->willReturn(\React\Promise\resolve($connection));
  63. $timeout = new TimeoutConnector($connector, 5.0, $loop);
  64. $promise = $timeout->connect('example.com:80');
  65. $resolved = null;
  66. $promise->then(function ($value) use (&$resolved) {
  67. $resolved = $value;
  68. });
  69. $this->assertSame($connection, $resolved);
  70. }
  71. public function testResolvesPromiseAfterCancellingTimerWhenWrappedConnectorReturnsPendingPromiseThatResolves()
  72. {
  73. $timer = $this->getMockBuilder('React\EventLoop\TimerInterface')->getMock();
  74. $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
  75. $loop->expects($this->once())->method('addTimer')->with(5.0, $this->anything())->willReturn($timer);
  76. $loop->expects($this->once())->method('cancelTimer')->with($timer);
  77. $deferred = new Deferred();
  78. $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
  79. $connector->expects($this->once())->method('connect')->with('example.com:80')->willReturn($deferred->promise());
  80. $timeout = new TimeoutConnector($connector, 5.0, $loop);
  81. $promise = $timeout->connect('example.com:80');
  82. $connection = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock();
  83. $deferred->resolve($connection);
  84. $resolved = null;
  85. $promise->then(function ($value) use (&$resolved) {
  86. $resolved = $value;
  87. });
  88. $this->assertSame($connection, $resolved);
  89. }
  90. public function testRejectsPromiseAndCancelsPendingConnectionWhenTimeoutTriggers()
  91. {
  92. $timerCallback = null;
  93. $timer = $this->getMockBuilder('React\EventLoop\TimerInterface')->getMock();
  94. $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
  95. $loop->expects($this->once())->method('addTimer')->with(0.01, $this->callback(function ($callback) use (&$timerCallback) {
  96. $timerCallback = $callback;
  97. return true;
  98. }))->willReturn($timer);
  99. $loop->expects($this->once())->method('cancelTimer')->with($timer);
  100. $cancelled = 0;
  101. $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
  102. $connector->expects($this->once())->method('connect')->with('example.com:80')->willReturn(new Promise(function () { }, function () use (&$cancelled) {
  103. ++$cancelled;
  104. throw new \RuntimeException();
  105. }));
  106. $timeout = new TimeoutConnector($connector, 0.01, $loop);
  107. $promise = $timeout->connect('example.com:80');
  108. $this->assertEquals(0, $cancelled);
  109. $this->assertNotNull($timerCallback);
  110. $timerCallback();
  111. $this->assertEquals(1, $cancelled);
  112. $exception = null;
  113. $promise->then(null, function ($reason) use (&$exception) {
  114. $exception = $reason;
  115. });
  116. assert($exception instanceof \RuntimeException);
  117. $this->assertEquals('Connection to example.com:80 timed out after 0.01 seconds (ETIMEDOUT)' , $exception->getMessage());
  118. $this->assertEquals(\defined('SOCKET_ETIMEDOUT') ? \SOCKET_ETIMEDOUT : 110, $exception->getCode());
  119. }
  120. public function testCancellingPromiseWillCancelPendingConnectionAndRejectPromise()
  121. {
  122. $timer = $this->getMockBuilder('React\EventLoop\TimerInterface')->getMock();
  123. $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
  124. $loop->expects($this->once())->method('addTimer')->with(0.01, $this->anything())->willReturn($timer);
  125. $loop->expects($this->once())->method('cancelTimer')->with($timer);
  126. $cancelled = 0;
  127. $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
  128. $connector->expects($this->once())->method('connect')->with('example.com:80')->willReturn(new Promise(function () { }, function () use (&$cancelled) {
  129. ++$cancelled;
  130. throw new \RuntimeException('Cancelled');
  131. }));
  132. $timeout = new TimeoutConnector($connector, 0.01, $loop);
  133. $promise = $timeout->connect('example.com:80');
  134. $this->assertEquals(0, $cancelled);
  135. assert(method_exists($promise, 'cancel'));
  136. $promise->cancel();
  137. $this->assertEquals(1, $cancelled);
  138. $exception = null;
  139. $promise->then(null, function ($reason) use (&$exception) {
  140. $exception = $reason;
  141. });
  142. assert($exception instanceof \RuntimeException);
  143. $this->assertEquals('Cancelled', $exception->getMessage());
  144. }
  145. public function testRejectionDuringConnectionShouldNotCreateAnyGarbageReferences()
  146. {
  147. if (class_exists('React\Promise\When')) {
  148. $this->markTestSkipped('Not supported on legacy Promise v1 API');
  149. }
  150. while (gc_collect_cycles()) {
  151. // collect all garbage cycles
  152. }
  153. $connection = new Deferred();
  154. $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
  155. $connector->expects($this->once())->method('connect')->with('example.com:80')->willReturn($connection->promise());
  156. $timeout = new TimeoutConnector($connector, 0.01);
  157. $promise = $timeout->connect('example.com:80');
  158. $promise->then(null, $this->expectCallableOnce()); // avoid reporting unhandled rejection
  159. $connection->reject(new \RuntimeException('Connection failed'));
  160. unset($promise, $connection);
  161. $this->assertEquals(0, gc_collect_cycles());
  162. }
  163. public function testRejectionDueToTimeoutShouldNotCreateAnyGarbageReferences()
  164. {
  165. if (class_exists('React\Promise\When')) {
  166. $this->markTestSkipped('Not supported on legacy Promise v1 API');
  167. }
  168. while (gc_collect_cycles()) {
  169. // collect all garbage cycles
  170. }
  171. $connection = new Deferred(function () {
  172. throw new \RuntimeException('Connection cancelled');
  173. });
  174. $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
  175. $connector->expects($this->once())->method('connect')->with('example.com:80')->willReturn($connection->promise());
  176. $timeout = new TimeoutConnector($connector, 0);
  177. $promise = $timeout->connect('example.com:80');
  178. $promise->then(null, $this->expectCallableOnce()); // avoid reporting unhandled rejection
  179. Loop::run();
  180. unset($promise, $connection);
  181. $this->assertEquals(0, gc_collect_cycles());
  182. }
  183. }