PipelineTest.php 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. <?php
  2. namespace think\tests;
  3. use Closure;
  4. use Exception;
  5. use PHPUnit\Framework\TestCase;
  6. use think\Pipeline;
  7. class PipelineTest extends TestCase
  8. {
  9. protected $pipeline;
  10. protected function setUp(): void
  11. {
  12. $this->pipeline = new Pipeline();
  13. }
  14. public function testSend()
  15. {
  16. $data = 'test data';
  17. $result = $this->pipeline->send($data);
  18. $this->assertSame($this->pipeline, $result);
  19. }
  20. public function testThroughWithArray()
  21. {
  22. $pipes = [
  23. function ($passable, $next) {
  24. return $next($passable . ' pipe1');
  25. },
  26. function ($passable, $next) {
  27. return $next($passable . ' pipe2');
  28. }
  29. ];
  30. $result = $this->pipeline->through($pipes);
  31. $this->assertSame($this->pipeline, $result);
  32. }
  33. public function testThroughWithArguments()
  34. {
  35. $pipe1 = function ($passable, $next) {
  36. return $next($passable . ' pipe1');
  37. };
  38. $pipe2 = function ($passable, $next) {
  39. return $next($passable . ' pipe2');
  40. };
  41. $result = $this->pipeline->through($pipe1, $pipe2);
  42. $this->assertSame($this->pipeline, $result);
  43. }
  44. public function testThenExecutesPipeline()
  45. {
  46. $pipes = [
  47. function ($passable, $next) {
  48. return $next($passable . ' pipe1');
  49. },
  50. function ($passable, $next) {
  51. return $next($passable . ' pipe2');
  52. }
  53. ];
  54. $destination = function ($passable) {
  55. return $passable . ' destination';
  56. };
  57. $result = $this->pipeline
  58. ->send('start')
  59. ->through($pipes)
  60. ->then($destination);
  61. $this->assertEquals('start pipe1 pipe2 destination', $result);
  62. }
  63. public function testPipelineExecutesInCorrectOrder()
  64. {
  65. $order = [];
  66. $pipes = [
  67. function ($passable, $next) use (&$order) {
  68. $order[] = 'pipe1_before';
  69. $result = $next($passable);
  70. $order[] = 'pipe1_after';
  71. return $result;
  72. },
  73. function ($passable, $next) use (&$order) {
  74. $order[] = 'pipe2_before';
  75. $result = $next($passable);
  76. $order[] = 'pipe2_after';
  77. return $result;
  78. }
  79. ];
  80. $destination = function ($passable) use (&$order) {
  81. $order[] = 'destination';
  82. return $passable;
  83. };
  84. $this->pipeline
  85. ->send('test')
  86. ->through($pipes)
  87. ->then($destination);
  88. $expected = ['pipe1_before', 'pipe2_before', 'destination', 'pipe2_after', 'pipe1_after'];
  89. $this->assertEquals($expected, $order);
  90. }
  91. public function testEmptyPipelineExecutesDestination()
  92. {
  93. $destination = function ($passable) {
  94. return $passable . ' processed';
  95. };
  96. $result = $this->pipeline
  97. ->send('test')
  98. ->through([])
  99. ->then($destination);
  100. $this->assertEquals('test processed', $result);
  101. }
  102. public function testPipelineCanModifyData()
  103. {
  104. $pipes = [
  105. function ($passable, $next) {
  106. $passable['pipe1'] = true;
  107. return $next($passable);
  108. },
  109. function ($passable, $next) {
  110. $passable['pipe2'] = true;
  111. return $next($passable);
  112. }
  113. ];
  114. $destination = function ($passable) {
  115. $passable['destination'] = true;
  116. return $passable;
  117. };
  118. $result = $this->pipeline
  119. ->send([])
  120. ->through($pipes)
  121. ->then($destination);
  122. $expected = [
  123. 'pipe1' => true,
  124. 'pipe2' => true,
  125. 'destination' => true
  126. ];
  127. $this->assertEquals($expected, $result);
  128. }
  129. public function testPipelineCanShortCircuit()
  130. {
  131. $pipes = [
  132. function ($passable, $next) {
  133. if ($passable === 'stop') {
  134. return 'stopped';
  135. }
  136. return $next($passable);
  137. },
  138. function ($passable, $next) {
  139. return $next($passable . ' pipe2');
  140. }
  141. ];
  142. $destination = function ($passable) {
  143. return $passable . ' destination';
  144. };
  145. $result = $this->pipeline
  146. ->send('stop')
  147. ->through($pipes)
  148. ->then($destination);
  149. $this->assertEquals('stopped', $result);
  150. }
  151. public function testExceptionInDestinationIsHandled()
  152. {
  153. $pipes = [
  154. function ($passable, $next) {
  155. return $next($passable);
  156. }
  157. ];
  158. $destination = function ($passable) {
  159. throw new Exception('Destination error');
  160. };
  161. $this->expectException(Exception::class);
  162. $this->expectExceptionMessage('Destination error');
  163. $this->pipeline
  164. ->send('test')
  165. ->through($pipes)
  166. ->then($destination);
  167. }
  168. public function testExceptionInPipeIsHandled()
  169. {
  170. $pipes = [
  171. function ($passable, $next) {
  172. throw new Exception('Pipe error');
  173. }
  174. ];
  175. $destination = function ($passable) {
  176. return $passable;
  177. };
  178. $this->expectException(Exception::class);
  179. $this->expectExceptionMessage('Pipe error');
  180. $this->pipeline
  181. ->send('test')
  182. ->through($pipes)
  183. ->then($destination);
  184. }
  185. public function testWhenExceptionSetsHandler()
  186. {
  187. $result = $this->pipeline->whenException(function ($passable, $e) {
  188. return 'handled';
  189. });
  190. $this->assertSame($this->pipeline, $result);
  191. }
  192. public function testExceptionHandlerIsCalledOnException()
  193. {
  194. $pipes = [
  195. function ($passable, $next) {
  196. throw new Exception('Test exception');
  197. }
  198. ];
  199. $destination = function ($passable) {
  200. return $passable;
  201. };
  202. $result = $this->pipeline
  203. ->send('test')
  204. ->through($pipes)
  205. ->whenException(function ($passable, $e) {
  206. return 'handled: ' . $e->getMessage();
  207. })
  208. ->then($destination);
  209. $this->assertEquals('handled: Test exception', $result);
  210. }
  211. public function testExceptionHandlerReceivesCorrectParameters()
  212. {
  213. $pipes = [
  214. function ($passable, $next) {
  215. throw new Exception('Test exception');
  216. }
  217. ];
  218. $destination = function ($passable) {
  219. return $passable;
  220. };
  221. $handlerCalled = false;
  222. $receivedPassable = null;
  223. $receivedException = null;
  224. $this->pipeline
  225. ->send('original data')
  226. ->through($pipes)
  227. ->whenException(function ($passable, $e) use (&$handlerCalled, &$receivedPassable, &$receivedException) {
  228. $handlerCalled = true;
  229. $receivedPassable = $passable;
  230. $receivedException = $e;
  231. return 'handled';
  232. })
  233. ->then($destination);
  234. $this->assertTrue($handlerCalled);
  235. $this->assertEquals('original data', $receivedPassable);
  236. $this->assertInstanceOf(Exception::class, $receivedException);
  237. $this->assertEquals('Test exception', $receivedException->getMessage());
  238. }
  239. public function testExceptionInDestinationWithHandler()
  240. {
  241. $destination = function ($passable) {
  242. throw new Exception('Destination exception');
  243. };
  244. $result = $this->pipeline
  245. ->send('test')
  246. ->through([])
  247. ->whenException(function ($passable, $e) {
  248. return 'destination handled: ' . $e->getMessage();
  249. })
  250. ->then($destination);
  251. $this->assertEquals('destination handled: Destination exception', $result);
  252. }
  253. public function testComplexPipelineWithExceptions()
  254. {
  255. $pipes = [
  256. function ($passable, $next) {
  257. $passable[] = 'pipe1';
  258. return $next($passable);
  259. },
  260. function ($passable, $next) {
  261. if (in_array('error', $passable)) {
  262. throw new Exception('Pipe2 error');
  263. }
  264. $passable[] = 'pipe2';
  265. return $next($passable);
  266. },
  267. function ($passable, $next) {
  268. $passable[] = 'pipe3';
  269. return $next($passable);
  270. }
  271. ];
  272. $destination = function ($passable) {
  273. $passable[] = 'destination';
  274. return $passable;
  275. };
  276. // Normal execution
  277. $result1 = $this->pipeline
  278. ->send(['start'])
  279. ->through($pipes)
  280. ->then($destination);
  281. $this->assertEquals(['start', 'pipe1', 'pipe2', 'pipe3', 'destination'], $result1);
  282. // With exception handling
  283. $result2 = $this->pipeline
  284. ->send(['start', 'error'])
  285. ->through($pipes)
  286. ->whenException(function ($passable, $e) {
  287. return ['error_handled', $e->getMessage()];
  288. })
  289. ->then($destination);
  290. $this->assertEquals(['error_handled', 'Pipe2 error'], $result2);
  291. }
  292. }