FilterTest.php 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. <?php
  2. use Clue\StreamFilter;
  3. class FilterTest extends PHPUnit_Framework_TestCase
  4. {
  5. public function testAppendSimpleCallback()
  6. {
  7. $stream = $this->createStream();
  8. StreamFilter\append($stream, function ($chunk) {
  9. return strtoupper($chunk);
  10. });
  11. fwrite($stream, 'hello');
  12. fwrite($stream, 'world');
  13. rewind($stream);
  14. $this->assertEquals('HELLOWORLD', stream_get_contents($stream));
  15. fclose($stream);
  16. }
  17. public function testAppendNativePhpFunction()
  18. {
  19. $stream = $this->createStream();
  20. StreamFilter\append($stream, 'strtoupper');
  21. fwrite($stream, 'hello');
  22. fwrite($stream, 'world');
  23. rewind($stream);
  24. $this->assertEquals('HELLOWORLD', stream_get_contents($stream));
  25. fclose($stream);
  26. }
  27. public function testAppendChangingChunkSize()
  28. {
  29. $stream = $this->createStream();
  30. StreamFilter\append($stream, function ($chunk) {
  31. return str_replace(array('a','e','i','o','u'), '', $chunk);
  32. });
  33. fwrite($stream, 'hello');
  34. fwrite($stream, 'world');
  35. rewind($stream);
  36. $this->assertEquals('hllwrld', stream_get_contents($stream));
  37. fclose($stream);
  38. }
  39. public function testAppendReturningEmptyStringWillNotPassThrough()
  40. {
  41. $stream = $this->createStream();
  42. StreamFilter\append($stream, function ($chunk) {
  43. return '';
  44. });
  45. fwrite($stream, 'hello');
  46. fwrite($stream, 'world');
  47. rewind($stream);
  48. $this->assertEquals('', stream_get_contents($stream));
  49. fclose($stream);
  50. }
  51. public function testAppendEndEventCanBeBufferedOnClose()
  52. {
  53. if (PHP_VERSION < 5.4) $this->markTestSkipped('Not supported on legacy PHP');
  54. $stream = $this->createStream();
  55. StreamFilter\append($stream, function ($chunk = null) {
  56. if ($chunk === null) {
  57. // this signals the end event
  58. return '!';
  59. }
  60. return $chunk . ' ';
  61. }, STREAM_FILTER_WRITE);
  62. $buffered = '';
  63. StreamFilter\append($stream, function ($chunk) use (&$buffered) {
  64. $buffered .= $chunk;
  65. return '';
  66. });
  67. fwrite($stream, 'hello');
  68. fwrite($stream, 'world');
  69. fclose($stream);
  70. $this->assertEquals('hello world !', $buffered);
  71. }
  72. public function testAppendEndEventWillBeCalledOnRemove()
  73. {
  74. $stream = $this->createStream();
  75. $ended = false;
  76. $filter = StreamFilter\append($stream, function ($chunk = null) use (&$ended) {
  77. if ($chunk === null) {
  78. $ended = true;
  79. }
  80. return $chunk;
  81. }, STREAM_FILTER_WRITE);
  82. $this->assertEquals(0, $ended);
  83. StreamFilter\remove($filter);
  84. $this->assertEquals(1, $ended);
  85. }
  86. public function testAppendEndEventWillBeCalledOnClose()
  87. {
  88. $stream = $this->createStream();
  89. $ended = false;
  90. StreamFilter\append($stream, function ($chunk = null) use (&$ended) {
  91. if ($chunk === null) {
  92. $ended = true;
  93. }
  94. return $chunk;
  95. }, STREAM_FILTER_WRITE);
  96. $this->assertEquals(0, $ended);
  97. fclose($stream);
  98. $this->assertEquals(1, $ended);
  99. }
  100. public function testAppendWriteOnly()
  101. {
  102. $stream = $this->createStream();
  103. $invoked = 0;
  104. StreamFilter\append($stream, function ($chunk) use (&$invoked) {
  105. ++$invoked;
  106. return $chunk;
  107. }, STREAM_FILTER_WRITE);
  108. fwrite($stream, 'a');
  109. fwrite($stream, 'b');
  110. fwrite($stream, 'c');
  111. rewind($stream);
  112. $this->assertEquals(3, $invoked);
  113. $this->assertEquals('abc', stream_get_contents($stream));
  114. fclose($stream);
  115. }
  116. public function testAppendReadOnly()
  117. {
  118. $stream = $this->createStream();
  119. $invoked = 0;
  120. StreamFilter\append($stream, function ($chunk) use (&$invoked) {
  121. ++$invoked;
  122. return $chunk;
  123. }, STREAM_FILTER_READ);
  124. fwrite($stream, 'a');
  125. fwrite($stream, 'b');
  126. fwrite($stream, 'c');
  127. rewind($stream);
  128. $this->assertEquals(0, $invoked);
  129. $this->assertEquals('abc', stream_get_contents($stream));
  130. $this->assertEquals(1, $invoked);
  131. fclose($stream);
  132. }
  133. public function testOrderCallingAppendAfterPrepend()
  134. {
  135. $stream = $this->createStream();
  136. StreamFilter\append($stream, function ($chunk) {
  137. return '[' . $chunk . ']';
  138. }, STREAM_FILTER_WRITE);
  139. StreamFilter\prepend($stream, function ($chunk) {
  140. return '(' . $chunk . ')';
  141. }, STREAM_FILTER_WRITE);
  142. fwrite($stream, 'hello');
  143. rewind($stream);
  144. $this->assertEquals('[(hello)]', stream_get_contents($stream));
  145. fclose($stream);
  146. }
  147. public function testRemoveFilter()
  148. {
  149. $stream = $this->createStream();
  150. $first = StreamFilter\append($stream, function ($chunk) {
  151. return $chunk . '?';
  152. }, STREAM_FILTER_WRITE);
  153. StreamFilter\append($stream, function ($chunk) {
  154. return $chunk . '!';
  155. }, STREAM_FILTER_WRITE);
  156. StreamFilter\remove($first);
  157. fwrite($stream, 'hello');
  158. rewind($stream);
  159. $this->assertEquals('hello!', stream_get_contents($stream));
  160. fclose($stream);
  161. }
  162. public function testAppendFunDechunk()
  163. {
  164. if (defined('HHVM_VERSION')) $this->markTestSkipped('Not supported on HHVM (dechunk filter does not exist)');
  165. $stream = $this->createStream();
  166. StreamFilter\append($stream, StreamFilter\fun('dechunk'), STREAM_FILTER_WRITE);
  167. fwrite($stream, "2\r\nhe\r\n");
  168. fwrite($stream, "3\r\nllo\r\n");
  169. fwrite($stream, "0\r\n\r\n");
  170. rewind($stream);
  171. $this->assertEquals('hello', stream_get_contents($stream));
  172. fclose($stream);
  173. }
  174. public function testAppendThrows()
  175. {
  176. $this->createErrorHandler($errors);
  177. $stream = $this->createStream();
  178. $this->createErrorHandler($errors);
  179. StreamFilter\append($stream, function ($chunk) {
  180. throw new \DomainException($chunk);
  181. });
  182. fwrite($stream, 'test');
  183. $this->removeErrorHandler();
  184. $this->assertCount(1, $errors);
  185. $this->assertContains('test', $errors[0]);
  186. }
  187. public function testAppendThrowsDuringEnd()
  188. {
  189. $stream = $this->createStream();
  190. $this->createErrorHandler($errors);
  191. StreamFilter\append($stream, function ($chunk = null) {
  192. if ($chunk === null) {
  193. throw new \DomainException('end');
  194. }
  195. return $chunk;
  196. });
  197. fclose($stream);
  198. $this->removeErrorHandler();
  199. // We can only assert we're not seeing an exception here…
  200. // * php 5.3-5.6 sees one error here
  201. // * php 7 does not see any error here
  202. // * hhvm sees the same error twice
  203. //
  204. // If you're curious:
  205. //
  206. // var_dump($errors);
  207. // $this->assertCount(1, $errors);
  208. // $this->assertContains('end', $errors[0]);
  209. }
  210. public function testAppendThrowsShouldTriggerEnd()
  211. {
  212. $stream = $this->createStream();
  213. $this->createErrorHandler($errors);
  214. $ended = false;
  215. StreamFilter\append($stream, function ($chunk = null) use (&$ended) {
  216. if ($chunk === null) {
  217. $ended = true;
  218. return '';
  219. }
  220. throw new \DomainException($chunk);
  221. });
  222. $this->assertEquals(false, $ended);
  223. fwrite($stream, 'test');
  224. $this->assertEquals(true, $ended);
  225. $this->removeErrorHandler();
  226. $this->assertCount(1, $errors);
  227. $this->assertContains('test', $errors[0]);
  228. }
  229. public function testAppendThrowsShouldTriggerEndButIgnoreExceptionDuringEnd()
  230. {
  231. //$this->markTestIncomplete();
  232. $stream = $this->createStream();
  233. $this->createErrorHandler($errors);
  234. StreamFilter\append($stream, function ($chunk = null) {
  235. if ($chunk === null) {
  236. $chunk = 'end';
  237. //return '';
  238. }
  239. throw new \DomainException($chunk);
  240. });
  241. fwrite($stream, 'test');
  242. $this->removeErrorHandler();
  243. $this->assertCount(1, $errors);
  244. $this->assertContains('test', $errors[0]);
  245. }
  246. /**
  247. * @expectedException RuntimeException
  248. */
  249. public function testAppendInvalidStreamIsRuntimeError()
  250. {
  251. if (defined('HHVM_VERSION')) $this->markTestSkipped('Not supported on HHVM (does not reject invalid stream)');
  252. StreamFilter\append(false, function () { });
  253. }
  254. /**
  255. * @expectedException RuntimeException
  256. */
  257. public function testPrependInvalidStreamIsRuntimeError()
  258. {
  259. if (defined('HHVM_VERSION')) $this->markTestSkipped('Not supported on HHVM (does not reject invalid stream)');
  260. StreamFilter\prepend(false, function () { });
  261. }
  262. /**
  263. * @expectedException RuntimeException
  264. */
  265. public function testRemoveInvalidFilterIsRuntimeError()
  266. {
  267. if (defined('HHVM_VERSION')) $this->markTestSkipped('Not supported on HHVM (does not reject invalid filters)');
  268. StreamFilter\remove(false);
  269. }
  270. /**
  271. * @expectedException InvalidArgumentException
  272. */
  273. public function testInvalidCallbackIsInvalidArgument()
  274. {
  275. $stream = $this->createStream();
  276. StreamFilter\append($stream, 'a-b-c');
  277. }
  278. private function createStream()
  279. {
  280. return fopen('php://memory', 'r+');
  281. }
  282. private function createErrorHandler(&$errors)
  283. {
  284. $errors = array();
  285. set_error_handler(function ($_, $message) use (&$errors) {
  286. $errors []= $message;
  287. });
  288. }
  289. private function removeErrorHandler()
  290. {
  291. restore_error_handler();
  292. }
  293. }