scratch – Blame information for rev 115

Subversion Repositories:
Rev:
Rev Author Line No. Line
115 office 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  
12 namespace Symfony\Component\Process\Tests;
13  
14 use PHPUnit\Framework\TestCase;
15 use Symfony\Component\Process\Exception\LogicException;
16 use Symfony\Component\Process\Exception\ProcessTimedOutException;
17 use Symfony\Component\Process\Exception\RuntimeException;
18 use Symfony\Component\Process\InputStream;
19 use Symfony\Component\Process\PhpExecutableFinder;
20 use Symfony\Component\Process\Pipes\PipesInterface;
21 use Symfony\Component\Process\Process;
22  
23 /**
24 * @author Robert Schönthal <seroscho@googlemail.com>
25 */
26 class ProcessTest extends TestCase
27 {
28 private static $phpBin;
29 private static $process;
30 private static $sigchild;
31 private static $notEnhancedSigchild = false;
32  
33 public static function setUpBeforeClass()
34 {
35 $phpBin = new PhpExecutableFinder();
36 self::$phpBin = getenv('SYMFONY_PROCESS_PHP_TEST_BINARY') ?: ('phpdbg' === PHP_SAPI ? 'php' : $phpBin->find());
37  
38 ob_start();
39 phpinfo(INFO_GENERAL);
40 self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
41 }
42  
43 protected function tearDown()
44 {
45 if (self::$process) {
46 self::$process->stop(0);
47 self::$process = null;
48 }
49 }
50  
51 public function testThatProcessDoesNotThrowWarningDuringRun()
52 {
53 if ('\\' === DIRECTORY_SEPARATOR) {
54 $this->markTestSkipped('This test is transient on Windows');
55 }
56 @trigger_error('Test Error', E_USER_NOTICE);
57 $process = $this->getProcessForCode('sleep(3)');
58 $process->run();
59 $actualError = error_get_last();
60 $this->assertEquals('Test Error', $actualError['message']);
61 $this->assertEquals(E_USER_NOTICE, $actualError['type']);
62 }
63  
64 /**
65 * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
66 */
67 public function testNegativeTimeoutFromConstructor()
68 {
69 $this->getProcess('', null, null, null, -1);
70 }
71  
72 /**
73 * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
74 */
75 public function testNegativeTimeoutFromSetter()
76 {
77 $p = $this->getProcess('');
78 $p->setTimeout(-1);
79 }
80  
81 public function testFloatAndNullTimeout()
82 {
83 $p = $this->getProcess('');
84  
85 $p->setTimeout(10);
86 $this->assertSame(10.0, $p->getTimeout());
87  
88 $p->setTimeout(null);
89 $this->assertNull($p->getTimeout());
90  
91 $p->setTimeout(0.0);
92 $this->assertNull($p->getTimeout());
93 }
94  
95 /**
96 * @requires extension pcntl
97 */
98 public function testStopWithTimeoutIsActuallyWorking()
99 {
100 $p = $this->getProcess(array(self::$phpBin, __DIR__.'/NonStopableProcess.php', 30));
101 $p->start();
102  
103 while (false === strpos($p->getOutput(), 'received')) {
104 usleep(1000);
105 }
106 $start = microtime(true);
107 $p->stop(0.1);
108  
109 $p->wait();
110  
111 $this->assertLessThan(15, microtime(true) - $start);
112 }
113  
114 public function testAllOutputIsActuallyReadOnTermination()
115 {
116 // this code will result in a maximum of 2 reads of 8192 bytes by calling
117 // start() and isRunning(). by the time getOutput() is called the process
118 // has terminated so the internal pipes array is already empty. normally
119 // the call to start() will not read any data as the process will not have
120 // generated output, but this is non-deterministic so we must count it as
121 // a possibility. therefore we need 2 * PipesInterface::CHUNK_SIZE plus
122 // another byte which will never be read.
123 $expectedOutputSize = PipesInterface::CHUNK_SIZE * 2 + 2;
124  
125 $code = sprintf('echo str_repeat(\'*\', %d);', $expectedOutputSize);
126 $p = $this->getProcessForCode($code);
127  
128 $p->start();
129  
130 // Don't call Process::run nor Process::wait to avoid any read of pipes
131 $h = new \ReflectionProperty($p, 'process');
132 $h->setAccessible(true);
133 $h = $h->getValue($p);
134 $s = @proc_get_status($h);
135  
136 while (!empty($s['running'])) {
137 usleep(1000);
138 $s = proc_get_status($h);
139 }
140  
141 $o = $p->getOutput();
142  
143 $this->assertEquals($expectedOutputSize, strlen($o));
144 }
145  
146 public function testCallbacksAreExecutedWithStart()
147 {
148 $process = $this->getProcess('echo foo');
149 $process->start(function ($type, $buffer) use (&$data) {
150 $data .= $buffer;
151 });
152  
153 $process->wait();
154  
155 $this->assertSame('foo'.PHP_EOL, $data);
156 }
157  
158 /**
159 * tests results from sub processes.
160 *
161 * @dataProvider responsesCodeProvider
162 */
163 public function testProcessResponses($expected, $getter, $code)
164 {
165 $p = $this->getProcessForCode($code);
166 $p->run();
167  
168 $this->assertSame($expected, $p->$getter());
169 }
170  
171 /**
172 * tests results from sub processes.
173 *
174 * @dataProvider pipesCodeProvider
175 */
176 public function testProcessPipes($code, $size)
177 {
178 $expected = str_repeat(str_repeat('*', 1024), $size).'!';
179 $expectedLength = (1024 * $size) + 1;
180  
181 $p = $this->getProcessForCode($code);
182 $p->setInput($expected);
183 $p->run();
184  
185 $this->assertEquals($expectedLength, strlen($p->getOutput()));
186 $this->assertEquals($expectedLength, strlen($p->getErrorOutput()));
187 }
188  
189 /**
190 * @dataProvider pipesCodeProvider
191 */
192 public function testSetStreamAsInput($code, $size)
193 {
194 $expected = str_repeat(str_repeat('*', 1024), $size).'!';
195 $expectedLength = (1024 * $size) + 1;
196  
197 $stream = fopen('php://temporary', 'w+');
198 fwrite($stream, $expected);
199 rewind($stream);
200  
201 $p = $this->getProcessForCode($code);
202 $p->setInput($stream);
203 $p->run();
204  
205 fclose($stream);
206  
207 $this->assertEquals($expectedLength, strlen($p->getOutput()));
208 $this->assertEquals($expectedLength, strlen($p->getErrorOutput()));
209 }
210  
211 public function testLiveStreamAsInput()
212 {
213 $stream = fopen('php://memory', 'r+');
214 fwrite($stream, 'hello');
215 rewind($stream);
216  
217 $p = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
218 $p->setInput($stream);
219 $p->start(function ($type, $data) use ($stream) {
220 if ('hello' === $data) {
221 fclose($stream);
222 }
223 });
224 $p->wait();
225  
226 $this->assertSame('hello', $p->getOutput());
227 }
228  
229 /**
230 * @expectedException \Symfony\Component\Process\Exception\LogicException
231 * @expectedExceptionMessage Input can not be set while the process is running.
232 */
233 public function testSetInputWhileRunningThrowsAnException()
234 {
235 $process = $this->getProcessForCode('sleep(30);');
236 $process->start();
237 try {
238 $process->setInput('foobar');
239 $process->stop();
240 $this->fail('A LogicException should have been raised.');
241 } catch (LogicException $e) {
242 }
243 $process->stop();
244  
245 throw $e;
246 }
247  
248 /**
249 * @dataProvider provideInvalidInputValues
250 * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
251 * @expectedExceptionMessage Symfony\Component\Process\Process::setInput only accepts strings, Traversable objects or stream resources.
252 */
253 public function testInvalidInput($value)
254 {
255 $process = $this->getProcess('foo');
256 $process->setInput($value);
257 }
258  
259 public function provideInvalidInputValues()
260 {
261 return array(
262 array(array()),
263 array(new NonStringifiable()),
264 );
265 }
266  
267 /**
268 * @dataProvider provideInputValues
269 */
270 public function testValidInput($expected, $value)
271 {
272 $process = $this->getProcess('foo');
273 $process->setInput($value);
274 $this->assertSame($expected, $process->getInput());
275 }
276  
277 public function provideInputValues()
278 {
279 return array(
280 array(null, null),
281 array('24.5', 24.5),
282 array('input data', 'input data'),
283 );
284 }
285  
286 public function chainedCommandsOutputProvider()
287 {
288 if ('\\' === DIRECTORY_SEPARATOR) {
289 return array(
290 array("2 \r\n2\r\n", '&&', '2'),
291 );
292 }
293  
294 return array(
295 array("1\n1\n", ';', '1'),
296 array("2\n2\n", '&&', '2'),
297 );
298 }
299  
300 /**
301 * @dataProvider chainedCommandsOutputProvider
302 */
303 public function testChainedCommandsOutput($expected, $operator, $input)
304 {
305 $process = $this->getProcess(sprintf('echo %s %s echo %s', $input, $operator, $input));
306 $process->run();
307 $this->assertEquals($expected, $process->getOutput());
308 }
309  
310 public function testCallbackIsExecutedForOutput()
311 {
312 $p = $this->getProcessForCode('echo \'foo\';');
313  
314 $called = false;
315 $p->run(function ($type, $buffer) use (&$called) {
316 $called = $buffer === 'foo';
317 });
318  
319 $this->assertTrue($called, 'The callback should be executed with the output');
320 }
321  
322 public function testCallbackIsExecutedForOutputWheneverOutputIsDisabled()
323 {
324 $p = $this->getProcessForCode('echo \'foo\';');
325 $p->disableOutput();
326  
327 $called = false;
328 $p->run(function ($type, $buffer) use (&$called) {
329 $called = $buffer === 'foo';
330 });
331  
332 $this->assertTrue($called, 'The callback should be executed with the output');
333 }
334  
335 public function testGetErrorOutput()
336 {
337 $p = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }');
338  
339 $p->run();
340 $this->assertEquals(3, preg_match_all('/ERROR/', $p->getErrorOutput(), $matches));
341 }
342  
343 public function testFlushErrorOutput()
344 {
345 $p = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }');
346  
347 $p->run();
348 $p->clearErrorOutput();
349 $this->assertEmpty($p->getErrorOutput());
350 }
351  
352 /**
353 * @dataProvider provideIncrementalOutput
354 */
355 public function testIncrementalOutput($getOutput, $getIncrementalOutput, $uri)
356 {
357 $lock = tempnam(sys_get_temp_dir(), __FUNCTION__);
358  
359 $p = $this->getProcessForCode('file_put_contents($s = \''.$uri.'\', \'foo\'); flock(fopen('.var_export($lock, true).', \'r\'), LOCK_EX); file_put_contents($s, \'bar\');');
360  
361 $h = fopen($lock, 'w');
362 flock($h, LOCK_EX);
363  
364 $p->start();
365  
366 foreach (array('foo', 'bar') as $s) {
367 while (false === strpos($p->$getOutput(), $s)) {
368 usleep(1000);
369 }
370  
371 $this->assertSame($s, $p->$getIncrementalOutput());
372 $this->assertSame('', $p->$getIncrementalOutput());
373  
374 flock($h, LOCK_UN);
375 }
376  
377 fclose($h);
378 }
379  
380 public function provideIncrementalOutput()
381 {
382 return array(
383 array('getOutput', 'getIncrementalOutput', 'php://stdout'),
384 array('getErrorOutput', 'getIncrementalErrorOutput', 'php://stderr'),
385 );
386 }
387  
388 public function testGetOutput()
389 {
390 $p = $this->getProcessForCode('$n = 0; while ($n < 3) { echo \' foo \'; $n++; }');
391  
392 $p->run();
393 $this->assertEquals(3, preg_match_all('/foo/', $p->getOutput(), $matches));
394 }
395  
396 public function testFlushOutput()
397 {
398 $p = $this->getProcessForCode('$n=0;while ($n<3) {echo \' foo \';$n++;}');
399  
400 $p->run();
401 $p->clearOutput();
402 $this->assertEmpty($p->getOutput());
403 }
404  
405 public function testZeroAsOutput()
406 {
407 if ('\\' === DIRECTORY_SEPARATOR) {
408 // see http://stackoverflow.com/questions/7105433/windows-batch-echo-without-new-line
409 $p = $this->getProcess('echo | set /p dummyName=0');
410 } else {
411 $p = $this->getProcess('printf 0');
412 }
413  
414 $p->run();
415 $this->assertSame('0', $p->getOutput());
416 }
417  
418 public function testExitCodeCommandFailed()
419 {
420 if ('\\' === DIRECTORY_SEPARATOR) {
421 $this->markTestSkipped('Windows does not support POSIX exit code');
422 }
423 $this->skipIfNotEnhancedSigchild();
424  
425 // such command run in bash return an exitcode 127
426 $process = $this->getProcess('nonexistingcommandIhopeneversomeonewouldnameacommandlikethis');
427 $process->run();
428  
429 $this->assertGreaterThan(0, $process->getExitCode());
430 }
431  
432 public function testTTYCommand()
433 {
434 if ('\\' === DIRECTORY_SEPARATOR) {
435 $this->markTestSkipped('Windows does not have /dev/tty support');
436 }
437  
438 $process = $this->getProcess('echo "foo" >> /dev/null && '.$this->getProcessForCode('usleep(100000);')->getCommandLine());
439 $process->setTty(true);
440 $process->start();
441 $this->assertTrue($process->isRunning());
442 $process->wait();
443  
444 $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
445 }
446  
447 public function testTTYCommandExitCode()
448 {
449 if ('\\' === DIRECTORY_SEPARATOR) {
450 $this->markTestSkipped('Windows does have /dev/tty support');
451 }
452 $this->skipIfNotEnhancedSigchild();
453  
454 $process = $this->getProcess('echo "foo" >> /dev/null');
455 $process->setTty(true);
456 $process->run();
457  
458 $this->assertTrue($process->isSuccessful());
459 }
460  
461 /**
462 * @expectedException \Symfony\Component\Process\Exception\RuntimeException
463 * @expectedExceptionMessage TTY mode is not supported on Windows platform.
464 */
465 public function testTTYInWindowsEnvironment()
466 {
467 if ('\\' !== DIRECTORY_SEPARATOR) {
468 $this->markTestSkipped('This test is for Windows platform only');
469 }
470  
471 $process = $this->getProcess('echo "foo" >> /dev/null');
472 $process->setTty(false);
473 $process->setTty(true);
474 }
475  
476 public function testExitCodeTextIsNullWhenExitCodeIsNull()
477 {
478 $this->skipIfNotEnhancedSigchild();
479  
480 $process = $this->getProcess('');
481 $this->assertNull($process->getExitCodeText());
482 }
483  
484 public function testPTYCommand()
485 {
486 if (!Process::isPtySupported()) {
487 $this->markTestSkipped('PTY is not supported on this operating system.');
488 }
489  
490 $process = $this->getProcess('echo "foo"');
491 $process->setPty(true);
492 $process->run();
493  
494 $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
495 $this->assertEquals("foo\r\n", $process->getOutput());
496 }
497  
498 public function testMustRun()
499 {
500 $this->skipIfNotEnhancedSigchild();
501  
502 $process = $this->getProcess('echo foo');
503  
504 $this->assertSame($process, $process->mustRun());
505 $this->assertEquals('foo'.PHP_EOL, $process->getOutput());
506 }
507  
508 public function testSuccessfulMustRunHasCorrectExitCode()
509 {
510 $this->skipIfNotEnhancedSigchild();
511  
512 $process = $this->getProcess('echo foo')->mustRun();
513 $this->assertEquals(0, $process->getExitCode());
514 }
515  
516 /**
517 * @expectedException \Symfony\Component\Process\Exception\ProcessFailedException
518 */
519 public function testMustRunThrowsException()
520 {
521 $this->skipIfNotEnhancedSigchild();
522  
523 $process = $this->getProcess('exit 1');
524 $process->mustRun();
525 }
526  
527 public function testExitCodeText()
528 {
529 $this->skipIfNotEnhancedSigchild();
530  
531 $process = $this->getProcess('');
532 $r = new \ReflectionObject($process);
533 $p = $r->getProperty('exitcode');
534 $p->setAccessible(true);
535  
536 $p->setValue($process, 2);
537 $this->assertEquals('Misuse of shell builtins', $process->getExitCodeText());
538 }
539  
540 public function testStartIsNonBlocking()
541 {
542 $process = $this->getProcessForCode('usleep(500000);');
543 $start = microtime(true);
544 $process->start();
545 $end = microtime(true);
546 $this->assertLessThan(0.4, $end - $start);
547 $process->stop();
548 }
549  
550 public function testUpdateStatus()
551 {
552 $process = $this->getProcess('echo foo');
553 $process->run();
554 $this->assertTrue(strlen($process->getOutput()) > 0);
555 }
556  
557 public function testGetExitCodeIsNullOnStart()
558 {
559 $this->skipIfNotEnhancedSigchild();
560  
561 $process = $this->getProcessForCode('usleep(100000);');
562 $this->assertNull($process->getExitCode());
563 $process->start();
564 $this->assertNull($process->getExitCode());
565 $process->wait();
566 $this->assertEquals(0, $process->getExitCode());
567 }
568  
569 public function testGetExitCodeIsNullOnWhenStartingAgain()
570 {
571 $this->skipIfNotEnhancedSigchild();
572  
573 $process = $this->getProcessForCode('usleep(100000);');
574 $process->run();
575 $this->assertEquals(0, $process->getExitCode());
576 $process->start();
577 $this->assertNull($process->getExitCode());
578 $process->wait();
579 $this->assertEquals(0, $process->getExitCode());
580 }
581  
582 public function testGetExitCode()
583 {
584 $this->skipIfNotEnhancedSigchild();
585  
586 $process = $this->getProcess('echo foo');
587 $process->run();
588 $this->assertSame(0, $process->getExitCode());
589 }
590  
591 public function testStatus()
592 {
593 $process = $this->getProcessForCode('usleep(100000);');
594 $this->assertFalse($process->isRunning());
595 $this->assertFalse($process->isStarted());
596 $this->assertFalse($process->isTerminated());
597 $this->assertSame(Process::STATUS_READY, $process->getStatus());
598 $process->start();
599 $this->assertTrue($process->isRunning());
600 $this->assertTrue($process->isStarted());
601 $this->assertFalse($process->isTerminated());
602 $this->assertSame(Process::STATUS_STARTED, $process->getStatus());
603 $process->wait();
604 $this->assertFalse($process->isRunning());
605 $this->assertTrue($process->isStarted());
606 $this->assertTrue($process->isTerminated());
607 $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
608 }
609  
610 public function testStop()
611 {
612 $process = $this->getProcessForCode('sleep(31);');
613 $process->start();
614 $this->assertTrue($process->isRunning());
615 $process->stop();
616 $this->assertFalse($process->isRunning());
617 }
618  
619 public function testIsSuccessful()
620 {
621 $this->skipIfNotEnhancedSigchild();
622  
623 $process = $this->getProcess('echo foo');
624 $process->run();
625 $this->assertTrue($process->isSuccessful());
626 }
627  
628 public function testIsSuccessfulOnlyAfterTerminated()
629 {
630 $this->skipIfNotEnhancedSigchild();
631  
632 $process = $this->getProcessForCode('usleep(100000);');
633 $process->start();
634  
635 $this->assertFalse($process->isSuccessful());
636  
637 $process->wait();
638  
639 $this->assertTrue($process->isSuccessful());
640 }
641  
642 public function testIsNotSuccessful()
643 {
644 $this->skipIfNotEnhancedSigchild();
645  
646 $process = $this->getProcessForCode('throw new \Exception(\'BOUM\');');
647 $process->run();
648 $this->assertFalse($process->isSuccessful());
649 }
650  
651 public function testProcessIsNotSignaled()
652 {
653 if ('\\' === DIRECTORY_SEPARATOR) {
654 $this->markTestSkipped('Windows does not support POSIX signals');
655 }
656 $this->skipIfNotEnhancedSigchild();
657  
658 $process = $this->getProcess('echo foo');
659 $process->run();
660 $this->assertFalse($process->hasBeenSignaled());
661 }
662  
663 public function testProcessWithoutTermSignal()
664 {
665 if ('\\' === DIRECTORY_SEPARATOR) {
666 $this->markTestSkipped('Windows does not support POSIX signals');
667 }
668 $this->skipIfNotEnhancedSigchild();
669  
670 $process = $this->getProcess('echo foo');
671 $process->run();
672 $this->assertEquals(0, $process->getTermSignal());
673 }
674  
675 public function testProcessIsSignaledIfStopped()
676 {
677 if ('\\' === DIRECTORY_SEPARATOR) {
678 $this->markTestSkipped('Windows does not support POSIX signals');
679 }
680 $this->skipIfNotEnhancedSigchild();
681  
682 $process = $this->getProcessForCode('sleep(32);');
683 $process->start();
684 $process->stop();
685 $this->assertTrue($process->hasBeenSignaled());
686 $this->assertEquals(15, $process->getTermSignal()); // SIGTERM
687 }
688  
689 /**
690 * @expectedException \Symfony\Component\Process\Exception\RuntimeException
691 * @expectedExceptionMessage The process has been signaled
692 */
693 public function testProcessThrowsExceptionWhenExternallySignaled()
694 {
695 if (!function_exists('posix_kill')) {
696 $this->markTestSkipped('Function posix_kill is required.');
697 }
698 $this->skipIfNotEnhancedSigchild(false);
699  
700 $process = $this->getProcessForCode('sleep(32.1);');
701 $process->start();
702 posix_kill($process->getPid(), 9); // SIGKILL
703  
704 $process->wait();
705 }
706  
707 public function testRestart()
708 {
709 $process1 = $this->getProcessForCode('echo getmypid();');
710 $process1->run();
711 $process2 = $process1->restart();
712  
713 $process2->wait(); // wait for output
714  
715 // Ensure that both processed finished and the output is numeric
716 $this->assertFalse($process1->isRunning());
717 $this->assertFalse($process2->isRunning());
718 $this->assertInternalType('numeric', $process1->getOutput());
719 $this->assertInternalType('numeric', $process2->getOutput());
720  
721 // Ensure that restart returned a new process by check that the output is different
722 $this->assertNotEquals($process1->getOutput(), $process2->getOutput());
723 }
724  
725 /**
726 * @expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException
727 * @expectedExceptionMessage exceeded the timeout of 0.1 seconds.
728 */
729 public function testRunProcessWithTimeout()
730 {
731 $process = $this->getProcessForCode('sleep(30);');
732 $process->setTimeout(0.1);
733 $start = microtime(true);
734 try {
735 $process->run();
736 $this->fail('A RuntimeException should have been raised');
737 } catch (RuntimeException $e) {
738 }
739  
740 $this->assertLessThan(15, microtime(true) - $start);
741  
742 throw $e;
743 }
744  
745 /**
746 * @expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException
747 * @expectedExceptionMessage exceeded the timeout of 0.1 seconds.
748 */
749 public function testIterateOverProcessWithTimeout()
750 {
751 $process = $this->getProcessForCode('sleep(30);');
752 $process->setTimeout(0.1);
753 $start = microtime(true);
754 try {
755 $process->start();
756 foreach ($process as $buffer);
757 $this->fail('A RuntimeException should have been raised');
758 } catch (RuntimeException $e) {
759 }
760  
761 $this->assertLessThan(15, microtime(true) - $start);
762  
763 throw $e;
764 }
765  
766 public function testCheckTimeoutOnNonStartedProcess()
767 {
768 $process = $this->getProcess('echo foo');
769 $this->assertNull($process->checkTimeout());
770 }
771  
772 public function testCheckTimeoutOnTerminatedProcess()
773 {
774 $process = $this->getProcess('echo foo');
775 $process->run();
776 $this->assertNull($process->checkTimeout());
777 }
778  
779 /**
780 * @expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException
781 * @expectedExceptionMessage exceeded the timeout of 0.1 seconds.
782 */
783 public function testCheckTimeoutOnStartedProcess()
784 {
785 $process = $this->getProcessForCode('sleep(33);');
786 $process->setTimeout(0.1);
787  
788 $process->start();
789 $start = microtime(true);
790  
791 try {
792 while ($process->isRunning()) {
793 $process->checkTimeout();
794 usleep(100000);
795 }
796 $this->fail('A ProcessTimedOutException should have been raised');
797 } catch (ProcessTimedOutException $e) {
798 }
799  
800 $this->assertLessThan(15, microtime(true) - $start);
801  
802 throw $e;
803 }
804  
805 public function testIdleTimeout()
806 {
807 $process = $this->getProcessForCode('sleep(34);');
808 $process->setTimeout(60);
809 $process->setIdleTimeout(0.1);
810  
811 try {
812 $process->run();
813  
814 $this->fail('A timeout exception was expected.');
815 } catch (ProcessTimedOutException $e) {
816 $this->assertTrue($e->isIdleTimeout());
817 $this->assertFalse($e->isGeneralTimeout());
818 $this->assertEquals(0.1, $e->getExceededTimeout());
819 }
820 }
821  
822 public function testIdleTimeoutNotExceededWhenOutputIsSent()
823 {
824 $process = $this->getProcessForCode('while (true) {echo \'foo \'; usleep(1000);}');
825 $process->setTimeout(1);
826 $process->start();
827  
828 while (false === strpos($process->getOutput(), 'foo')) {
829 usleep(1000);
830 }
831  
832 $process->setIdleTimeout(0.5);
833  
834 try {
835 $process->wait();
836 $this->fail('A timeout exception was expected.');
837 } catch (ProcessTimedOutException $e) {
838 $this->assertTrue($e->isGeneralTimeout(), 'A general timeout is expected.');
839 $this->assertFalse($e->isIdleTimeout(), 'No idle timeout is expected.');
840 $this->assertEquals(1, $e->getExceededTimeout());
841 }
842 }
843  
844 /**
845 * @expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException
846 * @expectedExceptionMessage exceeded the timeout of 0.1 seconds.
847 */
848 public function testStartAfterATimeout()
849 {
850 $process = $this->getProcessForCode('sleep(35);');
851 $process->setTimeout(0.1);
852  
853 try {
854 $process->run();
855 $this->fail('A ProcessTimedOutException should have been raised.');
856 } catch (ProcessTimedOutException $e) {
857 }
858 $this->assertFalse($process->isRunning());
859 $process->start();
860 $this->assertTrue($process->isRunning());
861 $process->stop(0);
862  
863 throw $e;
864 }
865  
866 public function testGetPid()
867 {
868 $process = $this->getProcessForCode('sleep(36);');
869 $process->start();
870 $this->assertGreaterThan(0, $process->getPid());
871 $process->stop(0);
872 }
873  
874 public function testGetPidIsNullBeforeStart()
875 {
876 $process = $this->getProcess('foo');
877 $this->assertNull($process->getPid());
878 }
879  
880 public function testGetPidIsNullAfterRun()
881 {
882 $process = $this->getProcess('echo foo');
883 $process->run();
884 $this->assertNull($process->getPid());
885 }
886  
887 /**
888 * @requires extension pcntl
889 */
890 public function testSignal()
891 {
892 $process = $this->getProcess(array(self::$phpBin, __DIR__.'/SignalListener.php'));
893 $process->start();
894  
895 while (false === strpos($process->getOutput(), 'Caught')) {
896 usleep(1000);
897 }
898 $process->signal(SIGUSR1);
899 $process->wait();
900  
901 $this->assertEquals('Caught SIGUSR1', $process->getOutput());
902 }
903  
904 /**
905 * @requires extension pcntl
906 */
907 public function testExitCodeIsAvailableAfterSignal()
908 {
909 $this->skipIfNotEnhancedSigchild();
910  
911 $process = $this->getProcess('sleep 4');
912 $process->start();
913 $process->signal(SIGKILL);
914  
915 while ($process->isRunning()) {
916 usleep(10000);
917 }
918  
919 $this->assertFalse($process->isRunning());
920 $this->assertTrue($process->hasBeenSignaled());
921 $this->assertFalse($process->isSuccessful());
922 $this->assertEquals(137, $process->getExitCode());
923 }
924  
925 /**
926 * @expectedException \Symfony\Component\Process\Exception\LogicException
927 * @expectedExceptionMessage Can not send signal on a non running process.
928 */
929 public function testSignalProcessNotRunning()
930 {
931 $process = $this->getProcess('foo');
932 $process->signal(1); // SIGHUP
933 }
934  
935 /**
936 * @dataProvider provideMethodsThatNeedARunningProcess
937 */
938 public function testMethodsThatNeedARunningProcess($method)
939 {
940 $process = $this->getProcess('foo');
941  
942 if (method_exists($this, 'expectException')) {
943 $this->expectException('Symfony\Component\Process\Exception\LogicException');
944 $this->expectExceptionMessage(sprintf('Process must be started before calling %s.', $method));
945 } else {
946 $this->setExpectedException('Symfony\Component\Process\Exception\LogicException', sprintf('Process must be started before calling %s.', $method));
947 }
948  
949 $process->{$method}();
950 }
951  
952 public function provideMethodsThatNeedARunningProcess()
953 {
954 return array(
955 array('getOutput'),
956 array('getIncrementalOutput'),
957 array('getErrorOutput'),
958 array('getIncrementalErrorOutput'),
959 array('wait'),
960 );
961 }
962  
963 /**
964 * @dataProvider provideMethodsThatNeedATerminatedProcess
965 * @expectedException \Symfony\Component\Process\Exception\LogicException
966 * @expectedExceptionMessage Process must be terminated before calling
967 */
968 public function testMethodsThatNeedATerminatedProcess($method)
969 {
970 $process = $this->getProcessForCode('sleep(37);');
971 $process->start();
972 try {
973 $process->{$method}();
974 $process->stop(0);
975 $this->fail('A LogicException must have been thrown');
976 } catch (\Exception $e) {
977 }
978 $process->stop(0);
979  
980 throw $e;
981 }
982  
983 public function provideMethodsThatNeedATerminatedProcess()
984 {
985 return array(
986 array('hasBeenSignaled'),
987 array('getTermSignal'),
988 array('hasBeenStopped'),
989 array('getStopSignal'),
990 );
991 }
992  
993 /**
994 * @dataProvider provideWrongSignal
995 * @expectedException \Symfony\Component\Process\Exception\RuntimeException
996 */
997 public function testWrongSignal($signal)
998 {
999 if ('\\' === DIRECTORY_SEPARATOR) {
1000 $this->markTestSkipped('POSIX signals do not work on Windows');
1001 }
1002  
1003 $process = $this->getProcessForCode('sleep(38);');
1004 $process->start();
1005 try {
1006 $process->signal($signal);
1007 $this->fail('A RuntimeException must have been thrown');
1008 } catch (RuntimeException $e) {
1009 $process->stop(0);
1010 }
1011  
1012 throw $e;
1013 }
1014  
1015 public function provideWrongSignal()
1016 {
1017 return array(
1018 array(-4),
1019 array('Céphalopodes'),
1020 );
1021 }
1022  
1023 public function testDisableOutputDisablesTheOutput()
1024 {
1025 $p = $this->getProcess('foo');
1026 $this->assertFalse($p->isOutputDisabled());
1027 $p->disableOutput();
1028 $this->assertTrue($p->isOutputDisabled());
1029 $p->enableOutput();
1030 $this->assertFalse($p->isOutputDisabled());
1031 }
1032  
1033 /**
1034 * @expectedException \Symfony\Component\Process\Exception\RuntimeException
1035 * @expectedExceptionMessage Disabling output while the process is running is not possible.
1036 */
1037 public function testDisableOutputWhileRunningThrowsException()
1038 {
1039 $p = $this->getProcessForCode('sleep(39);');
1040 $p->start();
1041 $p->disableOutput();
1042 }
1043  
1044 /**
1045 * @expectedException \Symfony\Component\Process\Exception\RuntimeException
1046 * @expectedExceptionMessage Enabling output while the process is running is not possible.
1047 */
1048 public function testEnableOutputWhileRunningThrowsException()
1049 {
1050 $p = $this->getProcessForCode('sleep(40);');
1051 $p->disableOutput();
1052 $p->start();
1053 $p->enableOutput();
1054 }
1055  
1056 public function testEnableOrDisableOutputAfterRunDoesNotThrowException()
1057 {
1058 $p = $this->getProcess('echo foo');
1059 $p->disableOutput();
1060 $p->run();
1061 $p->enableOutput();
1062 $p->disableOutput();
1063 $this->assertTrue($p->isOutputDisabled());
1064 }
1065  
1066 /**
1067 * @expectedException \Symfony\Component\Process\Exception\LogicException
1068 * @expectedExceptionMessage Output can not be disabled while an idle timeout is set.
1069 */
1070 public function testDisableOutputWhileIdleTimeoutIsSet()
1071 {
1072 $process = $this->getProcess('foo');
1073 $process->setIdleTimeout(1);
1074 $process->disableOutput();
1075 }
1076  
1077 /**
1078 * @expectedException \Symfony\Component\Process\Exception\LogicException
1079 * @expectedExceptionMessage timeout can not be set while the output is disabled.
1080 */
1081 public function testSetIdleTimeoutWhileOutputIsDisabled()
1082 {
1083 $process = $this->getProcess('foo');
1084 $process->disableOutput();
1085 $process->setIdleTimeout(1);
1086 }
1087  
1088 public function testSetNullIdleTimeoutWhileOutputIsDisabled()
1089 {
1090 $process = $this->getProcess('foo');
1091 $process->disableOutput();
1092 $this->assertSame($process, $process->setIdleTimeout(null));
1093 }
1094  
1095 /**
1096 * @dataProvider provideOutputFetchingMethods
1097 * @expectedException \Symfony\Component\Process\Exception\LogicException
1098 * @expectedExceptionMessage Output has been disabled.
1099 */
1100 public function testGetOutputWhileDisabled($fetchMethod)
1101 {
1102 $p = $this->getProcessForCode('sleep(41);');
1103 $p->disableOutput();
1104 $p->start();
1105 $p->{$fetchMethod}();
1106 }
1107  
1108 public function provideOutputFetchingMethods()
1109 {
1110 return array(
1111 array('getOutput'),
1112 array('getIncrementalOutput'),
1113 array('getErrorOutput'),
1114 array('getIncrementalErrorOutput'),
1115 );
1116 }
1117  
1118 public function testStopTerminatesProcessCleanly()
1119 {
1120 $process = $this->getProcessForCode('echo 123; sleep(42);');
1121 $process->run(function () use ($process) {
1122 $process->stop();
1123 });
1124 $this->assertTrue(true, 'A call to stop() is not expected to cause wait() to throw a RuntimeException');
1125 }
1126  
1127 public function testKillSignalTerminatesProcessCleanly()
1128 {
1129 $process = $this->getProcessForCode('echo 123; sleep(43);');
1130 $process->run(function () use ($process) {
1131 $process->signal(9); // SIGKILL
1132 });
1133 $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException');
1134 }
1135  
1136 public function testTermSignalTerminatesProcessCleanly()
1137 {
1138 $process = $this->getProcessForCode('echo 123; sleep(44);');
1139 $process->run(function () use ($process) {
1140 $process->signal(15); // SIGTERM
1141 });
1142 $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException');
1143 }
1144  
1145 public function responsesCodeProvider()
1146 {
1147 return array(
1148 //expected output / getter / code to execute
1149 //array(1,'getExitCode','exit(1);'),
1150 //array(true,'isSuccessful','exit();'),
1151 array('output', 'getOutput', 'echo \'output\';'),
1152 );
1153 }
1154  
1155 public function pipesCodeProvider()
1156 {
1157 $variations = array(
1158 'fwrite(STDOUT, $in = file_get_contents(\'php://stdin\')); fwrite(STDERR, $in);',
1159 'include \''.__DIR__.'/PipeStdinInStdoutStdErrStreamSelect.php\';',
1160 );
1161  
1162 if ('\\' === DIRECTORY_SEPARATOR) {
1163 // Avoid XL buffers on Windows because of https://bugs.php.net/bug.php?id=65650
1164 $sizes = array(1, 2, 4, 8);
1165 } else {
1166 $sizes = array(1, 16, 64, 1024, 4096);
1167 }
1168  
1169 $codes = array();
1170 foreach ($sizes as $size) {
1171 foreach ($variations as $code) {
1172 $codes[] = array($code, $size);
1173 }
1174 }
1175  
1176 return $codes;
1177 }
1178  
1179 /**
1180 * @dataProvider provideVariousIncrementals
1181 */
1182 public function testIncrementalOutputDoesNotRequireAnotherCall($stream, $method)
1183 {
1184 $process = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\''.$stream.'\', $n, 1); $n++; usleep(1000); }', null, null, null, null);
1185 $process->start();
1186 $result = '';
1187 $limit = microtime(true) + 3;
1188 $expected = '012';
1189  
1190 while ($result !== $expected && microtime(true) < $limit) {
1191 $result .= $process->$method();
1192 }
1193  
1194 $this->assertSame($expected, $result);
1195 $process->stop();
1196 }
1197  
1198 public function provideVariousIncrementals()
1199 {
1200 return array(
1201 array('php://stdout', 'getIncrementalOutput'),
1202 array('php://stderr', 'getIncrementalErrorOutput'),
1203 );
1204 }
1205  
1206 public function testIteratorInput()
1207 {
1208 $input = function () {
1209 yield 'ping';
1210 yield 'pong';
1211 };
1212  
1213 $process = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);', null, null, $input());
1214 $process->run();
1215 $this->assertSame('pingpong', $process->getOutput());
1216 }
1217  
1218 public function testSimpleInputStream()
1219 {
1220 $input = new InputStream();
1221  
1222 $process = $this->getProcessForCode('echo \'ping\'; stream_copy_to_stream(STDIN, STDOUT);');
1223 $process->setInput($input);
1224  
1225 $process->start(function ($type, $data) use ($input) {
1226 if ('ping' === $data) {
1227 $input->write('pang');
1228 } elseif (!$input->isClosed()) {
1229 $input->write('pong');
1230 $input->close();
1231 }
1232 });
1233  
1234 $process->wait();
1235 $this->assertSame('pingpangpong', $process->getOutput());
1236 }
1237  
1238 public function testInputStreamWithCallable()
1239 {
1240 $i = 0;
1241 $stream = fopen('php://memory', 'w+');
1242 $stream = function () use ($stream, &$i) {
1243 if ($i < 3) {
1244 rewind($stream);
1245 fwrite($stream, ++$i);
1246 rewind($stream);
1247  
1248 return $stream;
1249 }
1250 };
1251  
1252 $input = new InputStream();
1253 $input->onEmpty($stream);
1254 $input->write($stream());
1255  
1256 $process = $this->getProcessForCode('echo fread(STDIN, 3);');
1257 $process->setInput($input);
1258 $process->start(function ($type, $data) use ($input) {
1259 $input->close();
1260 });
1261  
1262 $process->wait();
1263 $this->assertSame('123', $process->getOutput());
1264 }
1265  
1266 public function testInputStreamWithGenerator()
1267 {
1268 $input = new InputStream();
1269 $input->onEmpty(function ($input) {
1270 yield 'pong';
1271 $input->close();
1272 });
1273  
1274 $process = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
1275 $process->setInput($input);
1276 $process->start();
1277 $input->write('ping');
1278 $process->wait();
1279 $this->assertSame('pingpong', $process->getOutput());
1280 }
1281  
1282 public function testInputStreamOnEmpty()
1283 {
1284 $i = 0;
1285 $input = new InputStream();
1286 $input->onEmpty(function () use (&$i) { ++$i; });
1287  
1288 $process = $this->getProcessForCode('echo 123; echo fread(STDIN, 1); echo 456;');
1289 $process->setInput($input);
1290 $process->start(function ($type, $data) use ($input) {
1291 if ('123' === $data) {
1292 $input->close();
1293 }
1294 });
1295 $process->wait();
1296  
1297 $this->assertSame(0, $i, 'InputStream->onEmpty callback should be called only when the input *becomes* empty');
1298 $this->assertSame('123456', $process->getOutput());
1299 }
1300  
1301 public function testIteratorOutput()
1302 {
1303 $input = new InputStream();
1304  
1305 $process = $this->getProcessForCode('fwrite(STDOUT, 123); fwrite(STDERR, 234); flush(); usleep(10000); fwrite(STDOUT, fread(STDIN, 3)); fwrite(STDERR, 456);');
1306 $process->setInput($input);
1307 $process->start();
1308 $output = array();
1309  
1310 foreach ($process as $type => $data) {
1311 $output[] = array($type, $data);
1312 break;
1313 }
1314 $expectedOutput = array(
1315 array($process::OUT, '123'),
1316 );
1317 $this->assertSame($expectedOutput, $output);
1318  
1319 $input->write(345);
1320  
1321 foreach ($process as $type => $data) {
1322 $output[] = array($type, $data);
1323 }
1324  
1325 $this->assertSame('', $process->getOutput());
1326 $this->assertFalse($process->isRunning());
1327  
1328 $expectedOutput = array(
1329 array($process::OUT, '123'),
1330 array($process::ERR, '234'),
1331 array($process::OUT, '345'),
1332 array($process::ERR, '456'),
1333 );
1334 $this->assertSame($expectedOutput, $output);
1335 }
1336  
1337 public function testNonBlockingNorClearingIteratorOutput()
1338 {
1339 $input = new InputStream();
1340  
1341 $process = $this->getProcessForCode('fwrite(STDOUT, fread(STDIN, 3));');
1342 $process->setInput($input);
1343 $process->start();
1344 $output = array();
1345  
1346 foreach ($process->getIterator($process::ITER_NON_BLOCKING | $process::ITER_KEEP_OUTPUT) as $type => $data) {
1347 $output[] = array($type, $data);
1348 break;
1349 }
1350 $expectedOutput = array(
1351 array($process::OUT, ''),
1352 );
1353 $this->assertSame($expectedOutput, $output);
1354  
1355 $input->write(123);
1356  
1357 foreach ($process->getIterator($process::ITER_NON_BLOCKING | $process::ITER_KEEP_OUTPUT) as $type => $data) {
1358 if ('' !== $data) {
1359 $output[] = array($type, $data);
1360 }
1361 }
1362  
1363 $this->assertSame('123', $process->getOutput());
1364 $this->assertFalse($process->isRunning());
1365  
1366 $expectedOutput = array(
1367 array($process::OUT, ''),
1368 array($process::OUT, '123'),
1369 );
1370 $this->assertSame($expectedOutput, $output);
1371 }
1372  
1373 public function testChainedProcesses()
1374 {
1375 $p1 = $this->getProcessForCode('fwrite(STDERR, 123); fwrite(STDOUT, 456);');
1376 $p2 = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
1377 $p2->setInput($p1);
1378  
1379 $p1->start();
1380 $p2->run();
1381  
1382 $this->assertSame('123', $p1->getErrorOutput());
1383 $this->assertSame('', $p1->getOutput());
1384 $this->assertSame('', $p2->getErrorOutput());
1385 $this->assertSame('456', $p2->getOutput());
1386 }
1387  
1388 public function testSetBadEnv()
1389 {
1390 $process = $this->getProcess('echo hello');
1391 $process->setEnv(array('bad%%' => '123'));
1392 $process->inheritEnvironmentVariables(true);
1393  
1394 $process->run();
1395  
1396 $this->assertSame('hello'.PHP_EOL, $process->getOutput());
1397 $this->assertSame('', $process->getErrorOutput());
1398 }
1399  
1400 public function testEnvBackupDoesNotDeleteExistingVars()
1401 {
1402 putenv('existing_var=foo');
1403 $process = $this->getProcess('php -r "echo getenv(\'new_test_var\');"');
1404 $process->setEnv(array('existing_var' => 'bar', 'new_test_var' => 'foo'));
1405 $process->inheritEnvironmentVariables();
1406  
1407 $process->run();
1408  
1409 $this->assertSame('foo', $process->getOutput());
1410 $this->assertSame('foo', getenv('existing_var'));
1411 $this->assertFalse(getenv('new_test_var'));
1412 }
1413  
1414 public function testEnvIsInherited()
1415 {
1416 $process = $this->getProcessForCode('echo serialize($_SERVER);', null, array('BAR' => 'BAZ'));
1417  
1418 putenv('FOO=BAR');
1419  
1420 $process->run();
1421  
1422 $expected = array('BAR' => 'BAZ', 'FOO' => 'BAR');
1423 $env = array_intersect_key(unserialize($process->getOutput()), $expected);
1424  
1425 $this->assertEquals($expected, $env);
1426 }
1427  
1428 /**
1429 * @group legacy
1430 */
1431 public function testInheritEnvDisabled()
1432 {
1433 $process = $this->getProcessForCode('echo serialize($_SERVER);', null, array('BAR' => 'BAZ'));
1434  
1435 putenv('FOO=BAR');
1436  
1437 $this->assertSame($process, $process->inheritEnvironmentVariables(false));
1438 $this->assertFalse($process->areEnvironmentVariablesInherited());
1439  
1440 $process->run();
1441  
1442 $expected = array('BAR' => 'BAZ', 'FOO' => 'BAR');
1443 $env = array_intersect_key(unserialize($process->getOutput()), $expected);
1444 unset($expected['FOO']);
1445  
1446 $this->assertSame($expected, $env);
1447 }
1448  
1449 public function testGetCommandLine()
1450 {
1451 $p = new Process(array('/usr/bin/php'));
1452  
1453 $expected = '\\' === DIRECTORY_SEPARATOR ? '"/usr/bin/php"' : "'/usr/bin/php'";
1454 $this->assertSame($expected, $p->getCommandLine());
1455 }
1456  
1457 /**
1458 * @dataProvider provideEscapeArgument
1459 */
1460 public function testEscapeArgument($arg)
1461 {
1462 $p = new Process(array(self::$phpBin, '-r', 'echo $argv[1];', $arg));
1463 $p->run();
1464  
1465 $this->assertSame($arg, $p->getOutput());
1466 }
1467  
1468 /**
1469 * @dataProvider provideEscapeArgument
1470 * @group legacy
1471 */
1472 public function testEscapeArgumentWhenInheritEnvDisabled($arg)
1473 {
1474 $p = new Process(array(self::$phpBin, '-r', 'echo $argv[1];', $arg), null, array('BAR' => 'BAZ'));
1475 $p->inheritEnvironmentVariables(false);
1476 $p->run();
1477  
1478 $this->assertSame($arg, $p->getOutput());
1479 }
1480  
1481 public function provideEscapeArgument()
1482 {
1483 yield array('a"b%c%');
1484 yield array('a"b^c^');
1485 yield array("a\nb'c");
1486 yield array('a^b c!');
1487 yield array("a!b\tc");
1488 yield array('a\\\\"\\"');
1489 yield array('éÉèÈàÀöä');
1490 }
1491  
1492 public function testEnvArgument()
1493 {
1494 $env = array('FOO' => 'Foo', 'BAR' => 'Bar');
1495 $cmd = '\\' === DIRECTORY_SEPARATOR ? 'echo !FOO! !BAR! !BAZ!' : 'echo $FOO $BAR $BAZ';
1496 $p = new Process($cmd, null, $env);
1497 $p->run(null, array('BAR' => 'baR', 'BAZ' => 'baZ'));
1498  
1499 $this->assertSame('Foo baR baZ', rtrim($p->getOutput()));
1500 $this->assertSame($env, $p->getEnv());
1501 }
1502  
1503 /**
1504 * @param string $commandline
1505 * @param null|string $cwd
1506 * @param null|array $env
1507 * @param null|string $input
1508 * @param int $timeout
1509 * @param array $options
1510 *
1511 * @return Process
1512 */
1513 private function getProcess($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60)
1514 {
1515 $process = new Process($commandline, $cwd, $env, $input, $timeout);
1516 $process->inheritEnvironmentVariables();
1517  
1518 if (false !== $enhance = getenv('ENHANCE_SIGCHLD')) {
1519 try {
1520 $process->setEnhanceSigchildCompatibility(false);
1521 $process->getExitCode();
1522 $this->fail('ENHANCE_SIGCHLD must be used together with a sigchild-enabled PHP.');
1523 } catch (RuntimeException $e) {
1524 $this->assertSame('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.', $e->getMessage());
1525 if ($enhance) {
1526 $process->setEnhanceSigchildCompatibility(true);
1527 } else {
1528 self::$notEnhancedSigchild = true;
1529 }
1530 }
1531 }
1532  
1533 if (self::$process) {
1534 self::$process->stop(0);
1535 }
1536  
1537 return self::$process = $process;
1538 }
1539  
1540 /**
1541 * @return Process
1542 */
1543 private function getProcessForCode($code, $cwd = null, array $env = null, $input = null, $timeout = 60)
1544 {
1545 return $this->getProcess(array(self::$phpBin, '-r', $code), $cwd, $env, $input, $timeout);
1546 }
1547  
1548 private function skipIfNotEnhancedSigchild($expectException = true)
1549 {
1550 if (self::$sigchild) {
1551 if (!$expectException) {
1552 $this->markTestSkipped('PHP is compiled with --enable-sigchild.');
1553 } elseif (self::$notEnhancedSigchild) {
1554 if (method_exists($this, 'expectException')) {
1555 $this->expectException('Symfony\Component\Process\Exception\RuntimeException');
1556 $this->expectExceptionMessage('This PHP has been compiled with --enable-sigchild.');
1557 } else {
1558 $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild.');
1559 }
1560 }
1561 }
1562 }
1563 }
1564  
1565 class NonStringifiable
1566 {
1567 }