ILIAS  release_5-2 Revision v5.2.25-18-g3f80b828510
ProcessTest.php
Go to the documentation of this file.
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
13
21
26{
27 private static $phpBin;
28 private static $process;
29 private static $sigchild;
30 private static $notEnhancedSigchild = false;
31
32 public static function setUpBeforeClass()
33 {
35 self::$phpBin = getenv('SYMFONY_PROCESS_PHP_TEST_BINARY') ?: ('phpdbg' === PHP_SAPI ? 'php' : $phpBin->find());
36 if ('\\' !== DIRECTORY_SEPARATOR) {
37 // exec is mandatory to deal with sending a signal to the process
38 // see https://github.com/symfony/symfony/issues/5030 about prepending
39 // command with exec
40 self::$phpBin = 'exec '.self::$phpBin;
41 }
42
43 ob_start();
44 phpinfo(INFO_GENERAL);
45 self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
46 }
47
48 protected function tearDown()
49 {
50 if (self::$process) {
51 self::$process->stop(0);
52 self::$process = null;
53 }
54 }
55
57 {
58 if ('\\' === DIRECTORY_SEPARATOR) {
59 $this->markTestSkipped('This test is transient on Windows');
60 }
61 @trigger_error('Test Error', E_USER_NOTICE);
62 $process = $this->getProcess(self::$phpBin." -r 'sleep(3)'");
63 $process->run();
64 $actualError = error_get_last();
65 $this->assertEquals('Test Error', $actualError['message']);
66 $this->assertEquals(E_USER_NOTICE, $actualError['type']);
67 }
68
73 {
74 $this->getProcess('', null, null, null, -1);
75 }
76
81 {
82 $p = $this->getProcess('');
83 $p->setTimeout(-1);
84 }
85
86 public function testFloatAndNullTimeout()
87 {
88 $p = $this->getProcess('');
89
90 $p->setTimeout(10);
91 $this->assertSame(10.0, $p->getTimeout());
92
93 $p->setTimeout(null);
94 $this->assertNull($p->getTimeout());
95
96 $p->setTimeout(0.0);
97 $this->assertNull($p->getTimeout());
98 }
99
104 {
105 $p = $this->getProcess(self::$phpBin.' '.__DIR__.'/NonStopableProcess.php 30');
106 $p->start();
107
108 while (false === strpos($p->getOutput(), 'received')) {
109 usleep(1000);
110 }
111 $start = microtime(true);
112 $p->stop(0.1);
113
114 $p->wait();
115
116 $this->assertLessThan(15, microtime(true) - $start);
117 }
118
120 {
121 // this code will result in a maximum of 2 reads of 8192 bytes by calling
122 // start() and isRunning(). by the time getOutput() is called the process
123 // has terminated so the internal pipes array is already empty. normally
124 // the call to start() will not read any data as the process will not have
125 // generated output, but this is non-deterministic so we must count it as
126 // a possibility. therefore we need 2 * PipesInterface::CHUNK_SIZE plus
127 // another byte which will never be read.
128 $expectedOutputSize = PipesInterface::CHUNK_SIZE * 2 + 2;
129
130 $code = sprintf('echo str_repeat(\'*\', %d);', $expectedOutputSize);
131 $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg($code)));
132
133 $p->start();
134
135 // Don't call Process::run nor Process::wait to avoid any read of pipes
136 $h = new \ReflectionProperty($p, 'process');
137 $h->setAccessible(true);
138 $h = $h->getValue($p);
139 $s = @proc_get_status($h);
140
141 while (!empty($s['running'])) {
142 usleep(1000);
143 $s = proc_get_status($h);
144 }
145
146 $o = $p->getOutput();
147
148 $this->assertEquals($expectedOutputSize, strlen($o));
149 }
150
152 {
153 $process = $this->getProcess('echo foo');
154 $process->start(function ($type, $buffer) use (&$data) {
155 $data .= $buffer;
156 });
157
158 $process->wait();
159
160 $this->assertSame('foo'.PHP_EOL, $data);
161 }
162
168 public function testProcessResponses($expected, $getter, $code)
169 {
170 $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg($code)));
171 $p->run();
172
173 $this->assertSame($expected, $p->$getter());
174 }
175
181 public function testProcessPipes($code, $size)
182 {
183 $expected = str_repeat(str_repeat('*', 1024), $size).'!';
184 $expectedLength = (1024 * $size) + 1;
185
186 $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg($code)));
187 $p->setInput($expected);
188 $p->run();
189
190 $this->assertEquals($expectedLength, strlen($p->getOutput()));
191 $this->assertEquals($expectedLength, strlen($p->getErrorOutput()));
192 }
193
198 {
199 $expected = str_repeat(str_repeat('*', 1024), $size).'!';
200 $expectedLength = (1024 * $size) + 1;
201
202 $stream = fopen('php://temporary', 'w+');
203 fwrite($stream, $expected);
204 rewind($stream);
205
206 $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg($code)));
207 $p->setInput($stream);
208 $p->run();
209
210 fclose($stream);
211
212 $this->assertEquals($expectedLength, strlen($p->getOutput()));
213 $this->assertEquals($expectedLength, strlen($p->getErrorOutput()));
214 }
215
216 public function testLiveStreamAsInput()
217 {
218 $stream = fopen('php://memory', 'r+');
219 fwrite($stream, 'hello');
220 rewind($stream);
221
222 $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('stream_copy_to_stream(STDIN, STDOUT);')));
223 $p->setInput($stream);
224 $p->start(function ($type, $data) use ($stream) {
225 if ('hello' === $data) {
226 fclose($stream);
227 }
228 });
229 $p->wait();
230
231 $this->assertSame('hello', $p->getOutput());
232 }
233
239 {
240 $process = $this->getProcess(self::$phpBin.' -r "sleep(30);"');
241 $process->start();
242 try {
243 $process->setInput('foobar');
244 $process->stop();
245 $this->fail('A LogicException should have been raised.');
246 } catch (LogicException $e) {
247 }
248 $process->stop();
249
250 throw $e;
251 }
252
258 public function testInvalidInput($value)
259 {
260 $process = $this->getProcess('foo');
261 $process->setInput($value);
262 }
263
265 {
266 return array(
267 array(array()),
268 array(new NonStringifiable()),
269 );
270 }
271
275 public function testValidInput($expected, $value)
276 {
277 $process = $this->getProcess('foo');
278 $process->setInput($value);
279 $this->assertSame($expected, $process->getInput());
280 }
281
282 public function provideInputValues()
283 {
284 return array(
285 array(null, null),
286 array('24.5', 24.5),
287 array('input data', 'input data'),
288 );
289 }
290
292 {
293 if ('\\' === DIRECTORY_SEPARATOR) {
294 return array(
295 array("2 \r\n2\r\n", '&&', '2'),
296 );
297 }
298
299 return array(
300 array("1\n1\n", ';', '1'),
301 array("2\n2\n", '&&', '2'),
302 );
303 }
304
308 public function testChainedCommandsOutput($expected, $operator, $input)
309 {
310 $process = $this->getProcess(sprintf('echo %s %s echo %s', $input, $operator, $input));
311 $process->run();
312 $this->assertEquals($expected, $process->getOutput());
313 }
314
316 {
317 $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('echo \'foo\';')));
318
319 $called = false;
320 $p->run(function ($type, $buffer) use (&$called) {
321 $called = $buffer === 'foo';
322 });
323
324 $this->assertTrue($called, 'The callback should be executed with the output');
325 }
326
328 {
329 $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('echo \'foo\';')));
330 $p->disableOutput();
331
332 $called = false;
333 $p->run(function ($type, $buffer) use (&$called) {
334 $called = $buffer === 'foo';
335 });
336
337 $this->assertTrue($called, 'The callback should be executed with the output');
338 }
339
340 public function testGetErrorOutput()
341 {
342 $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }')));
343
344 $p->run();
345 $this->assertEquals(3, preg_match_all('/ERROR/', $p->getErrorOutput(), $matches));
346 }
347
348 public function testFlushErrorOutput()
349 {
350 $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }')));
351
352 $p->run();
353 $p->clearErrorOutput();
354 $this->assertEmpty($p->getErrorOutput());
355 }
356
360 public function testIncrementalOutput($getOutput, $getIncrementalOutput, $uri)
361 {
362 $lock = tempnam(sys_get_temp_dir(), __FUNCTION__);
363
364 $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('file_put_contents($s = \''.$uri.'\', \'foo\'); flock(fopen('.var_export($lock, true).', \'r\'), LOCK_EX); file_put_contents($s, \'bar\');')));
365
366 $h = fopen($lock, 'w');
367 flock($h, LOCK_EX);
368
369 $p->start();
370
371 foreach (array('foo', 'bar') as $s) {
372 while (false === strpos($p->$getOutput(), $s)) {
373 usleep(1000);
374 }
375
376 $this->assertSame($s, $p->$getIncrementalOutput());
377 $this->assertSame('', $p->$getIncrementalOutput());
378
379 flock($h, LOCK_UN);
380 }
381
382 fclose($h);
383 }
384
385 public function provideIncrementalOutput()
386 {
387 return array(
388 array('getOutput', 'getIncrementalOutput', 'php://stdout'),
389 array('getErrorOutput', 'getIncrementalErrorOutput', 'php://stderr'),
390 );
391 }
392
393 public function testGetOutput()
394 {
395 $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { echo \' foo \'; $n++; }')));
396
397 $p->run();
398 $this->assertEquals(3, preg_match_all('/foo/', $p->getOutput(), $matches));
399 }
400
401 public function testFlushOutput()
402 {
403 $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n=0;while ($n<3) {echo \' foo \';$n++;}')));
404
405 $p->run();
406 $p->clearOutput();
407 $this->assertEmpty($p->getOutput());
408 }
409
410 public function testZeroAsOutput()
411 {
412 if ('\\' === DIRECTORY_SEPARATOR) {
413 // see http://stackoverflow.com/questions/7105433/windows-batch-echo-without-new-line
414 $p = $this->getProcess('echo | set /p dummyName=0');
415 } else {
416 $p = $this->getProcess('printf 0');
417 }
418
419 $p->run();
420 $this->assertSame('0', $p->getOutput());
421 }
422
424 {
425 if ('\\' === DIRECTORY_SEPARATOR) {
426 $this->markTestSkipped('Windows does not support POSIX exit code');
427 }
429
430 // such command run in bash return an exitcode 127
431 $process = $this->getProcess('nonexistingcommandIhopeneversomeonewouldnameacommandlikethis');
432 $process->run();
433
434 $this->assertGreaterThan(0, $process->getExitCode());
435 }
436
437 public function testTTYCommand()
438 {
439 if ('\\' === DIRECTORY_SEPARATOR) {
440 $this->markTestSkipped('Windows does not have /dev/tty support');
441 }
442
443 $process = $this->getProcess('echo "foo" >> /dev/null && '.self::$phpBin.' -r "usleep(100000);"');
444 $process->setTty(true);
445 $process->start();
446 $this->assertTrue($process->isRunning());
447 $process->wait();
448
449 $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
450 }
451
452 public function testTTYCommandExitCode()
453 {
454 if ('\\' === DIRECTORY_SEPARATOR) {
455 $this->markTestSkipped('Windows does have /dev/tty support');
456 }
458
459 $process = $this->getProcess('echo "foo" >> /dev/null');
460 $process->setTty(true);
461 $process->run();
462
463 $this->assertTrue($process->isSuccessful());
464 }
465
471 {
472 if ('\\' !== DIRECTORY_SEPARATOR) {
473 $this->markTestSkipped('This test is for Windows platform only');
474 }
475
476 $process = $this->getProcess('echo "foo" >> /dev/null');
477 $process->setTty(false);
478 $process->setTty(true);
479 }
480
482 {
484
485 $process = $this->getProcess('');
486 $this->assertNull($process->getExitCodeText());
487 }
488
489 public function testPTYCommand()
490 {
492 $this->markTestSkipped('PTY is not supported on this operating system.');
493 }
494
495 $process = $this->getProcess('echo "foo"');
496 $process->setPty(true);
497 $process->run();
498
499 $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
500 $this->assertEquals("foo\r\n", $process->getOutput());
501 }
502
503 public function testMustRun()
504 {
506
507 $process = $this->getProcess('echo foo');
508
509 $this->assertSame($process, $process->mustRun());
510 $this->assertEquals('foo'.PHP_EOL, $process->getOutput());
511 }
512
514 {
516
517 $process = $this->getProcess('echo foo')->mustRun();
518 $this->assertEquals(0, $process->getExitCode());
519 }
520
525 {
527
528 $process = $this->getProcess('exit 1');
529 $process->mustRun();
530 }
531
532 public function testExitCodeText()
533 {
535
536 $process = $this->getProcess('');
537 $r = new \ReflectionObject($process);
538 $p = $r->getProperty('exitcode');
539 $p->setAccessible(true);
540
541 $p->setValue($process, 2);
542 $this->assertEquals('Misuse of shell builtins', $process->getExitCodeText());
543 }
544
545 public function testStartIsNonBlocking()
546 {
547 $process = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
548 $start = microtime(true);
549 $process->start();
550 $end = microtime(true);
551 $this->assertLessThan(0.4, $end - $start);
552 $process->stop();
553 }
554
555 public function testUpdateStatus()
556 {
557 $process = $this->getProcess('echo foo');
558 $process->run();
559 $this->assertTrue(strlen($process->getOutput()) > 0);
560 }
561
563 {
565
566 $process = $this->getProcess(self::$phpBin.' -r "usleep(100000);"');
567 $this->assertNull($process->getExitCode());
568 $process->start();
569 $this->assertNull($process->getExitCode());
570 $process->wait();
571 $this->assertEquals(0, $process->getExitCode());
572 }
573
575 {
577
578 $process = $this->getProcess(self::$phpBin.' -r "usleep(100000);"');
579 $process->run();
580 $this->assertEquals(0, $process->getExitCode());
581 $process->start();
582 $this->assertNull($process->getExitCode());
583 $process->wait();
584 $this->assertEquals(0, $process->getExitCode());
585 }
586
587 public function testGetExitCode()
588 {
590
591 $process = $this->getProcess('echo foo');
592 $process->run();
593 $this->assertSame(0, $process->getExitCode());
594 }
595
596 public function testStatus()
597 {
598 $process = $this->getProcess(self::$phpBin.' -r "usleep(100000);"');
599 $this->assertFalse($process->isRunning());
600 $this->assertFalse($process->isStarted());
601 $this->assertFalse($process->isTerminated());
602 $this->assertSame(Process::STATUS_READY, $process->getStatus());
603 $process->start();
604 $this->assertTrue($process->isRunning());
605 $this->assertTrue($process->isStarted());
606 $this->assertFalse($process->isTerminated());
607 $this->assertSame(Process::STATUS_STARTED, $process->getStatus());
608 $process->wait();
609 $this->assertFalse($process->isRunning());
610 $this->assertTrue($process->isStarted());
611 $this->assertTrue($process->isTerminated());
612 $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
613 }
614
615 public function testStop()
616 {
617 $process = $this->getProcess(self::$phpBin.' -r "sleep(31);"');
618 $process->start();
619 $this->assertTrue($process->isRunning());
620 $process->stop();
621 $this->assertFalse($process->isRunning());
622 }
623
624 public function testIsSuccessful()
625 {
627
628 $process = $this->getProcess('echo foo');
629 $process->run();
630 $this->assertTrue($process->isSuccessful());
631 }
632
634 {
636
637 $process = $this->getProcess(self::$phpBin.' -r "usleep(100000);"');
638 $process->start();
639
640 $this->assertFalse($process->isSuccessful());
641
642 $process->wait();
643
644 $this->assertTrue($process->isSuccessful());
645 }
646
647 public function testIsNotSuccessful()
648 {
650
651 $process = $this->getProcess(self::$phpBin.' -r "throw new \Exception(\'BOUM\');"');
652 $process->run();
653 $this->assertFalse($process->isSuccessful());
654 }
655
656 public function testProcessIsNotSignaled()
657 {
658 if ('\\' === DIRECTORY_SEPARATOR) {
659 $this->markTestSkipped('Windows does not support POSIX signals');
660 }
662
663 $process = $this->getProcess('echo foo');
664 $process->run();
665 $this->assertFalse($process->hasBeenSignaled());
666 }
667
669 {
670 if ('\\' === DIRECTORY_SEPARATOR) {
671 $this->markTestSkipped('Windows does not support POSIX signals');
672 }
674
675 $process = $this->getProcess('echo foo');
676 $process->run();
677 $this->assertEquals(0, $process->getTermSignal());
678 }
679
681 {
682 if ('\\' === DIRECTORY_SEPARATOR) {
683 $this->markTestSkipped('Windows does not support POSIX signals');
684 }
686
687 $process = $this->getProcess(self::$phpBin.' -r "sleep(32);"');
688 $process->start();
689 $process->stop();
690 $this->assertTrue($process->hasBeenSignaled());
691 $this->assertEquals(15, $process->getTermSignal()); // SIGTERM
692 }
693
699 {
700 if (!function_exists('posix_kill')) {
701 $this->markTestSkipped('Function posix_kill is required.');
702 }
703 $this->skipIfNotEnhancedSigchild(false);
704
705 $process = $this->getProcess(self::$phpBin.' -r "sleep(32.1)"');
706 $process->start();
707 posix_kill($process->getPid(), 9); // SIGKILL
708
709 $process->wait();
710 }
711
712 public function testRestart()
713 {
714 $process1 = $this->getProcess(self::$phpBin.' -r "echo getmypid();"');
715 $process1->run();
716 $process2 = $process1->restart();
717
718 $process2->wait(); // wait for output
719
720 // Ensure that both processed finished and the output is numeric
721 $this->assertFalse($process1->isRunning());
722 $this->assertFalse($process2->isRunning());
723 $this->assertTrue(is_numeric($process1->getOutput()));
724 $this->assertTrue(is_numeric($process2->getOutput()));
725
726 // Ensure that restart returned a new process by check that the output is different
727 $this->assertNotEquals($process1->getOutput(), $process2->getOutput());
728 }
729
735 {
736 $process = $this->getProcess(self::$phpBin.' -r "sleep(30);"');
737 $process->setTimeout(0.1);
738 $start = microtime(true);
739 try {
740 $process->run();
741 $this->fail('A RuntimeException should have been raised');
742 } catch (RuntimeException $e) {
743 }
744
745 $this->assertLessThan(15, microtime(true) - $start);
746
747 throw $e;
748 }
749
751 {
752 $process = $this->getProcess('echo foo');
753 $this->assertNull($process->checkTimeout());
754 }
755
757 {
758 $process = $this->getProcess('echo foo');
759 $process->run();
760 $this->assertNull($process->checkTimeout());
761 }
762
768 {
769 $process = $this->getProcess(self::$phpBin.' -r "sleep(33);"');
770 $process->setTimeout(0.1);
771
772 $process->start();
773 $start = microtime(true);
774
775 try {
776 while ($process->isRunning()) {
777 $process->checkTimeout();
778 usleep(100000);
779 }
780 $this->fail('A ProcessTimedOutException should have been raised');
781 } catch (ProcessTimedOutException $e) {
782 }
783
784 $this->assertLessThan(15, microtime(true) - $start);
785
786 throw $e;
787 }
788
789 public function testIdleTimeout()
790 {
791 $process = $this->getProcess(self::$phpBin.' -r "sleep(34);"');
792 $process->setTimeout(60);
793 $process->setIdleTimeout(0.1);
794
795 try {
796 $process->run();
797
798 $this->fail('A timeout exception was expected.');
799 } catch (ProcessTimedOutException $e) {
800 $this->assertTrue($e->isIdleTimeout());
801 $this->assertFalse($e->isGeneralTimeout());
802 $this->assertEquals(0.1, $e->getExceededTimeout());
803 }
804 }
805
807 {
808 $process = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('while (true) {echo \'foo \'; usleep(1000);}')));
809 $process->setTimeout(1);
810 $process->start();
811
812 while (false === strpos($process->getOutput(), 'foo')) {
813 usleep(1000);
814 }
815
816 $process->setIdleTimeout(0.5);
817
818 try {
819 $process->wait();
820 $this->fail('A timeout exception was expected.');
821 } catch (ProcessTimedOutException $e) {
822 $this->assertTrue($e->isGeneralTimeout(), 'A general timeout is expected.');
823 $this->assertFalse($e->isIdleTimeout(), 'No idle timeout is expected.');
824 $this->assertEquals(1, $e->getExceededTimeout());
825 }
826 }
827
832 public function testStartAfterATimeout()
833 {
834 $process = $this->getProcess(self::$phpBin.' -r "sleep(35);"');
835 $process->setTimeout(0.1);
836
837 try {
838 $process->run();
839 $this->fail('A ProcessTimedOutException should have been raised.');
840 } catch (ProcessTimedOutException $e) {
841 }
842 $this->assertFalse($process->isRunning());
843 $process->start();
844 $this->assertTrue($process->isRunning());
845 $process->stop(0);
846
847 throw $e;
848 }
849
850 public function testGetPid()
851 {
852 $process = $this->getProcess(self::$phpBin.' -r "sleep(36);"');
853 $process->start();
854 $this->assertGreaterThan(0, $process->getPid());
855 $process->stop(0);
856 }
857
859 {
860 $process = $this->getProcess('foo');
861 $this->assertNull($process->getPid());
862 }
863
864 public function testGetPidIsNullAfterRun()
865 {
866 $process = $this->getProcess('echo foo');
867 $process->run();
868 $this->assertNull($process->getPid());
869 }
870
874 public function testSignal()
875 {
876 $process = $this->getProcess(self::$phpBin.' '.__DIR__.'/SignalListener.php');
877 $process->start();
878
879 while (false === strpos($process->getOutput(), 'Caught')) {
880 usleep(1000);
881 }
882 $process->signal(SIGUSR1);
883 $process->wait();
884
885 $this->assertEquals('Caught SIGUSR1', $process->getOutput());
886 }
887
892 {
894
895 $process = $this->getProcess('sleep 4');
896 $process->start();
897 $process->signal(SIGKILL);
898
899 while ($process->isRunning()) {
900 usleep(10000);
901 }
902
903 $this->assertFalse($process->isRunning());
904 $this->assertTrue($process->hasBeenSignaled());
905 $this->assertFalse($process->isSuccessful());
906 $this->assertEquals(137, $process->getExitCode());
907 }
908
914 {
915 $process = $this->getProcess('foo');
916 $process->signal(1); // SIGHUP
917 }
918
922 public function testMethodsThatNeedARunningProcess($method)
923 {
924 $process = $this->getProcess('foo');
925 $this->setExpectedException('Symfony\Component\Process\Exception\LogicException', sprintf('Process must be started before calling %s.', $method));
926 $process->{$method}();
927 }
928
930 {
931 return array(
932 array('getOutput'),
933 array('getIncrementalOutput'),
934 array('getErrorOutput'),
935 array('getIncrementalErrorOutput'),
936 array('wait'),
937 );
938 }
939
946 {
947 $process = $this->getProcess(self::$phpBin.' -r "sleep(37);"');
948 $process->start();
949 try {
950 $process->{$method}();
951 $process->stop(0);
952 $this->fail('A LogicException must have been thrown');
953 } catch (\Exception $e) {
954 }
955 $process->stop(0);
956
957 throw $e;
958 }
959
961 {
962 return array(
963 array('hasBeenSignaled'),
964 array('getTermSignal'),
965 array('hasBeenStopped'),
966 array('getStopSignal'),
967 );
968 }
969
974 public function testWrongSignal($signal)
975 {
976 if ('\\' === DIRECTORY_SEPARATOR) {
977 $this->markTestSkipped('POSIX signals do not work on Windows');
978 }
979
980 $process = $this->getProcess(self::$phpBin.' -r "sleep(38);"');
981 $process->start();
982 try {
983 $process->signal($signal);
984 $this->fail('A RuntimeException must have been thrown');
985 } catch (RuntimeException $e) {
986 $process->stop(0);
987 }
988
989 throw $e;
990 }
991
992 public function provideWrongSignal()
993 {
994 return array(
995 array(-4),
996 array('Céphalopodes'),
997 );
998 }
999
1001 {
1002 $p = $this->getProcess('foo');
1003 $this->assertFalse($p->isOutputDisabled());
1004 $p->disableOutput();
1005 $this->assertTrue($p->isOutputDisabled());
1006 $p->enableOutput();
1007 $this->assertFalse($p->isOutputDisabled());
1008 }
1009
1015 {
1016 $p = $this->getProcess(self::$phpBin.' -r "sleep(39);"');
1017 $p->start();
1018 $p->disableOutput();
1019 }
1020
1026 {
1027 $p = $this->getProcess(self::$phpBin.' -r "sleep(40);"');
1028 $p->disableOutput();
1029 $p->start();
1030 $p->enableOutput();
1031 }
1032
1034 {
1035 $p = $this->getProcess('echo foo');
1036 $p->disableOutput();
1037 $p->run();
1038 $p->enableOutput();
1039 $p->disableOutput();
1040 $this->assertTrue($p->isOutputDisabled());
1041 }
1042
1048 {
1049 $process = $this->getProcess('foo');
1050 $process->setIdleTimeout(1);
1051 $process->disableOutput();
1052 }
1053
1059 {
1060 $process = $this->getProcess('foo');
1061 $process->disableOutput();
1062 $process->setIdleTimeout(1);
1063 }
1064
1066 {
1067 $process = $this->getProcess('foo');
1068 $process->disableOutput();
1069 $this->assertSame($process, $process->setIdleTimeout(null));
1070 }
1071
1077 public function testGetOutputWhileDisabled($fetchMethod)
1078 {
1079 $p = $this->getProcess(self::$phpBin.' -r "sleep(41);"');
1080 $p->disableOutput();
1081 $p->start();
1082 $p->{$fetchMethod}();
1083 }
1084
1086 {
1087 return array(
1088 array('getOutput'),
1089 array('getIncrementalOutput'),
1090 array('getErrorOutput'),
1091 array('getIncrementalErrorOutput'),
1092 );
1093 }
1094
1096 {
1097 $process = $this->getProcess(self::$phpBin.' -r "echo 123; sleep(42);"');
1098 $process->run(function () use ($process) {
1099 $process->stop();
1100 });
1101 $this->assertTrue(true, 'A call to stop() is not expected to cause wait() to throw a RuntimeException');
1102 }
1103
1105 {
1106 $process = $this->getProcess(self::$phpBin.' -r "echo 123; sleep(43);"');
1107 $process->run(function () use ($process) {
1108 $process->signal(9); // SIGKILL
1109 });
1110 $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException');
1111 }
1112
1114 {
1115 $process = $this->getProcess(self::$phpBin.' -r "echo 123; sleep(44);"');
1116 $process->run(function () use ($process) {
1117 $process->signal(15); // SIGTERM
1118 });
1119 $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException');
1120 }
1121
1122 public function responsesCodeProvider()
1123 {
1124 return array(
1125 //expected output / getter / code to execute
1126 //array(1,'getExitCode','exit(1);'),
1127 //array(true,'isSuccessful','exit();'),
1128 array('output', 'getOutput', 'echo \'output\';'),
1129 );
1130 }
1131
1132 public function pipesCodeProvider()
1133 {
1134 $variations = array(
1135 'fwrite(STDOUT, $in = file_get_contents(\'php://stdin\')); fwrite(STDERR, $in);',
1136 'include \''.__DIR__.'/PipeStdinInStdoutStdErrStreamSelect.php\';',
1137 );
1138
1139 if ('\\' === DIRECTORY_SEPARATOR) {
1140 // Avoid XL buffers on Windows because of https://bugs.php.net/bug.php?id=65650
1141 $sizes = array(1, 2, 4, 8);
1142 } else {
1143 $sizes = array(1, 16, 64, 1024, 4096);
1144 }
1145
1146 $codes = array();
1147 foreach ($sizes as $size) {
1148 foreach ($variations as $code) {
1149 $codes[] = array($code, $size);
1150 }
1151 }
1152
1153 return $codes;
1154 }
1155
1159 public function testIncrementalOutputDoesNotRequireAnotherCall($stream, $method)
1160 {
1161 $process = $this->getProcess(self::$phpBin.' -r '.escapeshellarg('$n = 0; while ($n < 3) { file_put_contents(\''.$stream.'\', $n, 1); $n++; usleep(1000); }'), null, null, null, null);
1162 $process->start();
1163 $result = '';
1164 $limit = microtime(true) + 3;
1165 $expected = '012';
1166
1167 while ($result !== $expected && microtime(true) < $limit) {
1168 $result .= $process->$method();
1169 }
1170
1171 $this->assertSame($expected, $result);
1172 $process->stop();
1173 }
1174
1175 public function provideVariousIncrementals()
1176 {
1177 return array(
1178 array('php://stdout', 'getIncrementalOutput'),
1179 array('php://stderr', 'getIncrementalErrorOutput'),
1180 );
1181 }
1182
1183 public function testIteratorInput()
1184 {
1185 $input = function () {
1186 yield 'ping';
1187 yield 'pong';
1188 };
1189
1190 $process = $this->getProcess(self::$phpBin.' -r '.escapeshellarg('stream_copy_to_stream(STDIN, STDOUT);'), null, null, $input());
1191 $process->run();
1192 $this->assertSame('pingpong', $process->getOutput());
1193 }
1194
1195 public function testSimpleInputStream()
1196 {
1197 $input = new InputStream();
1198
1199 $process = $this->getProcess(self::$phpBin.' -r '.escapeshellarg('echo \'ping\'; stream_copy_to_stream(STDIN, STDOUT);'));
1200 $process->setInput($input);
1201
1202 $process->start(function ($type, $data) use ($input) {
1203 if ('ping' === $data) {
1204 $input->write('pang');
1205 } elseif (!$input->isClosed()) {
1206 $input->write('pong');
1207 $input->close();
1208 }
1209 });
1210
1211 $process->wait();
1212 $this->assertSame('pingpangpong', $process->getOutput());
1213 }
1214
1216 {
1217 $i = 0;
1218 $stream = fopen('php://memory', 'w+');
1219 $stream = function () use ($stream, &$i) {
1220 if ($i < 3) {
1221 rewind($stream);
1222 fwrite($stream, ++$i);
1223 rewind($stream);
1224
1225 return $stream;
1226 }
1227 };
1228
1229 $input = new InputStream();
1230 $input->onEmpty($stream);
1231 $input->write($stream());
1232
1233 $process = $this->getProcess(self::$phpBin.' -r '.escapeshellarg('echo fread(STDIN, 3);'));
1234 $process->setInput($input);
1235 $process->start(function ($type, $data) use ($input) {
1236 $input->close();
1237 });
1238
1239 $process->wait();
1240 $this->assertSame('123', $process->getOutput());
1241 }
1242
1244 {
1245 $input = new InputStream();
1246 $input->onEmpty(function ($input) {
1247 yield 'pong';
1248 $input->close();
1249 });
1250
1251 $process = $this->getProcess(self::$phpBin.' -r '.escapeshellarg('stream_copy_to_stream(STDIN, STDOUT);'));
1252 $process->setInput($input);
1253 $process->start();
1254 $input->write('ping');
1255 $process->wait();
1256 $this->assertSame('pingpong', $process->getOutput());
1257 }
1258
1259 public function testInputStreamOnEmpty()
1260 {
1261 $i = 0;
1262 $input = new InputStream();
1263 $input->onEmpty(function () use (&$i) {++$i;});
1264
1265 $process = $this->getProcess(self::$phpBin.' -r '.escapeshellarg('echo 123; echo fread(STDIN, 1); echo 456;'));
1266 $process->setInput($input);
1267 $process->start(function ($type, $data) use ($input) {
1268 if ('123' === $data) {
1269 $input->close();
1270 }
1271 });
1272 $process->wait();
1273
1274 $this->assertSame(0, $i, 'InputStream->onEmpty callback should be called only when the input *becomes* empty');
1275 $this->assertSame('123456', $process->getOutput());
1276 }
1277
1278 public function testIteratorOutput()
1279 {
1280 $input = new InputStream();
1281
1282 $process = $this->getProcess(self::$phpBin.' -r '.escapeshellarg('fwrite(STDOUT, 123); fwrite(STDERR, 234); flush(); usleep(10000); fwrite(STDOUT, fread(STDIN, 3)); fwrite(STDERR, 456);'));
1283 $process->setInput($input);
1284 $process->start();
1285 $output = array();
1286
1287 foreach ($process as $type => $data) {
1288 $output[] = array($type, $data);
1289 break;
1290 }
1291 $expectedOutput = array(
1292 array($process::OUT, '123'),
1293 );
1294 $this->assertSame($expectedOutput, $output);
1295
1296 $input->write(345);
1297
1298 foreach ($process as $type => $data) {
1299 $output[] = array($type, $data);
1300 }
1301
1302 $this->assertSame('', $process->getOutput());
1303 $this->assertFalse($process->isRunning());
1304
1305 $expectedOutput = array(
1306 array($process::OUT, '123'),
1307 array($process::ERR, '234'),
1308 array($process::OUT, '345'),
1309 array($process::ERR, '456'),
1310 );
1311 $this->assertSame($expectedOutput, $output);
1312 }
1313
1315 {
1316 $input = new InputStream();
1317
1318 $process = $this->getProcess(self::$phpBin.' -r '.escapeshellarg('fwrite(STDOUT, fread(STDIN, 3));'));
1319 $process->setInput($input);
1320 $process->start();
1321 $output = array();
1322
1323 foreach ($process->getIterator($process::ITER_NON_BLOCKING | $process::ITER_KEEP_OUTPUT) as $type => $data) {
1324 $output[] = array($type, $data);
1325 break;
1326 }
1327 $expectedOutput = array(
1328 array($process::OUT, ''),
1329 );
1330 $this->assertSame($expectedOutput, $output);
1331
1332 $input->write(123);
1333
1334 foreach ($process->getIterator($process::ITER_NON_BLOCKING | $process::ITER_KEEP_OUTPUT) as $type => $data) {
1335 if ('' !== $data) {
1336 $output[] = array($type, $data);
1337 }
1338 }
1339
1340 $this->assertSame('123', $process->getOutput());
1341 $this->assertFalse($process->isRunning());
1342
1343 $expectedOutput = array(
1344 array($process::OUT, ''),
1345 array($process::OUT, '123'),
1346 );
1347 $this->assertSame($expectedOutput, $output);
1348 }
1349
1350 public function testChainedProcesses()
1351 {
1352 $p1 = new Process(self::$phpBin.' -r '.escapeshellarg('fwrite(STDERR, 123); fwrite(STDOUT, 456);'));
1353 $p2 = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('stream_copy_to_stream(STDIN, STDOUT);')));
1354 $p2->setInput($p1);
1355
1356 $p1->start();
1357 $p2->run();
1358
1359 $this->assertSame('123', $p1->getErrorOutput());
1360 $this->assertSame('', $p1->getOutput());
1361 $this->assertSame('', $p2->getErrorOutput());
1362 $this->assertSame('456', $p2->getOutput());
1363 }
1364
1375 private function getProcess($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = array())
1376 {
1377 $process = new Process($commandline, $cwd, $env, $input, $timeout, $options);
1378
1379 if (false !== $enhance = getenv('ENHANCE_SIGCHLD')) {
1380 try {
1381 $process->setEnhanceSigchildCompatibility(false);
1382 $process->getExitCode();
1383 $this->fail('ENHANCE_SIGCHLD must be used together with a sigchild-enabled PHP.');
1384 } catch (RuntimeException $e) {
1385 $this->assertSame('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.', $e->getMessage());
1386 if ($enhance) {
1387 $process->setEnhanceSigchildCompatibility(true);
1388 } else {
1389 self::$notEnhancedSigchild = true;
1390 }
1391 }
1392 }
1393
1394 if (self::$process) {
1395 self::$process->stop(0);
1396 }
1397
1398 return self::$process = $process;
1399 }
1400
1401 private function skipIfNotEnhancedSigchild($expectException = true)
1402 {
1403 if (self::$sigchild) {
1404 if (!$expectException) {
1405 $this->markTestSkipped('PHP is compiled with --enable-sigchild.');
1406 } elseif (self::$notEnhancedSigchild) {
1407 $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild.');
1408 }
1409 }
1410 }
1411}
1412
1414{
1415}
sprintf('%.4f', $callTime)
$n
Definition: RandomTest.php:80
$size
Definition: RandomTest.php:79
An exception for terminatinating execution or to throw for unit testing.
LogicException for the Process Component.
Exception that is thrown when a process times out.
RuntimeException for the Process Component.
Provides a way to continuously write to the input of a Process until the InputStream is closed.
Definition: InputStream.php:22
An executable finder specifically designed for the PHP executable.
Process is a thin wrapper around proc_* functions to easily start independent PHP processes.
Definition: Process.php:31
static isPtySupported()
Returns whether PTY is supported on the current operating system.
Definition: Process.php:1232
testInvalidInput($value)
@dataProvider provideInvalidInputValues @expectedException \Symfony\Component\Process\Exception\Inval...
testCheckTimeoutOnStartedProcess()
@expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException @expectedExceptionMe...
testWrongSignal($signal)
@dataProvider provideWrongSignal @expectedException \Symfony\Component\Process\Exception\RuntimeExcep...
testSetStreamAsInput($code, $size)
@dataProvider pipesCodeProvider
testDisableOutputWhileIdleTimeoutIsSet()
@expectedException \Symfony\Component\Process\Exception\LogicException @expectedExceptionMessage Outp...
testTTYInWindowsEnvironment()
@expectedException \Symfony\Component\Process\Exception\RuntimeException @expectedExceptionMessage TT...
testNegativeTimeoutFromConstructor()
@expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
Definition: ProcessTest.php:72
testSignalProcessNotRunning()
@expectedException \Symfony\Component\Process\Exception\LogicException @expectedExceptionMessage Can ...
testValidInput($expected, $value)
@dataProvider provideInputValues
testSignal()
@requires extension pcntl
testStartAfterATimeout()
@expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException @expectedExceptionMe...
testMustRunThrowsException()
@expectedException \Symfony\Component\Process\Exception\ProcessFailedException
testGetOutputWhileDisabled($fetchMethod)
@dataProvider provideOutputFetchingMethods @expectedException \Symfony\Component\Process\Exception\Lo...
testProcessPipes($code, $size)
tests results from sub processes.
testDisableOutputWhileRunningThrowsException()
@expectedException \Symfony\Component\Process\Exception\RuntimeException @expectedExceptionMessage Di...
testRunProcessWithTimeout()
@expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException @expectedExceptionMe...
testMethodsThatNeedARunningProcess($method)
@dataProvider provideMethodsThatNeedARunningProcess
testIncrementalOutputDoesNotRequireAnotherCall($stream, $method)
@dataProvider provideVariousIncrementals
testMethodsThatNeedATerminatedProcess($method)
@dataProvider provideMethodsThatNeedATerminatedProcess @expectedException Symfony\Component\Process\E...
testExitCodeIsAvailableAfterSignal()
@requires extension pcntl
testSetIdleTimeoutWhileOutputIsDisabled()
@expectedException \Symfony\Component\Process\Exception\LogicException @expectedExceptionMessage time...
testChainedCommandsOutput($expected, $operator, $input)
@dataProvider chainedCommandsOutputProvider
testProcessResponses($expected, $getter, $code)
tests results from sub processes.
testIncrementalOutput($getOutput, $getIncrementalOutput, $uri)
@dataProvider provideIncrementalOutput
testNegativeTimeoutFromSetter()
@expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
Definition: ProcessTest.php:80
testProcessThrowsExceptionWhenExternallySignaled()
@expectedException \Symfony\Component\Process\Exception\RuntimeException @expectedExceptionMessage Th...
testEnableOutputWhileRunningThrowsException()
@expectedException \Symfony\Component\Process\Exception\RuntimeException @expectedExceptionMessage En...
skipIfNotEnhancedSigchild($expectException=true)
getProcess($commandline, $cwd=null, array $env=null, $input=null, $timeout=60, array $options=array())
testSetInputWhileRunningThrowsAnException()
@expectedException \Symfony\Component\Process\Exception\LogicException @expectedExceptionMessage Inpu...
testStopWithTimeoutIsActuallyWorking()
@requires extension pcntl
$h
$r
Definition: example_031.php:79
$code
Definition: example_050.php:99
if(!is_dir( $entity_dir)) exit("Fatal Error ([A-Za-z0-9]+)\s+" &#(? foreach( $entity_files as $file) $output
PipesInterface manages descriptors and pipes for the use of proc_open.
if(!is_array($argv)) $options