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