scratch – Blame information for rev 126

Subversion Repositories:
Rev:
Rev Author Line No. Line
126 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 /**
433 * @group tty
434 */
435 public function testTTYCommand()
436 {
437 if ('\\' === DIRECTORY_SEPARATOR) {
438 $this->markTestSkipped('Windows does not have /dev/tty support');
439 }
440  
441 $process = $this->getProcess('echo "foo" >> /dev/null && '.$this->getProcessForCode('usleep(100000);')->getCommandLine());
442 $process->setTty(true);
443 $process->start();
444 $this->assertTrue($process->isRunning());
445 $process->wait();
446  
447 $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
448 }
449  
450 /**
451 * @group tty
452 */
453 public function testTTYCommandExitCode()
454 {
455 if ('\\' === DIRECTORY_SEPARATOR) {
456 $this->markTestSkipped('Windows does have /dev/tty support');
457 }
458 $this->skipIfNotEnhancedSigchild();
459  
460 $process = $this->getProcess('echo "foo" >> /dev/null');
461 $process->setTty(true);
462 $process->run();
463  
464 $this->assertTrue($process->isSuccessful());
465 }
466  
467 /**
468 * @expectedException \Symfony\Component\Process\Exception\RuntimeException
469 * @expectedExceptionMessage TTY mode is not supported on Windows platform.
470 */
471 public function testTTYInWindowsEnvironment()
472 {
473 if ('\\' !== DIRECTORY_SEPARATOR) {
474 $this->markTestSkipped('This test is for Windows platform only');
475 }
476  
477 $process = $this->getProcess('echo "foo" >> /dev/null');
478 $process->setTty(false);
479 $process->setTty(true);
480 }
481  
482 public function testExitCodeTextIsNullWhenExitCodeIsNull()
483 {
484 $this->skipIfNotEnhancedSigchild();
485  
486 $process = $this->getProcess('');
487 $this->assertNull($process->getExitCodeText());
488 }
489  
490 public function testPTYCommand()
491 {
492 if (!Process::isPtySupported()) {
493 $this->markTestSkipped('PTY is not supported on this operating system.');
494 }
495  
496 $process = $this->getProcess('echo "foo"');
497 $process->setPty(true);
498 $process->run();
499  
500 $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
501 $this->assertEquals("foo\r\n", $process->getOutput());
502 }
503  
504 public function testMustRun()
505 {
506 $this->skipIfNotEnhancedSigchild();
507  
508 $process = $this->getProcess('echo foo');
509  
510 $this->assertSame($process, $process->mustRun());
511 $this->assertEquals('foo'.PHP_EOL, $process->getOutput());
512 }
513  
514 public function testSuccessfulMustRunHasCorrectExitCode()
515 {
516 $this->skipIfNotEnhancedSigchild();
517  
518 $process = $this->getProcess('echo foo')->mustRun();
519 $this->assertEquals(0, $process->getExitCode());
520 }
521  
522 /**
523 * @expectedException \Symfony\Component\Process\Exception\ProcessFailedException
524 */
525 public function testMustRunThrowsException()
526 {
527 $this->skipIfNotEnhancedSigchild();
528  
529 $process = $this->getProcess('exit 1');
530 $process->mustRun();
531 }
532  
533 public function testExitCodeText()
534 {
535 $this->skipIfNotEnhancedSigchild();
536  
537 $process = $this->getProcess('');
538 $r = new \ReflectionObject($process);
539 $p = $r->getProperty('exitcode');
540 $p->setAccessible(true);
541  
542 $p->setValue($process, 2);
543 $this->assertEquals('Misuse of shell builtins', $process->getExitCodeText());
544 }
545  
546 public function testStartIsNonBlocking()
547 {
548 $process = $this->getProcessForCode('usleep(500000);');
549 $start = microtime(true);
550 $process->start();
551 $end = microtime(true);
552 $this->assertLessThan(0.4, $end - $start);
553 $process->stop();
554 }
555  
556 public function testUpdateStatus()
557 {
558 $process = $this->getProcess('echo foo');
559 $process->run();
560 $this->assertTrue(strlen($process->getOutput()) > 0);
561 }
562  
563 public function testGetExitCodeIsNullOnStart()
564 {
565 $this->skipIfNotEnhancedSigchild();
566  
567 $process = $this->getProcessForCode('usleep(100000);');
568 $this->assertNull($process->getExitCode());
569 $process->start();
570 $this->assertNull($process->getExitCode());
571 $process->wait();
572 $this->assertEquals(0, $process->getExitCode());
573 }
574  
575 public function testGetExitCodeIsNullOnWhenStartingAgain()
576 {
577 $this->skipIfNotEnhancedSigchild();
578  
579 $process = $this->getProcessForCode('usleep(100000);');
580 $process->run();
581 $this->assertEquals(0, $process->getExitCode());
582 $process->start();
583 $this->assertNull($process->getExitCode());
584 $process->wait();
585 $this->assertEquals(0, $process->getExitCode());
586 }
587  
588 public function testGetExitCode()
589 {
590 $this->skipIfNotEnhancedSigchild();
591  
592 $process = $this->getProcess('echo foo');
593 $process->run();
594 $this->assertSame(0, $process->getExitCode());
595 }
596  
597 public function testStatus()
598 {
599 $process = $this->getProcessForCode('usleep(100000);');
600 $this->assertFalse($process->isRunning());
601 $this->assertFalse($process->isStarted());
602 $this->assertFalse($process->isTerminated());
603 $this->assertSame(Process::STATUS_READY, $process->getStatus());
604 $process->start();
605 $this->assertTrue($process->isRunning());
606 $this->assertTrue($process->isStarted());
607 $this->assertFalse($process->isTerminated());
608 $this->assertSame(Process::STATUS_STARTED, $process->getStatus());
609 $process->wait();
610 $this->assertFalse($process->isRunning());
611 $this->assertTrue($process->isStarted());
612 $this->assertTrue($process->isTerminated());
613 $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
614 }
615  
616 public function testStop()
617 {
618 $process = $this->getProcessForCode('sleep(31);');
619 $process->start();
620 $this->assertTrue($process->isRunning());
621 $process->stop();
622 $this->assertFalse($process->isRunning());
623 }
624  
625 public function testIsSuccessful()
626 {
627 $this->skipIfNotEnhancedSigchild();
628  
629 $process = $this->getProcess('echo foo');
630 $process->run();
631 $this->assertTrue($process->isSuccessful());
632 }
633  
634 public function testIsSuccessfulOnlyAfterTerminated()
635 {
636 $this->skipIfNotEnhancedSigchild();
637  
638 $process = $this->getProcessForCode('usleep(100000);');
639 $process->start();
640  
641 $this->assertFalse($process->isSuccessful());
642  
643 $process->wait();
644  
645 $this->assertTrue($process->isSuccessful());
646 }
647  
648 public function testIsNotSuccessful()
649 {
650 $this->skipIfNotEnhancedSigchild();
651  
652 $process = $this->getProcessForCode('throw new \Exception(\'BOUM\');');
653 $process->run();
654 $this->assertFalse($process->isSuccessful());
655 }
656  
657 public function testProcessIsNotSignaled()
658 {
659 if ('\\' === DIRECTORY_SEPARATOR) {
660 $this->markTestSkipped('Windows does not support POSIX signals');
661 }
662 $this->skipIfNotEnhancedSigchild();
663  
664 $process = $this->getProcess('echo foo');
665 $process->run();
666 $this->assertFalse($process->hasBeenSignaled());
667 }
668  
669 public function testProcessWithoutTermSignal()
670 {
671 if ('\\' === DIRECTORY_SEPARATOR) {
672 $this->markTestSkipped('Windows does not support POSIX signals');
673 }
674 $this->skipIfNotEnhancedSigchild();
675  
676 $process = $this->getProcess('echo foo');
677 $process->run();
678 $this->assertEquals(0, $process->getTermSignal());
679 }
680  
681 public function testProcessIsSignaledIfStopped()
682 {
683 if ('\\' === DIRECTORY_SEPARATOR) {
684 $this->markTestSkipped('Windows does not support POSIX signals');
685 }
686 $this->skipIfNotEnhancedSigchild();
687  
688 $process = $this->getProcessForCode('sleep(32);');
689 $process->start();
690 $process->stop();
691 $this->assertTrue($process->hasBeenSignaled());
692 $this->assertEquals(15, $process->getTermSignal()); // SIGTERM
693 }
694  
695 /**
696 * @expectedException \Symfony\Component\Process\Exception\RuntimeException
697 * @expectedExceptionMessage The process has been signaled
698 */
699 public function testProcessThrowsExceptionWhenExternallySignaled()
700 {
701 if (!function_exists('posix_kill')) {
702 $this->markTestSkipped('Function posix_kill is required.');
703 }
704 $this->skipIfNotEnhancedSigchild(false);
705  
706 $process = $this->getProcessForCode('sleep(32.1);');
707 $process->start();
708 posix_kill($process->getPid(), 9); // SIGKILL
709  
710 $process->wait();
711 }
712  
713 public function testRestart()
714 {
715 $process1 = $this->getProcessForCode('echo getmypid();');
716 $process1->run();
717 $process2 = $process1->restart();
718  
719 $process2->wait(); // wait for output
720  
721 // Ensure that both processed finished and the output is numeric
722 $this->assertFalse($process1->isRunning());
723 $this->assertFalse($process2->isRunning());
724 $this->assertInternalType('numeric', $process1->getOutput());
725 $this->assertInternalType('numeric', $process2->getOutput());
726  
727 // Ensure that restart returned a new process by check that the output is different
728 $this->assertNotEquals($process1->getOutput(), $process2->getOutput());
729 }
730  
731 /**
732 * @expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException
733 * @expectedExceptionMessage exceeded the timeout of 0.1 seconds.
734 */
735 public function testRunProcessWithTimeout()
736 {
737 $process = $this->getProcessForCode('sleep(30);');
738 $process->setTimeout(0.1);
739 $start = microtime(true);
740 try {
741 $process->run();
742 $this->fail('A RuntimeException should have been raised');
743 } catch (RuntimeException $e) {
744 }
745  
746 $this->assertLessThan(15, microtime(true) - $start);
747  
748 throw $e;
749 }
750  
751 /**
752 * @expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException
753 * @expectedExceptionMessage exceeded the timeout of 0.1 seconds.
754 */
755 public function testIterateOverProcessWithTimeout()
756 {
757 $process = $this->getProcessForCode('sleep(30);');
758 $process->setTimeout(0.1);
759 $start = microtime(true);
760 try {
761 $process->start();
762 foreach ($process as $buffer);
763 $this->fail('A RuntimeException should have been raised');
764 } catch (RuntimeException $e) {
765 }
766  
767 $this->assertLessThan(15, microtime(true) - $start);
768  
769 throw $e;
770 }
771  
772 public function testCheckTimeoutOnNonStartedProcess()
773 {
774 $process = $this->getProcess('echo foo');
775 $this->assertNull($process->checkTimeout());
776 }
777  
778 public function testCheckTimeoutOnTerminatedProcess()
779 {
780 $process = $this->getProcess('echo foo');
781 $process->run();
782 $this->assertNull($process->checkTimeout());
783 }
784  
785 /**
786 * @expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException
787 * @expectedExceptionMessage exceeded the timeout of 0.1 seconds.
788 */
789 public function testCheckTimeoutOnStartedProcess()
790 {
791 $process = $this->getProcessForCode('sleep(33);');
792 $process->setTimeout(0.1);
793  
794 $process->start();
795 $start = microtime(true);
796  
797 try {
798 while ($process->isRunning()) {
799 $process->checkTimeout();
800 usleep(100000);
801 }
802 $this->fail('A ProcessTimedOutException should have been raised');
803 } catch (ProcessTimedOutException $e) {
804 }
805  
806 $this->assertLessThan(15, microtime(true) - $start);
807  
808 throw $e;
809 }
810  
811 public function testIdleTimeout()
812 {
813 $process = $this->getProcessForCode('sleep(34);');
814 $process->setTimeout(60);
815 $process->setIdleTimeout(0.1);
816  
817 try {
818 $process->run();
819  
820 $this->fail('A timeout exception was expected.');
821 } catch (ProcessTimedOutException $e) {
822 $this->assertTrue($e->isIdleTimeout());
823 $this->assertFalse($e->isGeneralTimeout());
824 $this->assertEquals(0.1, $e->getExceededTimeout());
825 }
826 }
827  
828 public function testIdleTimeoutNotExceededWhenOutputIsSent()
829 {
830 $process = $this->getProcessForCode('while (true) {echo \'foo \'; usleep(1000);}');
831 $process->setTimeout(1);
832 $process->start();
833  
834 while (false === strpos($process->getOutput(), 'foo')) {
835 usleep(1000);
836 }
837  
838 $process->setIdleTimeout(0.5);
839  
840 try {
841 $process->wait();
842 $this->fail('A timeout exception was expected.');
843 } catch (ProcessTimedOutException $e) {
844 $this->assertTrue($e->isGeneralTimeout(), 'A general timeout is expected.');
845 $this->assertFalse($e->isIdleTimeout(), 'No idle timeout is expected.');
846 $this->assertEquals(1, $e->getExceededTimeout());
847 }
848 }
849  
850 /**
851 * @expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException
852 * @expectedExceptionMessage exceeded the timeout of 0.1 seconds.
853 */
854 public function testStartAfterATimeout()
855 {
856 $process = $this->getProcessForCode('sleep(35);');
857 $process->setTimeout(0.1);
858  
859 try {
860 $process->run();
861 $this->fail('A ProcessTimedOutException should have been raised.');
862 } catch (ProcessTimedOutException $e) {
863 }
864 $this->assertFalse($process->isRunning());
865 $process->start();
866 $this->assertTrue($process->isRunning());
867 $process->stop(0);
868  
869 throw $e;
870 }
871  
872 public function testGetPid()
873 {
874 $process = $this->getProcessForCode('sleep(36);');
875 $process->start();
876 $this->assertGreaterThan(0, $process->getPid());
877 $process->stop(0);
878 }
879  
880 public function testGetPidIsNullBeforeStart()
881 {
882 $process = $this->getProcess('foo');
883 $this->assertNull($process->getPid());
884 }
885  
886 public function testGetPidIsNullAfterRun()
887 {
888 $process = $this->getProcess('echo foo');
889 $process->run();
890 $this->assertNull($process->getPid());
891 }
892  
893 /**
894 * @requires extension pcntl
895 */
896 public function testSignal()
897 {
898 $process = $this->getProcess(array(self::$phpBin, __DIR__.'/SignalListener.php'));
899 $process->start();
900  
901 while (false === strpos($process->getOutput(), 'Caught')) {
902 usleep(1000);
903 }
904 $process->signal(SIGUSR1);
905 $process->wait();
906  
907 $this->assertEquals('Caught SIGUSR1', $process->getOutput());
908 }
909  
910 /**
911 * @requires extension pcntl
912 */
913 public function testExitCodeIsAvailableAfterSignal()
914 {
915 $this->skipIfNotEnhancedSigchild();
916  
917 $process = $this->getProcess('sleep 4');
918 $process->start();
919 $process->signal(SIGKILL);
920  
921 while ($process->isRunning()) {
922 usleep(10000);
923 }
924  
925 $this->assertFalse($process->isRunning());
926 $this->assertTrue($process->hasBeenSignaled());
927 $this->assertFalse($process->isSuccessful());
928 $this->assertEquals(137, $process->getExitCode());
929 }
930  
931 /**
932 * @expectedException \Symfony\Component\Process\Exception\LogicException
933 * @expectedExceptionMessage Can not send signal on a non running process.
934 */
935 public function testSignalProcessNotRunning()
936 {
937 $process = $this->getProcess('foo');
938 $process->signal(1); // SIGHUP
939 }
940  
941 /**
942 * @dataProvider provideMethodsThatNeedARunningProcess
943 */
944 public function testMethodsThatNeedARunningProcess($method)
945 {
946 $process = $this->getProcess('foo');
947  
948 if (method_exists($this, 'expectException')) {
949 $this->expectException('Symfony\Component\Process\Exception\LogicException');
950 $this->expectExceptionMessage(sprintf('Process must be started before calling %s.', $method));
951 } else {
952 $this->setExpectedException('Symfony\Component\Process\Exception\LogicException', sprintf('Process must be started before calling %s.', $method));
953 }
954  
955 $process->{$method}();
956 }
957  
958 public function provideMethodsThatNeedARunningProcess()
959 {
960 return array(
961 array('getOutput'),
962 array('getIncrementalOutput'),
963 array('getErrorOutput'),
964 array('getIncrementalErrorOutput'),
965 array('wait'),
966 );
967 }
968  
969 /**
970 * @dataProvider provideMethodsThatNeedATerminatedProcess
971 * @expectedException \Symfony\Component\Process\Exception\LogicException
972 * @expectedExceptionMessage Process must be terminated before calling
973 */
974 public function testMethodsThatNeedATerminatedProcess($method)
975 {
976 $process = $this->getProcessForCode('sleep(37);');
977 $process->start();
978 try {
979 $process->{$method}();
980 $process->stop(0);
981 $this->fail('A LogicException must have been thrown');
982 } catch (\Exception $e) {
983 }
984 $process->stop(0);
985  
986 throw $e;
987 }
988  
989 public function provideMethodsThatNeedATerminatedProcess()
990 {
991 return array(
992 array('hasBeenSignaled'),
993 array('getTermSignal'),
994 array('hasBeenStopped'),
995 array('getStopSignal'),
996 );
997 }
998  
999 /**
1000 * @dataProvider provideWrongSignal
1001 * @expectedException \Symfony\Component\Process\Exception\RuntimeException
1002 */
1003 public function testWrongSignal($signal)
1004 {
1005 if ('\\' === DIRECTORY_SEPARATOR) {
1006 $this->markTestSkipped('POSIX signals do not work on Windows');
1007 }
1008  
1009 $process = $this->getProcessForCode('sleep(38);');
1010 $process->start();
1011 try {
1012 $process->signal($signal);
1013 $this->fail('A RuntimeException must have been thrown');
1014 } catch (RuntimeException $e) {
1015 $process->stop(0);
1016 }
1017  
1018 throw $e;
1019 }
1020  
1021 public function provideWrongSignal()
1022 {
1023 return array(
1024 array(-4),
1025 array('Céphalopodes'),
1026 );
1027 }
1028  
1029 public function testDisableOutputDisablesTheOutput()
1030 {
1031 $p = $this->getProcess('foo');
1032 $this->assertFalse($p->isOutputDisabled());
1033 $p->disableOutput();
1034 $this->assertTrue($p->isOutputDisabled());
1035 $p->enableOutput();
1036 $this->assertFalse($p->isOutputDisabled());
1037 }
1038  
1039 /**
1040 * @expectedException \Symfony\Component\Process\Exception\RuntimeException
1041 * @expectedExceptionMessage Disabling output while the process is running is not possible.
1042 */
1043 public function testDisableOutputWhileRunningThrowsException()
1044 {
1045 $p = $this->getProcessForCode('sleep(39);');
1046 $p->start();
1047 $p->disableOutput();
1048 }
1049  
1050 /**
1051 * @expectedException \Symfony\Component\Process\Exception\RuntimeException
1052 * @expectedExceptionMessage Enabling output while the process is running is not possible.
1053 */
1054 public function testEnableOutputWhileRunningThrowsException()
1055 {
1056 $p = $this->getProcessForCode('sleep(40);');
1057 $p->disableOutput();
1058 $p->start();
1059 $p->enableOutput();
1060 }
1061  
1062 public function testEnableOrDisableOutputAfterRunDoesNotThrowException()
1063 {
1064 $p = $this->getProcess('echo foo');
1065 $p->disableOutput();
1066 $p->run();
1067 $p->enableOutput();
1068 $p->disableOutput();
1069 $this->assertTrue($p->isOutputDisabled());
1070 }
1071  
1072 /**
1073 * @expectedException \Symfony\Component\Process\Exception\LogicException
1074 * @expectedExceptionMessage Output can not be disabled while an idle timeout is set.
1075 */
1076 public function testDisableOutputWhileIdleTimeoutIsSet()
1077 {
1078 $process = $this->getProcess('foo');
1079 $process->setIdleTimeout(1);
1080 $process->disableOutput();
1081 }
1082  
1083 /**
1084 * @expectedException \Symfony\Component\Process\Exception\LogicException
1085 * @expectedExceptionMessage timeout can not be set while the output is disabled.
1086 */
1087 public function testSetIdleTimeoutWhileOutputIsDisabled()
1088 {
1089 $process = $this->getProcess('foo');
1090 $process->disableOutput();
1091 $process->setIdleTimeout(1);
1092 }
1093  
1094 public function testSetNullIdleTimeoutWhileOutputIsDisabled()
1095 {
1096 $process = $this->getProcess('foo');
1097 $process->disableOutput();
1098 $this->assertSame($process, $process->setIdleTimeout(null));
1099 }
1100  
1101 /**
1102 * @dataProvider provideOutputFetchingMethods
1103 * @expectedException \Symfony\Component\Process\Exception\LogicException
1104 * @expectedExceptionMessage Output has been disabled.
1105 */
1106 public function testGetOutputWhileDisabled($fetchMethod)
1107 {
1108 $p = $this->getProcessForCode('sleep(41);');
1109 $p->disableOutput();
1110 $p->start();
1111 $p->{$fetchMethod}();
1112 }
1113  
1114 public function provideOutputFetchingMethods()
1115 {
1116 return array(
1117 array('getOutput'),
1118 array('getIncrementalOutput'),
1119 array('getErrorOutput'),
1120 array('getIncrementalErrorOutput'),
1121 );
1122 }
1123  
1124 public function testStopTerminatesProcessCleanly()
1125 {
1126 $process = $this->getProcessForCode('echo 123; sleep(42);');
1127 $process->run(function () use ($process) {
1128 $process->stop();
1129 });
1130 $this->assertTrue(true, 'A call to stop() is not expected to cause wait() to throw a RuntimeException');
1131 }
1132  
1133 public function testKillSignalTerminatesProcessCleanly()
1134 {
1135 $process = $this->getProcessForCode('echo 123; sleep(43);');
1136 $process->run(function () use ($process) {
1137 $process->signal(9); // SIGKILL
1138 });
1139 $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException');
1140 }
1141  
1142 public function testTermSignalTerminatesProcessCleanly()
1143 {
1144 $process = $this->getProcessForCode('echo 123; sleep(44);');
1145 $process->run(function () use ($process) {
1146 $process->signal(15); // SIGTERM
1147 });
1148 $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException');
1149 }
1150  
1151 public function responsesCodeProvider()
1152 {
1153 return array(
1154 //expected output / getter / code to execute
1155 //array(1,'getExitCode','exit(1);'),
1156 //array(true,'isSuccessful','exit();'),
1157 array('output', 'getOutput', 'echo \'output\';'),
1158 );
1159 }
1160  
1161 public function pipesCodeProvider()
1162 {
1163 $variations = array(
1164 'fwrite(STDOUT, $in = file_get_contents(\'php://stdin\')); fwrite(STDERR, $in);',
1165 'include \''.__DIR__.'/PipeStdinInStdoutStdErrStreamSelect.php\';',
1166 );
1167  
1168 if ('\\' === DIRECTORY_SEPARATOR) {
1169 // Avoid XL buffers on Windows because of https://bugs.php.net/bug.php?id=65650
1170 $sizes = array(1, 2, 4, 8);
1171 } else {
1172 $sizes = array(1, 16, 64, 1024, 4096);
1173 }
1174  
1175 $codes = array();
1176 foreach ($sizes as $size) {
1177 foreach ($variations as $code) {
1178 $codes[] = array($code, $size);
1179 }
1180 }
1181  
1182 return $codes;
1183 }
1184  
1185 /**
1186 * @dataProvider provideVariousIncrementals
1187 */
1188 public function testIncrementalOutputDoesNotRequireAnotherCall($stream, $method)
1189 {
1190 $process = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\''.$stream.'\', $n, 1); $n++; usleep(1000); }', null, null, null, null);
1191 $process->start();
1192 $result = '';
1193 $limit = microtime(true) + 3;
1194 $expected = '012';
1195  
1196 while ($result !== $expected && microtime(true) < $limit) {
1197 $result .= $process->$method();
1198 }
1199  
1200 $this->assertSame($expected, $result);
1201 $process->stop();
1202 }
1203  
1204 public function provideVariousIncrementals()
1205 {
1206 return array(
1207 array('php://stdout', 'getIncrementalOutput'),
1208 array('php://stderr', 'getIncrementalErrorOutput'),
1209 );
1210 }
1211  
1212 public function testIteratorInput()
1213 {
1214 $input = function () {
1215 yield 'ping';
1216 yield 'pong';
1217 };
1218  
1219 $process = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);', null, null, $input());
1220 $process->run();
1221 $this->assertSame('pingpong', $process->getOutput());
1222 }
1223  
1224 public function testSimpleInputStream()
1225 {
1226 $input = new InputStream();
1227  
1228 $process = $this->getProcessForCode('echo \'ping\'; stream_copy_to_stream(STDIN, STDOUT);');
1229 $process->setInput($input);
1230  
1231 $process->start(function ($type, $data) use ($input) {
1232 if ('ping' === $data) {
1233 $input->write('pang');
1234 } elseif (!$input->isClosed()) {
1235 $input->write('pong');
1236 $input->close();
1237 }
1238 });
1239  
1240 $process->wait();
1241 $this->assertSame('pingpangpong', $process->getOutput());
1242 }
1243  
1244 public function testInputStreamWithCallable()
1245 {
1246 $i = 0;
1247 $stream = fopen('php://memory', 'w+');
1248 $stream = function () use ($stream, &$i) {
1249 if ($i < 3) {
1250 rewind($stream);
1251 fwrite($stream, ++$i);
1252 rewind($stream);
1253  
1254 return $stream;
1255 }
1256 };
1257  
1258 $input = new InputStream();
1259 $input->onEmpty($stream);
1260 $input->write($stream());
1261  
1262 $process = $this->getProcessForCode('echo fread(STDIN, 3);');
1263 $process->setInput($input);
1264 $process->start(function ($type, $data) use ($input) {
1265 $input->close();
1266 });
1267  
1268 $process->wait();
1269 $this->assertSame('123', $process->getOutput());
1270 }
1271  
1272 public function testInputStreamWithGenerator()
1273 {
1274 $input = new InputStream();
1275 $input->onEmpty(function ($input) {
1276 yield 'pong';
1277 $input->close();
1278 });
1279  
1280 $process = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
1281 $process->setInput($input);
1282 $process->start();
1283 $input->write('ping');
1284 $process->wait();
1285 $this->assertSame('pingpong', $process->getOutput());
1286 }
1287  
1288 public function testInputStreamOnEmpty()
1289 {
1290 $i = 0;
1291 $input = new InputStream();
1292 $input->onEmpty(function () use (&$i) { ++$i; });
1293  
1294 $process = $this->getProcessForCode('echo 123; echo fread(STDIN, 1); echo 456;');
1295 $process->setInput($input);
1296 $process->start(function ($type, $data) use ($input) {
1297 if ('123' === $data) {
1298 $input->close();
1299 }
1300 });
1301 $process->wait();
1302  
1303 $this->assertSame(0, $i, 'InputStream->onEmpty callback should be called only when the input *becomes* empty');
1304 $this->assertSame('123456', $process->getOutput());
1305 }
1306  
1307 public function testIteratorOutput()
1308 {
1309 $input = new InputStream();
1310  
1311 $process = $this->getProcessForCode('fwrite(STDOUT, 123); fwrite(STDERR, 234); flush(); usleep(10000); fwrite(STDOUT, fread(STDIN, 3)); fwrite(STDERR, 456);');
1312 $process->setInput($input);
1313 $process->start();
1314 $output = array();
1315  
1316 foreach ($process as $type => $data) {
1317 $output[] = array($type, $data);
1318 break;
1319 }
1320 $expectedOutput = array(
1321 array($process::OUT, '123'),
1322 );
1323 $this->assertSame($expectedOutput, $output);
1324  
1325 $input->write(345);
1326  
1327 foreach ($process as $type => $data) {
1328 $output[] = array($type, $data);
1329 }
1330  
1331 $this->assertSame('', $process->getOutput());
1332 $this->assertFalse($process->isRunning());
1333  
1334 $expectedOutput = array(
1335 array($process::OUT, '123'),
1336 array($process::ERR, '234'),
1337 array($process::OUT, '345'),
1338 array($process::ERR, '456'),
1339 );
1340 $this->assertSame($expectedOutput, $output);
1341 }
1342  
1343 public function testNonBlockingNorClearingIteratorOutput()
1344 {
1345 $input = new InputStream();
1346  
1347 $process = $this->getProcessForCode('fwrite(STDOUT, fread(STDIN, 3));');
1348 $process->setInput($input);
1349 $process->start();
1350 $output = array();
1351  
1352 foreach ($process->getIterator($process::ITER_NON_BLOCKING | $process::ITER_KEEP_OUTPUT) as $type => $data) {
1353 $output[] = array($type, $data);
1354 break;
1355 }
1356 $expectedOutput = array(
1357 array($process::OUT, ''),
1358 );
1359 $this->assertSame($expectedOutput, $output);
1360  
1361 $input->write(123);
1362  
1363 foreach ($process->getIterator($process::ITER_NON_BLOCKING | $process::ITER_KEEP_OUTPUT) as $type => $data) {
1364 if ('' !== $data) {
1365 $output[] = array($type, $data);
1366 }
1367 }
1368  
1369 $this->assertSame('123', $process->getOutput());
1370 $this->assertFalse($process->isRunning());
1371  
1372 $expectedOutput = array(
1373 array($process::OUT, ''),
1374 array($process::OUT, '123'),
1375 );
1376 $this->assertSame($expectedOutput, $output);
1377 }
1378  
1379 public function testChainedProcesses()
1380 {
1381 $p1 = $this->getProcessForCode('fwrite(STDERR, 123); fwrite(STDOUT, 456);');
1382 $p2 = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
1383 $p2->setInput($p1);
1384  
1385 $p1->start();
1386 $p2->run();
1387  
1388 $this->assertSame('123', $p1->getErrorOutput());
1389 $this->assertSame('', $p1->getOutput());
1390 $this->assertSame('', $p2->getErrorOutput());
1391 $this->assertSame('456', $p2->getOutput());
1392 }
1393  
1394 public function testSetBadEnv()
1395 {
1396 $process = $this->getProcess('echo hello');
1397 $process->setEnv(array('bad%%' => '123'));
1398 $process->inheritEnvironmentVariables(true);
1399  
1400 $process->run();
1401  
1402 $this->assertSame('hello'.PHP_EOL, $process->getOutput());
1403 $this->assertSame('', $process->getErrorOutput());
1404 }
1405  
1406 public function testEnvBackupDoesNotDeleteExistingVars()
1407 {
1408 putenv('existing_var=foo');
1409 $process = $this->getProcess('php -r "echo getenv(\'new_test_var\');"');
1410 $process->setEnv(array('existing_var' => 'bar', 'new_test_var' => 'foo'));
1411 $process->inheritEnvironmentVariables();
1412  
1413 $process->run();
1414  
1415 $this->assertSame('foo', $process->getOutput());
1416 $this->assertSame('foo', getenv('existing_var'));
1417 $this->assertFalse(getenv('new_test_var'));
1418 }
1419  
1420 public function testEnvIsInherited()
1421 {
1422 $process = $this->getProcessForCode('echo serialize($_SERVER);', null, array('BAR' => 'BAZ'));
1423  
1424 putenv('FOO=BAR');
1425  
1426 $process->run();
1427  
1428 $expected = array('BAR' => 'BAZ', 'FOO' => 'BAR');
1429 $env = array_intersect_key(unserialize($process->getOutput()), $expected);
1430  
1431 $this->assertEquals($expected, $env);
1432 }
1433  
1434 /**
1435 * @group legacy
1436 */
1437 public function testInheritEnvDisabled()
1438 {
1439 $process = $this->getProcessForCode('echo serialize($_SERVER);', null, array('BAR' => 'BAZ'));
1440  
1441 putenv('FOO=BAR');
1442  
1443 $this->assertSame($process, $process->inheritEnvironmentVariables(false));
1444 $this->assertFalse($process->areEnvironmentVariablesInherited());
1445  
1446 $process->run();
1447  
1448 $expected = array('BAR' => 'BAZ', 'FOO' => 'BAR');
1449 $env = array_intersect_key(unserialize($process->getOutput()), $expected);
1450 unset($expected['FOO']);
1451  
1452 $this->assertSame($expected, $env);
1453 }
1454  
1455 public function testGetCommandLine()
1456 {
1457 $p = new Process(array('/usr/bin/php'));
1458  
1459 $expected = '\\' === DIRECTORY_SEPARATOR ? '"/usr/bin/php"' : "'/usr/bin/php'";
1460 $this->assertSame($expected, $p->getCommandLine());
1461 }
1462  
1463 /**
1464 * @dataProvider provideEscapeArgument
1465 */
1466 public function testEscapeArgument($arg)
1467 {
1468 $p = new Process(array(self::$phpBin, '-r', 'echo $argv[1];', $arg));
1469 $p->run();
1470  
1471 $this->assertSame($arg, $p->getOutput());
1472 }
1473  
1474 /**
1475 * @dataProvider provideEscapeArgument
1476 * @group legacy
1477 */
1478 public function testEscapeArgumentWhenInheritEnvDisabled($arg)
1479 {
1480 $p = new Process(array(self::$phpBin, '-r', 'echo $argv[1];', $arg), null, array('BAR' => 'BAZ'));
1481 $p->inheritEnvironmentVariables(false);
1482 $p->run();
1483  
1484 $this->assertSame($arg, $p->getOutput());
1485 }
1486  
1487 public function provideEscapeArgument()
1488 {
1489 yield array('a"b%c%');
1490 yield array('a"b^c^');
1491 yield array("a\nb'c");
1492 yield array('a^b c!');
1493 yield array("a!b\tc");
1494 yield array('a\\\\"\\"');
1495 yield array('éÉèÈàÀöä');
1496 }
1497  
1498 public function testEnvArgument()
1499 {
1500 $env = array('FOO' => 'Foo', 'BAR' => 'Bar');
1501 $cmd = '\\' === DIRECTORY_SEPARATOR ? 'echo !FOO! !BAR! !BAZ!' : 'echo $FOO $BAR $BAZ';
1502 $p = new Process($cmd, null, $env);
1503 $p->run(null, array('BAR' => 'baR', 'BAZ' => 'baZ'));
1504  
1505 $this->assertSame('Foo baR baZ', rtrim($p->getOutput()));
1506 $this->assertSame($env, $p->getEnv());
1507 }
1508  
1509 /**
1510 * @param string $commandline
1511 * @param null|string $cwd
1512 * @param null|array $env
1513 * @param null|string $input
1514 * @param int $timeout
1515 * @param array $options
1516 *
1517 * @return Process
1518 */
1519 private function getProcess($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60)
1520 {
1521 $process = new Process($commandline, $cwd, $env, $input, $timeout);
1522 $process->inheritEnvironmentVariables();
1523  
1524 if (false !== $enhance = getenv('ENHANCE_SIGCHLD')) {
1525 try {
1526 $process->setEnhanceSigchildCompatibility(false);
1527 $process->getExitCode();
1528 $this->fail('ENHANCE_SIGCHLD must be used together with a sigchild-enabled PHP.');
1529 } catch (RuntimeException $e) {
1530 $this->assertSame('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.', $e->getMessage());
1531 if ($enhance) {
1532 $process->setEnhanceSigchildCompatibility(true);
1533 } else {
1534 self::$notEnhancedSigchild = true;
1535 }
1536 }
1537 }
1538  
1539 if (self::$process) {
1540 self::$process->stop(0);
1541 }
1542  
1543 return self::$process = $process;
1544 }
1545  
1546 /**
1547 * @return Process
1548 */
1549 private function getProcessForCode($code, $cwd = null, array $env = null, $input = null, $timeout = 60)
1550 {
1551 return $this->getProcess(array(self::$phpBin, '-r', $code), $cwd, $env, $input, $timeout);
1552 }
1553  
1554 private function skipIfNotEnhancedSigchild($expectException = true)
1555 {
1556 if (self::$sigchild) {
1557 if (!$expectException) {
1558 $this->markTestSkipped('PHP is compiled with --enable-sigchild.');
1559 } elseif (self::$notEnhancedSigchild) {
1560 if (method_exists($this, 'expectException')) {
1561 $this->expectException('Symfony\Component\Process\Exception\RuntimeException');
1562 $this->expectExceptionMessage('This PHP has been compiled with --enable-sigchild.');
1563 } else {
1564 $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild.');
1565 }
1566 }
1567 }
1568 }
1569 }
1570  
1571 class NonStringifiable
1572 {
1573 }