PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 3.2.5
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v3.2.5
4.7.0 4.6.1 4.6.0 4.5.5 4.5.4 4.5.3 4.5.2 3.2.20 3.2.21 3.2.22 3.2.3 3.2.5 3.2.6 3.2.7 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.7.0 3.7.1 All 111 releases
wp-optimize / vendor / symfony / process / Tests / ProcessTest.php

ProcessTest.php in WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance 3.2.5, 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 <[email protected]>
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 <[email protected]>
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