BufferTest.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. <?php
  2. namespace React\Tests\Stream;
  3. use React\Stream\Buffer;
  4. class BufferTest extends TestCase
  5. {
  6. /**
  7. * @covers React\Stream\Buffer::__construct
  8. */
  9. public function testConstructor()
  10. {
  11. $stream = fopen('php://temp', 'r+');
  12. $loop = $this->createLoopMock();
  13. $buffer = new Buffer($stream, $loop);
  14. $buffer->on('error', $this->expectCallableNever());
  15. }
  16. /**
  17. * @covers React\Stream\Buffer::__construct
  18. * @expectedException InvalidArgumentException
  19. */
  20. public function testConstructorThrowsIfNotAValidStreamResource()
  21. {
  22. $stream = null;
  23. $loop = $this->createLoopMock();
  24. new Buffer($stream, $loop);
  25. }
  26. /**
  27. * @covers React\Stream\Buffer::write
  28. * @covers React\Stream\Buffer::handleWrite
  29. */
  30. public function testWrite()
  31. {
  32. $stream = fopen('php://temp', 'r+');
  33. $loop = $this->createWriteableLoopMock();
  34. $buffer = new Buffer($stream, $loop);
  35. $buffer->on('error', $this->expectCallableNever());
  36. $buffer->write("foobar\n");
  37. rewind($stream);
  38. $this->assertSame("foobar\n", fread($stream, 1024));
  39. }
  40. /**
  41. * @covers React\Stream\Buffer::write
  42. */
  43. public function testWriteWithDataDoesAddResourceToLoop()
  44. {
  45. $stream = fopen('php://temp', 'r+');
  46. $loop = $this->createLoopMock();
  47. $loop->expects($this->once())->method('addWriteStream')->with($this->equalTo($stream));
  48. $buffer = new Buffer($stream, $loop);
  49. $buffer->write("foobar\n");
  50. }
  51. /**
  52. * @covers React\Stream\Buffer::write
  53. * @covers React\Stream\Buffer::handleWrite
  54. */
  55. public function testEmptyWriteDoesNotAddToLoop()
  56. {
  57. $stream = fopen('php://temp', 'r+');
  58. $loop = $this->createLoopMock();
  59. $loop->expects($this->never())->method('addWriteStream');
  60. $buffer = new Buffer($stream, $loop);
  61. $buffer->write("");
  62. $buffer->write(null);
  63. }
  64. /**
  65. * @covers React\Stream\Buffer::write
  66. */
  67. public function testWriteWillAddStreamToLoop()
  68. {
  69. $stream = fopen('php://temp', 'r+');
  70. $loop = $this->createLoopMock();
  71. $buffer = new Buffer($stream, $loop);
  72. $loop->expects($this->once())->method('addWriteStream')->with($stream);
  73. $buffer->write('foo');
  74. }
  75. /**
  76. * @covers React\Stream\Buffer::write
  77. * @covers React\Stream\Buffer::handleWrite
  78. */
  79. public function testWriteReturnsFalseWhenBufferIsFull()
  80. {
  81. $stream = fopen('php://temp', 'r+');
  82. $loop = $this->createWriteableLoopMock();
  83. $loop->preventWrites = true;
  84. $buffer = new Buffer($stream, $loop);
  85. $buffer->softLimit = 4;
  86. $buffer->on('error', $this->expectCallableNever());
  87. $this->assertTrue($buffer->write("foo"));
  88. $loop->preventWrites = false;
  89. $this->assertFalse($buffer->write("bar\n"));
  90. }
  91. /**
  92. * @covers React\Stream\Buffer::write
  93. */
  94. public function testWriteReturnsFalseWhenBufferIsExactlyFull()
  95. {
  96. $stream = fopen('php://temp', 'r+');
  97. $loop = $this->createLoopMock();
  98. $buffer = new Buffer($stream, $loop);
  99. $buffer->softLimit = 3;
  100. $this->assertFalse($buffer->write("foo"));
  101. }
  102. /**
  103. * @covers React\Stream\Buffer::write
  104. * @covers React\Stream\Buffer::handleWrite
  105. */
  106. public function testWriteEmitsErrorWhenResourceIsNotWritable()
  107. {
  108. if (defined('HHVM_VERSION')) {
  109. // via https://github.com/reactphp/stream/pull/52/files#r75493076
  110. $this->markTestSkipped('HHVM allows writing to read-only memory streams');
  111. }
  112. $stream = fopen('php://temp', 'r');
  113. $loop = $this->createLoopMock();
  114. $buffer = new Buffer($stream, $loop);
  115. $buffer->on('error', $this->expectCallableOnce());
  116. //$buffer->on('close', $this->expectCallableOnce());
  117. $buffer->write('hello');
  118. $buffer->handleWrite();
  119. }
  120. /**
  121. * @covers React\Stream\Buffer::write
  122. * @covers React\Stream\Buffer::handleWrite
  123. */
  124. public function testWriteDetectsWhenOtherSideIsClosed()
  125. {
  126. list($a, $b) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
  127. $loop = $this->createWriteableLoopMock();
  128. $buffer = new Buffer($a, $loop);
  129. $buffer->softLimit = 4;
  130. $buffer->on('error', $this->expectCallableOnce());
  131. fclose($b);
  132. $buffer->write("foo");
  133. }
  134. /**
  135. * @covers React\Stream\Buffer::write
  136. * @covers React\Stream\Buffer::handleWrite
  137. */
  138. public function testDrain()
  139. {
  140. $stream = fopen('php://temp', 'r+');
  141. $loop = $this->createWriteableLoopMock();
  142. $loop->preventWrites = true;
  143. $buffer = new Buffer($stream, $loop);
  144. $buffer->softLimit = 4;
  145. $buffer->on('error', $this->expectCallableNever());
  146. $buffer->on('drain', $this->expectCallableOnce());
  147. $buffer->write("foo");
  148. $loop->preventWrites = false;
  149. $buffer->listening = false;
  150. $buffer->write("bar\n");
  151. }
  152. /**
  153. * @covers React\Stream\Buffer::write
  154. * @covers React\Stream\Buffer::handleWrite
  155. */
  156. public function testWriteInDrain()
  157. {
  158. $stream = fopen('php://temp', 'r+');
  159. $loop = $this->createWriteableLoopMock();
  160. $loop->preventWrites = true;
  161. $buffer = new Buffer($stream, $loop);
  162. $buffer->softLimit = 2;
  163. $buffer->on('error', $this->expectCallableNever());
  164. $buffer->once('drain', function ($buffer) {
  165. $buffer->listening = false;
  166. $buffer->write("bar\n");
  167. });
  168. $this->assertFalse($buffer->write("foo"));
  169. $loop->preventWrites = false;
  170. $buffer->listening = false;
  171. $buffer->write("\n");
  172. fseek($stream, 0);
  173. $this->assertSame("foo\nbar\n", stream_get_contents($stream));
  174. }
  175. /**
  176. * @covers React\Stream\Buffer::write
  177. * @covers React\Stream\Buffer::handleWrite
  178. */
  179. public function testDrainAndFullDrainAfterWrite()
  180. {
  181. $stream = fopen('php://temp', 'r+');
  182. $loop = $this->createLoopMock();
  183. $buffer = new Buffer($stream, $loop);
  184. $buffer->softLimit = 2;
  185. $buffer->on('drain', $this->expectCallableOnce());
  186. $buffer->on('full-drain', $this->expectCallableOnce());
  187. $buffer->write("foo");
  188. $buffer->handleWrite();
  189. }
  190. /**
  191. * @covers React\Stream\Buffer::write
  192. * @covers React\Stream\Buffer::handleWrite
  193. */
  194. public function testCloseDuringDrainWillNotEmitFullDrain()
  195. {
  196. $stream = fopen('php://temp', 'r+');
  197. $loop = $this->createLoopMock();
  198. $buffer = new Buffer($stream, $loop);
  199. $buffer->softLimit = 2;
  200. // close buffer on drain event => expect close event, but no full-drain after
  201. $buffer->on('drain', $this->expectCallableOnce());
  202. $buffer->on('drain', array($buffer, 'close'));
  203. $buffer->on('close', $this->expectCallableOnce());
  204. $buffer->on('full-drain', $this->expectCallableNever());
  205. $buffer->write("foo");
  206. $buffer->handleWrite();
  207. }
  208. /**
  209. * @covers React\Stream\Buffer::end
  210. */
  211. public function testEnd()
  212. {
  213. $stream = fopen('php://temp', 'r+');
  214. $loop = $this->createLoopMock();
  215. $buffer = new Buffer($stream, $loop);
  216. $buffer->on('error', $this->expectCallableNever());
  217. $buffer->on('close', $this->expectCallableOnce());
  218. $this->assertTrue($buffer->isWritable());
  219. $buffer->end();
  220. $this->assertFalse($buffer->isWritable());
  221. }
  222. /**
  223. * @covers React\Stream\Buffer::end
  224. */
  225. public function testEndWithData()
  226. {
  227. $stream = fopen('php://temp', 'r+');
  228. $loop = $this->createWriteableLoopMock();
  229. $buffer = new Buffer($stream, $loop);
  230. $buffer->on('error', $this->expectCallableNever());
  231. $buffer->on('close', $this->expectCallableOnce());
  232. $buffer->end('final words');
  233. rewind($stream);
  234. $this->assertSame('final words', stream_get_contents($stream));
  235. }
  236. /**
  237. * @covers React\Stream\Buffer::isWritable
  238. * @covers React\Stream\Buffer::close
  239. */
  240. public function testClose()
  241. {
  242. $stream = fopen('php://temp', 'r+');
  243. $loop = $this->createLoopMock();
  244. $buffer = new Buffer($stream, $loop);
  245. $buffer->on('error', $this->expectCallableNever());
  246. $buffer->on('close', $this->expectCallableOnce());
  247. $this->assertTrue($buffer->isWritable());
  248. $buffer->close();
  249. $this->assertFalse($buffer->isWritable());
  250. $this->assertEquals(array(), $buffer->listeners('close'));
  251. }
  252. /**
  253. * @covers React\Stream\Buffer::close
  254. */
  255. public function testClosingAfterWriteRemovesStreamFromLoop()
  256. {
  257. $stream = fopen('php://temp', 'r+');
  258. $loop = $this->createLoopMock();
  259. $buffer = new Buffer($stream, $loop);
  260. $loop->expects($this->once())->method('removeWriteStream')->with($stream);
  261. $buffer->write('foo');
  262. $buffer->close();
  263. }
  264. /**
  265. * @covers React\Stream\Buffer::close
  266. */
  267. public function testClosingWithoutWritingDoesNotRemoveStreamFromLoop()
  268. {
  269. $stream = fopen('php://temp', 'r+');
  270. $loop = $this->createLoopMock();
  271. $buffer = new Buffer($stream, $loop);
  272. $loop->expects($this->never())->method('removeWriteStream');
  273. $buffer->close();
  274. }
  275. /**
  276. * @covers React\Stream\Buffer::close
  277. */
  278. public function testDoubleCloseWillEmitOnlyOnce()
  279. {
  280. $stream = fopen('php://temp', 'r+');
  281. $loop = $this->createLoopMock();
  282. $buffer = new Buffer($stream, $loop);
  283. $buffer->on('close', $this->expectCallableOnce());
  284. $buffer->close();
  285. $buffer->close();
  286. }
  287. /**
  288. * @covers React\Stream\Buffer::write
  289. * @covers React\Stream\Buffer::close
  290. */
  291. public function testWritingToClosedBufferShouldNotWriteToStream()
  292. {
  293. $stream = fopen('php://temp', 'r+');
  294. $loop = $this->createWriteableLoopMock();
  295. $buffer = new Buffer($stream, $loop);
  296. $buffer->close();
  297. $buffer->write('foo');
  298. rewind($stream);
  299. $this->assertSame('', stream_get_contents($stream));
  300. }
  301. /**
  302. * @covers React\Stream\Buffer::handleWrite
  303. */
  304. public function testErrorWhenStreamResourceIsInvalid()
  305. {
  306. $stream = fopen('php://temp', 'r+');
  307. $loop = $this->createWriteableLoopMock();
  308. $error = null;
  309. $buffer = new Buffer($stream, $loop);
  310. $buffer->on('error', function ($message) use (&$error) {
  311. $error = $message;
  312. });
  313. // invalidate stream resource
  314. fclose($stream);
  315. $buffer->write('Attempting to write to bad stream');
  316. $this->assertInstanceOf('Exception', $error);
  317. // the error messages differ between PHP versions, let's just check substrings
  318. $this->assertContains('Unable to write to stream: ', $error->getMessage());
  319. $this->assertContains(' not a valid stream resource', $error->getMessage(), '', true);
  320. }
  321. public function testWritingToClosedStream()
  322. {
  323. if ('Darwin' === PHP_OS) {
  324. $this->markTestSkipped('OS X issue with shutting down pair for writing');
  325. }
  326. list($a, $b) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
  327. $loop = $this->createWriteableLoopMock();
  328. $error = null;
  329. $buffer = new Buffer($a, $loop);
  330. $buffer->on('error', function($message) use (&$error) {
  331. $error = $message;
  332. });
  333. $buffer->write('foo');
  334. stream_socket_shutdown($b, STREAM_SHUT_RD);
  335. stream_socket_shutdown($a, STREAM_SHUT_RD);
  336. $buffer->write('bar');
  337. $this->assertInstanceOf('Exception', $error);
  338. $this->assertSame('Unable to write to stream: fwrite(): send of 3 bytes failed with errno=32 Broken pipe', $error->getMessage());
  339. }
  340. private function createWriteableLoopMock()
  341. {
  342. $loop = $this->createLoopMock();
  343. $loop->preventWrites = false;
  344. $loop
  345. ->expects($this->any())
  346. ->method('addWriteStream')
  347. ->will($this->returnCallback(function ($stream, $listener) use ($loop) {
  348. if (!$loop->preventWrites) {
  349. call_user_func($listener, $stream);
  350. }
  351. }));
  352. return $loop;
  353. }
  354. private function createLoopMock()
  355. {
  356. return $this->getMock('React\EventLoop\LoopInterface');
  357. }
  358. }