AbstractLoopTest.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. <?php
  2. namespace React\Tests\EventLoop;
  3. abstract class AbstractLoopTest extends TestCase
  4. {
  5. /**
  6. * @var \React\EventLoop\LoopInterface
  7. */
  8. protected $loop;
  9. private $tickTimeout;
  10. public function setUp()
  11. {
  12. // HHVM is a bit slow, so give it more time
  13. $this->tickTimeout = defined('HHVM_VERSION') ? 0.02 : 0.005;
  14. $this->loop = $this->createLoop();
  15. }
  16. abstract public function createLoop();
  17. public function createSocketPair()
  18. {
  19. $domain = (DIRECTORY_SEPARATOR === '\\') ? STREAM_PF_INET : STREAM_PF_UNIX;
  20. $sockets = stream_socket_pair($domain, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
  21. foreach ($sockets as $socket) {
  22. if (function_exists('stream_set_read_buffer')) {
  23. stream_set_read_buffer($socket, 0);
  24. }
  25. }
  26. return $sockets;
  27. }
  28. public function testAddReadStream()
  29. {
  30. list ($input, $output) = $this->createSocketPair();
  31. $this->loop->addReadStream($input, $this->expectCallableExactly(2));
  32. fwrite($output, "foo\n");
  33. $this->loop->tick();
  34. fwrite($output, "bar\n");
  35. $this->loop->tick();
  36. }
  37. public function testAddReadStreamIgnoresSecondCallable()
  38. {
  39. list ($input, $output) = $this->createSocketPair();
  40. $this->loop->addReadStream($input, $this->expectCallableExactly(2));
  41. $this->loop->addReadStream($input, $this->expectCallableNever());
  42. fwrite($output, "foo\n");
  43. $this->loop->tick();
  44. fwrite($output, "bar\n");
  45. $this->loop->tick();
  46. }
  47. public function testAddWriteStream()
  48. {
  49. list ($input) = $this->createSocketPair();
  50. $this->loop->addWriteStream($input, $this->expectCallableExactly(2));
  51. $this->loop->tick();
  52. $this->loop->tick();
  53. }
  54. public function testAddWriteStreamIgnoresSecondCallable()
  55. {
  56. list ($input) = $this->createSocketPair();
  57. $this->loop->addWriteStream($input, $this->expectCallableExactly(2));
  58. $this->loop->addWriteStream($input, $this->expectCallableNever());
  59. $this->loop->tick();
  60. $this->loop->tick();
  61. }
  62. public function testRemoveReadStreamInstantly()
  63. {
  64. list ($input, $output) = $this->createSocketPair();
  65. $this->loop->addReadStream($input, $this->expectCallableNever());
  66. $this->loop->removeReadStream($input);
  67. fwrite($output, "bar\n");
  68. $this->loop->tick();
  69. }
  70. public function testRemoveReadStreamAfterReading()
  71. {
  72. list ($input, $output) = $this->createSocketPair();
  73. $this->loop->addReadStream($input, $this->expectCallableOnce());
  74. fwrite($output, "foo\n");
  75. $this->loop->tick();
  76. $this->loop->removeReadStream($input);
  77. fwrite($output, "bar\n");
  78. $this->loop->tick();
  79. }
  80. public function testRemoveWriteStreamInstantly()
  81. {
  82. list ($input) = $this->createSocketPair();
  83. $this->loop->addWriteStream($input, $this->expectCallableNever());
  84. $this->loop->removeWriteStream($input);
  85. $this->loop->tick();
  86. }
  87. public function testRemoveWriteStreamAfterWriting()
  88. {
  89. list ($input) = $this->createSocketPair();
  90. $this->loop->addWriteStream($input, $this->expectCallableOnce());
  91. $this->loop->tick();
  92. $this->loop->removeWriteStream($input);
  93. $this->loop->tick();
  94. }
  95. public function testRemoveStreamInstantly()
  96. {
  97. list ($input, $output) = $this->createSocketPair();
  98. $this->loop->addReadStream($input, $this->expectCallableNever());
  99. $this->loop->addWriteStream($input, $this->expectCallableNever());
  100. $this->loop->removeStream($input);
  101. fwrite($output, "bar\n");
  102. $this->loop->tick();
  103. }
  104. public function testRemoveStreamForReadOnly()
  105. {
  106. list ($input, $output) = $this->createSocketPair();
  107. $this->loop->addReadStream($input, $this->expectCallableNever());
  108. $this->loop->addWriteStream($output, $this->expectCallableOnce());
  109. $this->loop->removeReadStream($input);
  110. fwrite($output, "foo\n");
  111. $this->loop->tick();
  112. }
  113. public function testRemoveStreamForWriteOnly()
  114. {
  115. list ($input, $output) = $this->createSocketPair();
  116. fwrite($output, "foo\n");
  117. $this->loop->addReadStream($input, $this->expectCallableOnce());
  118. $this->loop->addWriteStream($output, $this->expectCallableNever());
  119. $this->loop->removeWriteStream($output);
  120. $this->loop->tick();
  121. }
  122. public function testRemoveStream()
  123. {
  124. list ($input, $output) = $this->createSocketPair();
  125. $this->loop->addReadStream($input, $this->expectCallableOnce());
  126. $this->loop->addWriteStream($input, $this->expectCallableOnce());
  127. fwrite($output, "bar\n");
  128. $this->loop->tick();
  129. $this->loop->removeStream($input);
  130. fwrite($output, "bar\n");
  131. $this->loop->tick();
  132. }
  133. public function testRemoveInvalid()
  134. {
  135. list ($stream) = $this->createSocketPair();
  136. // remove a valid stream from the event loop that was never added in the first place
  137. $this->loop->removeReadStream($stream);
  138. $this->loop->removeWriteStream($stream);
  139. $this->loop->removeStream($stream);
  140. }
  141. /** @test */
  142. public function emptyRunShouldSimplyReturn()
  143. {
  144. $this->assertRunFasterThan($this->tickTimeout);
  145. }
  146. /** @test */
  147. public function runShouldReturnWhenNoMoreFds()
  148. {
  149. list ($input, $output) = $this->createSocketPair();
  150. $loop = $this->loop;
  151. $this->loop->addReadStream($input, function ($stream) use ($loop) {
  152. $loop->removeStream($stream);
  153. });
  154. fwrite($output, "foo\n");
  155. $this->assertRunFasterThan($this->tickTimeout * 2);
  156. }
  157. /** @test */
  158. public function stopShouldStopRunningLoop()
  159. {
  160. list ($input, $output) = $this->createSocketPair();
  161. $loop = $this->loop;
  162. $this->loop->addReadStream($input, function ($stream) use ($loop) {
  163. $loop->stop();
  164. });
  165. fwrite($output, "foo\n");
  166. $this->assertRunFasterThan($this->tickTimeout * 2);
  167. }
  168. public function testStopShouldPreventRunFromBlocking()
  169. {
  170. $this->loop->addTimer(
  171. 1,
  172. function () {
  173. $this->fail('Timer was executed.');
  174. }
  175. );
  176. $this->loop->nextTick(
  177. function () {
  178. $this->loop->stop();
  179. }
  180. );
  181. $this->assertRunFasterThan($this->tickTimeout * 2);
  182. }
  183. public function testIgnoreRemovedCallback()
  184. {
  185. // two independent streams, both should be readable right away
  186. list ($input1, $output1) = $this->createSocketPair();
  187. list ($input2, $output2) = $this->createSocketPair();
  188. $called = false;
  189. $loop = $this->loop;
  190. $loop->addReadStream($input1, function ($stream) use (& $called, $loop, $input2) {
  191. // stream1 is readable, remove stream2 as well => this will invalidate its callback
  192. $loop->removeReadStream($stream);
  193. $loop->removeReadStream($input2);
  194. $called = true;
  195. });
  196. // this callback would have to be called as well, but the first stream already removed us
  197. $loop->addReadStream($input2, function () use (& $called) {
  198. if ($called) {
  199. $this->fail('Callback 2 must not be called after callback 1 was called');
  200. }
  201. });
  202. fwrite($output1, "foo\n");
  203. fwrite($output2, "foo\n");
  204. $loop->run();
  205. $this->assertTrue($called);
  206. }
  207. public function testNextTick()
  208. {
  209. $called = false;
  210. $callback = function ($loop) use (&$called) {
  211. $this->assertSame($this->loop, $loop);
  212. $called = true;
  213. };
  214. $this->loop->nextTick($callback);
  215. $this->assertFalse($called);
  216. $this->loop->tick();
  217. $this->assertTrue($called);
  218. }
  219. public function testNextTickFiresBeforeIO()
  220. {
  221. list ($stream) = $this->createSocketPair();
  222. $this->loop->addWriteStream(
  223. $stream,
  224. function () {
  225. echo 'stream' . PHP_EOL;
  226. }
  227. );
  228. $this->loop->nextTick(
  229. function () {
  230. echo 'next-tick' . PHP_EOL;
  231. }
  232. );
  233. $this->expectOutputString('next-tick' . PHP_EOL . 'stream' . PHP_EOL);
  234. $this->loop->tick();
  235. }
  236. public function testRecursiveNextTick()
  237. {
  238. list ($stream) = $this->createSocketPair();
  239. $this->loop->addWriteStream(
  240. $stream,
  241. function () {
  242. echo 'stream' . PHP_EOL;
  243. }
  244. );
  245. $this->loop->nextTick(
  246. function () {
  247. $this->loop->nextTick(
  248. function () {
  249. echo 'next-tick' . PHP_EOL;
  250. }
  251. );
  252. }
  253. );
  254. $this->expectOutputString('next-tick' . PHP_EOL . 'stream' . PHP_EOL);
  255. $this->loop->tick();
  256. }
  257. public function testRunWaitsForNextTickEvents()
  258. {
  259. list ($stream) = $this->createSocketPair();
  260. $this->loop->addWriteStream(
  261. $stream,
  262. function () use ($stream) {
  263. $this->loop->removeStream($stream);
  264. $this->loop->nextTick(
  265. function () {
  266. echo 'next-tick' . PHP_EOL;
  267. }
  268. );
  269. }
  270. );
  271. $this->expectOutputString('next-tick' . PHP_EOL);
  272. $this->loop->run();
  273. }
  274. public function testNextTickEventGeneratedByFutureTick()
  275. {
  276. list ($stream) = $this->createSocketPair();
  277. $this->loop->futureTick(
  278. function () {
  279. $this->loop->nextTick(
  280. function () {
  281. echo 'next-tick' . PHP_EOL;
  282. }
  283. );
  284. }
  285. );
  286. $this->expectOutputString('next-tick' . PHP_EOL);
  287. $this->loop->run();
  288. }
  289. public function testNextTickEventGeneratedByTimer()
  290. {
  291. $this->loop->addTimer(
  292. 0.001,
  293. function () {
  294. $this->loop->nextTick(
  295. function () {
  296. echo 'next-tick' . PHP_EOL;
  297. }
  298. );
  299. }
  300. );
  301. $this->expectOutputString('next-tick' . PHP_EOL);
  302. $this->loop->run();
  303. }
  304. public function testFutureTick()
  305. {
  306. $called = false;
  307. $callback = function ($loop) use (&$called) {
  308. $this->assertSame($this->loop, $loop);
  309. $called = true;
  310. };
  311. $this->loop->futureTick($callback);
  312. $this->assertFalse($called);
  313. $this->loop->tick();
  314. $this->assertTrue($called);
  315. }
  316. public function testFutureTickFiresBeforeIO()
  317. {
  318. list ($stream) = $this->createSocketPair();
  319. $this->loop->addWriteStream(
  320. $stream,
  321. function () {
  322. echo 'stream' . PHP_EOL;
  323. }
  324. );
  325. $this->loop->futureTick(
  326. function () {
  327. echo 'future-tick' . PHP_EOL;
  328. }
  329. );
  330. $this->expectOutputString('future-tick' . PHP_EOL . 'stream' . PHP_EOL);
  331. $this->loop->tick();
  332. }
  333. public function testRecursiveFutureTick()
  334. {
  335. list ($stream) = $this->createSocketPair();
  336. $this->loop->addWriteStream(
  337. $stream,
  338. function () use ($stream) {
  339. echo 'stream' . PHP_EOL;
  340. $this->loop->removeWriteStream($stream);
  341. }
  342. );
  343. $this->loop->futureTick(
  344. function () {
  345. echo 'future-tick-1' . PHP_EOL;
  346. $this->loop->futureTick(
  347. function () {
  348. echo 'future-tick-2' . PHP_EOL;
  349. }
  350. );
  351. }
  352. );
  353. $this->expectOutputString('future-tick-1' . PHP_EOL . 'stream' . PHP_EOL . 'future-tick-2' . PHP_EOL);
  354. $this->loop->run();
  355. }
  356. public function testRunWaitsForFutureTickEvents()
  357. {
  358. list ($stream) = $this->createSocketPair();
  359. $this->loop->addWriteStream(
  360. $stream,
  361. function () use ($stream) {
  362. $this->loop->removeStream($stream);
  363. $this->loop->futureTick(
  364. function () {
  365. echo 'future-tick' . PHP_EOL;
  366. }
  367. );
  368. }
  369. );
  370. $this->expectOutputString('future-tick' . PHP_EOL);
  371. $this->loop->run();
  372. }
  373. public function testFutureTickEventGeneratedByNextTick()
  374. {
  375. list ($stream) = $this->createSocketPair();
  376. $this->loop->nextTick(
  377. function () {
  378. $this->loop->futureTick(
  379. function () {
  380. echo 'future-tick' . PHP_EOL;
  381. }
  382. );
  383. }
  384. );
  385. $this->expectOutputString('future-tick' . PHP_EOL);
  386. $this->loop->run();
  387. }
  388. public function testFutureTickEventGeneratedByTimer()
  389. {
  390. $this->loop->addTimer(
  391. 0.001,
  392. function () {
  393. $this->loop->futureTick(
  394. function () {
  395. echo 'future-tick' . PHP_EOL;
  396. }
  397. );
  398. }
  399. );
  400. $this->expectOutputString('future-tick' . PHP_EOL);
  401. $this->loop->run();
  402. }
  403. private function assertRunFasterThan($maxInterval)
  404. {
  405. $start = microtime(true);
  406. $this->loop->run();
  407. $end = microtime(true);
  408. $interval = $end - $start;
  409. $this->assertLessThan($maxInterval, $interval);
  410. }
  411. }