PluginProbe
UpStream: a Project Management Plugin for WordPress / trunk
UpStream: a Project Management Plugin for WordPress vtrunk
trunk 1.39.0 1.39.1 1.39.2 1.39.3 2.0.7 2.1.0
upstream / vendor / symfony / process / Tests / ProcessTest.php

ProcessTest.php in UpStream: a Project Management Plugin for WordPress trunk, at vendor/symfony/process/Tests/ProcessTest.php

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