Verzeichnisstruktur phpBB-3.3.15
- Veröffentlicht
- 28.08.2024
So funktioniert es
|
Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück |
Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis. |
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
ProcessTest.php
0001 <?php
0002
0003 /*
0004 * This file is part of the Symfony package.
0005 *
0006 * (c) Fabien Potencier <fabien@symfony.com>
0007 *
0008 * For the full copyright and license information, please view the LICENSE
0009 * file that was distributed with this source code.
0010 */
0011
0012 namespace Symfony\Component\Process\Tests;
0013
0014 use PHPUnit\Framework\TestCase;
0015 use Symfony\Component\Process\Exception\LogicException;
0016 use Symfony\Component\Process\Exception\ProcessTimedOutException;
0017 use Symfony\Component\Process\Exception\RuntimeException;
0018 use Symfony\Component\Process\InputStream;
0019 use Symfony\Component\Process\PhpExecutableFinder;
0020 use Symfony\Component\Process\Pipes\PipesInterface;
0021 use Symfony\Component\Process\Process;
0022
0023 /**
0024 * @author Robert Schönthal <seroscho@googlemail.com>
0025 */
0026 class ProcessTest extends TestCase
0027 {
0028 private static $phpBin;
0029 private static $process;
0030 private static $sigchild;
0031 private static $notEnhancedSigchild = false;
0032
0033 public static function setUpBeforeClass()
0034 {
0035 $phpBin = new PhpExecutableFinder();
0036 self::$phpBin = getenv('SYMFONY_PROCESS_PHP_TEST_BINARY') ?: ('phpdbg' === \PHP_SAPI ? 'php' : $phpBin->find());
0037
0038 ob_start();
0039 phpinfo(\INFO_GENERAL);
0040 self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
0041 }
0042
0043 protected function tearDown()
0044 {
0045 if (self::$process) {
0046 self::$process->stop(0);
0047 self::$process = null;
0048 }
0049 }
0050
0051 /**
0052 * @group legacy
0053 * @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.
0054 */
0055 public function testInvalidCwd()
0056 {
0057 if ('\\' === \DIRECTORY_SEPARATOR) {
0058 $this->markTestSkipped('False-positive on Windows/appveyor.');
0059 }
0060
0061 // Check that it works fine if the CWD exists
0062 $cmd = new Process('echo test', __DIR__);
0063 $cmd->run();
0064
0065 $cmd = new Process('echo test', __DIR__.'/notfound/');
0066 $cmd->run();
0067 }
0068
0069 public function testThatProcessDoesNotThrowWarningDuringRun()
0070 {
0071 if ('\\' === \DIRECTORY_SEPARATOR) {
0072 $this->markTestSkipped('This test is transient on Windows');
0073 }
0074 @trigger_error('Test Error', \E_USER_NOTICE);
0075 $process = $this->getProcessForCode('sleep(3)');
0076 $process->run();
0077 $actualError = error_get_last();
0078 $this->assertEquals('Test Error', $actualError['message']);
0079 $this->assertEquals(\E_USER_NOTICE, $actualError['type']);
0080 }
0081
0082 public function testNegativeTimeoutFromConstructor()
0083 {
0084 $this->expectException('Symfony\Component\Process\Exception\InvalidArgumentException');
0085 $this->getProcess('', null, null, null, -1);
0086 }
0087
0088 public function testNegativeTimeoutFromSetter()
0089 {
0090 $this->expectException('Symfony\Component\Process\Exception\InvalidArgumentException');
0091 $p = $this->getProcess('');
0092 $p->setTimeout(-1);
0093 }
0094
0095 public function testFloatAndNullTimeout()
0096 {
0097 $p = $this->getProcess('');
0098
0099 $p->setTimeout(10);
0100 $this->assertSame(10.0, $p->getTimeout());
0101
0102 $p->setTimeout(null);
0103 $this->assertNull($p->getTimeout());
0104
0105 $p->setTimeout(0.0);
0106 $this->assertNull($p->getTimeout());
0107 }
0108
0109 /**
0110 * @requires extension pcntl
0111 */
0112 public function testStopWithTimeoutIsActuallyWorking()
0113 {
0114 $p = $this->getProcess([self::$phpBin, __DIR__.'/NonStopableProcess.php', 30]);
0115 $p->start();
0116
0117 while (false === strpos($p->getOutput(), 'received')) {
0118 usleep(1000);
0119 }
0120 $start = microtime(true);
0121 $p->stop(0.1);
0122
0123 $p->wait();
0124
0125 $this->assertLessThan(15, microtime(true) - $start);
0126 }
0127
0128 public function testAllOutputIsActuallyReadOnTermination()
0129 {
0130 // this code will result in a maximum of 2 reads of 8192 bytes by calling
0131 // start() and isRunning(). by the time getOutput() is called the process
0132 // has terminated so the internal pipes array is already empty. normally
0133 // the call to start() will not read any data as the process will not have
0134 // generated output, but this is non-deterministic so we must count it as
0135 // a possibility. therefore we need 2 * PipesInterface::CHUNK_SIZE plus
0136 // another byte which will never be read.
0137 $expectedOutputSize = PipesInterface::CHUNK_SIZE * 2 + 2;
0138
0139 $code = sprintf('echo str_repeat(\'*\', %d);', $expectedOutputSize);
0140 $p = $this->getProcessForCode($code);
0141
0142 $p->start();
0143
0144 // Don't call Process::run nor Process::wait to avoid any read of pipes
0145 $h = new \ReflectionProperty($p, 'process');
0146 $h->setAccessible(true);
0147 $h = $h->getValue($p);
0148 $s = @proc_get_status($h);
0149
0150 while (!empty($s['running'])) {
0151 usleep(1000);
0152 $s = proc_get_status($h);
0153 }
0154
0155 $o = $p->getOutput();
0156
0157 $this->assertEquals($expectedOutputSize, \strlen($o));
0158 }
0159
0160 public function testCallbacksAreExecutedWithStart()
0161 {
0162 $process = $this->getProcess('echo foo');
0163 $process->start(function ($type, $buffer) use (&$data) {
0164 $data .= $buffer;
0165 });
0166
0167 $process->wait();
0168
0169 $this->assertSame('foo'.\PHP_EOL, $data);
0170 }
0171
0172 /**
0173 * tests results from sub processes.
0174 *
0175 * @dataProvider responsesCodeProvider
0176 */
0177 public function testProcessResponses($expected, $getter, $code)
0178 {
0179 $p = $this->getProcessForCode($code);
0180 $p->run();
0181
0182 $this->assertSame($expected, $p->$getter());
0183 }
0184
0185 /**
0186 * tests results from sub processes.
0187 *
0188 * @dataProvider pipesCodeProvider
0189 */
0190 public function testProcessPipes($code, $size)
0191 {
0192 $expected = str_repeat(str_repeat('*', 1024), $size).'!';
0193 $expectedLength = (1024 * $size) + 1;
0194
0195 $p = $this->getProcessForCode($code);
0196 $p->setInput($expected);
0197 $p->run();
0198
0199 $this->assertEquals($expectedLength, \strlen($p->getOutput()));
0200 $this->assertEquals($expectedLength, \strlen($p->getErrorOutput()));
0201 }
0202
0203 /**
0204 * @dataProvider pipesCodeProvider
0205 */
0206 public function testSetStreamAsInput($code, $size)
0207 {
0208 $expected = str_repeat(str_repeat('*', 1024), $size).'!';
0209 $expectedLength = (1024 * $size) + 1;
0210
0211 $stream = fopen('php://temporary', 'w+');
0212 fwrite($stream, $expected);
0213 rewind($stream);
0214
0215 $p = $this->getProcessForCode($code);
0216 $p->setInput($stream);
0217 $p->run();
0218
0219 fclose($stream);
0220
0221 $this->assertEquals($expectedLength, \strlen($p->getOutput()));
0222 $this->assertEquals($expectedLength, \strlen($p->getErrorOutput()));
0223 }
0224
0225 public function testLiveStreamAsInput()
0226 {
0227 $stream = fopen('php://memory', 'r+');
0228 fwrite($stream, 'hello');
0229 rewind($stream);
0230
0231 $p = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
0232 $p->setInput($stream);
0233 $p->start(function ($type, $data) use ($stream) {
0234 if ('hello' === $data) {
0235 fclose($stream);
0236 }
0237 });
0238 $p->wait();
0239
0240 $this->assertSame('hello', $p->getOutput());
0241 }
0242
0243 public function testSetInputWhileRunningThrowsAnException()
0244 {
0245 $this->expectException('Symfony\Component\Process\Exception\LogicException');
0246 $this->expectExceptionMessage('Input can not be set while the process is running.');
0247 $process = $this->getProcessForCode('sleep(30);');
0248 $process->start();
0249 try {
0250 $process->setInput('foobar');
0251 $process->stop();
0252 $this->fail('A LogicException should have been raised.');
0253 } catch (LogicException $e) {
0254 }
0255 $process->stop();
0256
0257 throw $e;
0258 }
0259
0260 /**
0261 * @dataProvider provideInvalidInputValues
0262 */
0263 public function testInvalidInput($value)
0264 {
0265 $this->expectException('Symfony\Component\Process\Exception\InvalidArgumentException');
0266 $this->expectExceptionMessage('"Symfony\Component\Process\Process::setInput" only accepts strings, Traversable objects or stream resources.');
0267 $process = $this->getProcess('foo');
0268 $process->setInput($value);
0269 }
0270
0271 public function provideInvalidInputValues()
0272 {
0273 return [
0274 [[]],
0275 [new NonStringifiable()],
0276 ];
0277 }
0278
0279 /**
0280 * @dataProvider provideInputValues
0281 */
0282 public function testValidInput($expected, $value)
0283 {
0284 $process = $this->getProcess('foo');
0285 $process->setInput($value);
0286 $this->assertSame($expected, $process->getInput());
0287 }
0288
0289 public function provideInputValues()
0290 {
0291 return [
0292 [null, null],
0293 ['24.5', 24.5],
0294 ['input data', 'input data'],
0295 ];
0296 }
0297
0298 public function chainedCommandsOutputProvider()
0299 {
0300 if ('\\' === \DIRECTORY_SEPARATOR) {
0301 return [
0302 ["2 \r\n2\r\n", '&&', '2'],
0303 ];
0304 }
0305
0306 return [
0307 ["1\n1\n", ';', '1'],
0308 ["2\n2\n", '&&', '2'],
0309 ];
0310 }
0311
0312 /**
0313 * @dataProvider chainedCommandsOutputProvider
0314 */
0315 public function testChainedCommandsOutput($expected, $operator, $input)
0316 {
0317 $process = $this->getProcess(sprintf('echo %s %s echo %s', $input, $operator, $input));
0318 $process->run();
0319 $this->assertEquals($expected, $process->getOutput());
0320 }
0321
0322 public function testCallbackIsExecutedForOutput()
0323 {
0324 $p = $this->getProcessForCode('echo \'foo\';');
0325
0326 $called = false;
0327 $p->run(function ($type, $buffer) use (&$called) {
0328 $called = 'foo' === $buffer;
0329 });
0330
0331 $this->assertTrue($called, 'The callback should be executed with the output');
0332 }
0333
0334 public function testCallbackIsExecutedForOutputWheneverOutputIsDisabled()
0335 {
0336 $p = $this->getProcessForCode('echo \'foo\';');
0337 $p->disableOutput();
0338
0339 $called = false;
0340 $p->run(function ($type, $buffer) use (&$called) {
0341 $called = 'foo' === $buffer;
0342 });
0343
0344 $this->assertTrue($called, 'The callback should be executed with the output');
0345 }
0346
0347 public function testGetErrorOutput()
0348 {
0349 $p = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }');
0350
0351 $p->run();
0352 $this->assertEquals(3, preg_match_all('/ERROR/', $p->getErrorOutput(), $matches));
0353 }
0354
0355 public function testFlushErrorOutput()
0356 {
0357 $p = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }');
0358
0359 $p->run();
0360 $p->clearErrorOutput();
0361 $this->assertEmpty($p->getErrorOutput());
0362 }
0363
0364 /**
0365 * @dataProvider provideIncrementalOutput
0366 */
0367 public function testIncrementalOutput($getOutput, $getIncrementalOutput, $uri)
0368 {
0369 $lock = tempnam(sys_get_temp_dir(), __FUNCTION__);
0370
0371 $p = $this->getProcessForCode('file_put_contents($s = \''.$uri.'\', \'foo\'); flock(fopen('.var_export($lock, true).', \'r\'), LOCK_EX); file_put_contents($s, \'bar\');');
0372
0373 $h = fopen($lock, 'w');
0374 flock($h, \LOCK_EX);
0375
0376 $p->start();
0377
0378 foreach (['foo', 'bar'] as $s) {
0379 while (false === strpos($p->$getOutput(), $s)) {
0380 usleep(1000);
0381 }
0382
0383 $this->assertSame($s, $p->$getIncrementalOutput());
0384 $this->assertSame('', $p->$getIncrementalOutput());
0385
0386 flock($h, \LOCK_UN);
0387 }
0388
0389 fclose($h);
0390 }
0391
0392 public function provideIncrementalOutput()
0393 {
0394 return [
0395 ['getOutput', 'getIncrementalOutput', 'php://stdout'],
0396 ['getErrorOutput', 'getIncrementalErrorOutput', 'php://stderr'],
0397 ];
0398 }
0399
0400 public function testGetOutput()
0401 {
0402 $p = $this->getProcessForCode('$n = 0; while ($n < 3) { echo \' foo \'; $n++; }');
0403
0404 $p->run();
0405 $this->assertEquals(3, preg_match_all('/foo/', $p->getOutput(), $matches));
0406 }
0407
0408 public function testFlushOutput()
0409 {
0410 $p = $this->getProcessForCode('$n=0;while ($n<3) {echo \' foo \';$n++;}');
0411
0412 $p->run();
0413 $p->clearOutput();
0414 $this->assertEmpty($p->getOutput());
0415 }
0416
0417 public function testZeroAsOutput()
0418 {
0419 if ('\\' === \DIRECTORY_SEPARATOR) {
0420 // see http://stackoverflow.com/questions/7105433/windows-batch-echo-without-new-line
0421 $p = $this->getProcess('echo | set /p dummyName=0');
0422 } else {
0423 $p = $this->getProcess('printf 0');
0424 }
0425
0426 $p->run();
0427 $this->assertSame('0', $p->getOutput());
0428 }
0429
0430 public function testExitCodeCommandFailed()
0431 {
0432 if ('\\' === \DIRECTORY_SEPARATOR) {
0433 $this->markTestSkipped('Windows does not support POSIX exit code');
0434 }
0435 $this->skipIfNotEnhancedSigchild();
0436
0437 // such command run in bash return an exitcode 127
0438 $process = $this->getProcess('nonexistingcommandIhopeneversomeonewouldnameacommandlikethis');
0439 $process->run();
0440
0441 $this->assertGreaterThan(0, $process->getExitCode());
0442 }
0443
0444 public function testTTYCommand()
0445 {
0446 if ('\\' === \DIRECTORY_SEPARATOR) {
0447 $this->markTestSkipped('Windows does not have /dev/tty support');
0448 }
0449
0450 $process = $this->getProcess('echo "foo" >> /dev/null && '.$this->getProcessForCode('usleep(100000);')->getCommandLine());
0451 $process->setTty(true);
0452 $process->start();
0453 $this->assertTrue($process->isRunning());
0454 $process->wait();
0455
0456 $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
0457 }
0458
0459 public function testTTYCommandExitCode()
0460 {
0461 if ('\\' === \DIRECTORY_SEPARATOR) {
0462 $this->markTestSkipped('Windows does have /dev/tty support');
0463 }
0464 $this->skipIfNotEnhancedSigchild();
0465
0466 $process = $this->getProcess('echo "foo" >> /dev/null');
0467 $process->setTty(true);
0468 $process->run();
0469
0470 $this->assertTrue($process->isSuccessful());
0471 }
0472
0473 public function testTTYInWindowsEnvironment()
0474 {
0475 $this->expectException('Symfony\Component\Process\Exception\RuntimeException');
0476 $this->expectExceptionMessage('TTY mode is not supported on Windows platform.');
0477 if ('\\' !== \DIRECTORY_SEPARATOR) {
0478 $this->markTestSkipped('This test is for Windows platform only');
0479 }
0480
0481 $process = $this->getProcess('echo "foo" >> /dev/null');
0482 $process->setTty(false);
0483 $process->setTty(true);
0484 }
0485
0486 public function testExitCodeTextIsNullWhenExitCodeIsNull()
0487 {
0488 $this->skipIfNotEnhancedSigchild();
0489
0490 $process = $this->getProcess('');
0491 $this->assertNull($process->getExitCodeText());
0492 }
0493
0494 public function testPTYCommand()
0495 {
0496 if (!Process::isPtySupported()) {
0497 $this->markTestSkipped('PTY is not supported on this operating system.');
0498 }
0499
0500 $process = $this->getProcess('echo "foo"');
0501 $process->setPty(true);
0502 $process->run();
0503
0504 $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
0505 $this->assertEquals("foo\r\n", $process->getOutput());
0506 }
0507
0508 public function testMustRun()
0509 {
0510 $this->skipIfNotEnhancedSigchild();
0511
0512 $process = $this->getProcess('echo foo');
0513
0514 $this->assertSame($process, $process->mustRun());
0515 $this->assertEquals('foo'.\PHP_EOL, $process->getOutput());
0516 }
0517
0518 public function testSuccessfulMustRunHasCorrectExitCode()
0519 {
0520 $this->skipIfNotEnhancedSigchild();
0521
0522 $process = $this->getProcess('echo foo')->mustRun();
0523 $this->assertEquals(0, $process->getExitCode());
0524 }
0525
0526 public function testMustRunThrowsException()
0527 {
0528 $this->expectException('Symfony\Component\Process\Exception\ProcessFailedException');
0529 $this->skipIfNotEnhancedSigchild();
0530
0531 $process = $this->getProcess('exit 1');
0532 $process->mustRun();
0533 }
0534
0535 public function testExitCodeText()
0536 {
0537 $this->skipIfNotEnhancedSigchild();
0538
0539 $process = $this->getProcess('');
0540 $r = new \ReflectionObject($process);
0541 $p = $r->getProperty('exitcode');
0542 $p->setAccessible(true);
0543
0544 $p->setValue($process, 2);
0545 $this->assertEquals('Misuse of shell builtins', $process->getExitCodeText());
0546 }
0547
0548 public function testStartIsNonBlocking()
0549 {
0550 $process = $this->getProcessForCode('usleep(500000);');
0551 $start = microtime(true);
0552 $process->start();
0553 $end = microtime(true);
0554 $this->assertLessThan(0.4, $end - $start);
0555 $process->stop();
0556 }
0557
0558 public function testUpdateStatus()
0559 {
0560 $process = $this->getProcess('echo foo');
0561 $process->run();
0562 $this->assertGreaterThan(0, \strlen($process->getOutput()));
0563 }
0564
0565 public function testGetExitCodeIsNullOnStart()
0566 {
0567 $this->skipIfNotEnhancedSigchild();
0568
0569 $process = $this->getProcessForCode('usleep(100000);');
0570 $this->assertNull($process->getExitCode());
0571 $process->start();
0572 $this->assertNull($process->getExitCode());
0573 $process->wait();
0574 $this->assertEquals(0, $process->getExitCode());
0575 }
0576
0577 public function testGetExitCodeIsNullOnWhenStartingAgain()
0578 {
0579 $this->skipIfNotEnhancedSigchild();
0580
0581 $process = $this->getProcessForCode('usleep(100000);');
0582 $process->run();
0583 $this->assertEquals(0, $process->getExitCode());
0584 $process->start();
0585 $this->assertNull($process->getExitCode());
0586 $process->wait();
0587 $this->assertEquals(0, $process->getExitCode());
0588 }
0589
0590 public function testGetExitCode()
0591 {
0592 $this->skipIfNotEnhancedSigchild();
0593
0594 $process = $this->getProcess('echo foo');
0595 $process->run();
0596 $this->assertSame(0, $process->getExitCode());
0597 }
0598
0599 public function testStatus()
0600 {
0601 $process = $this->getProcessForCode('usleep(100000);');
0602 $this->assertFalse($process->isRunning());
0603 $this->assertFalse($process->isStarted());
0604 $this->assertFalse($process->isTerminated());
0605 $this->assertSame(Process::STATUS_READY, $process->getStatus());
0606 $process->start();
0607 $this->assertTrue($process->isRunning());
0608 $this->assertTrue($process->isStarted());
0609 $this->assertFalse($process->isTerminated());
0610 $this->assertSame(Process::STATUS_STARTED, $process->getStatus());
0611 $process->wait();
0612 $this->assertFalse($process->isRunning());
0613 $this->assertTrue($process->isStarted());
0614 $this->assertTrue($process->isTerminated());
0615 $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
0616 }
0617
0618 public function testStop()
0619 {
0620 $process = $this->getProcessForCode('sleep(31);');
0621 $process->start();
0622 $this->assertTrue($process->isRunning());
0623 $process->stop();
0624 $this->assertFalse($process->isRunning());
0625 }
0626
0627 public function testIsSuccessful()
0628 {
0629 $this->skipIfNotEnhancedSigchild();
0630
0631 $process = $this->getProcess('echo foo');
0632 $process->run();
0633 $this->assertTrue($process->isSuccessful());
0634 }
0635
0636 public function testIsSuccessfulOnlyAfterTerminated()
0637 {
0638 $this->skipIfNotEnhancedSigchild();
0639
0640 $process = $this->getProcessForCode('usleep(100000);');
0641 $process->start();
0642
0643 $this->assertFalse($process->isSuccessful());
0644
0645 $process->wait();
0646
0647 $this->assertTrue($process->isSuccessful());
0648 }
0649
0650 public function testIsNotSuccessful()
0651 {
0652 $this->skipIfNotEnhancedSigchild();
0653
0654 $process = $this->getProcessForCode('throw new \Exception(\'BOUM\');');
0655 $process->run();
0656 $this->assertFalse($process->isSuccessful());
0657 }
0658
0659 public function testProcessIsNotSignaled()
0660 {
0661 if ('\\' === \DIRECTORY_SEPARATOR) {
0662 $this->markTestSkipped('Windows does not support POSIX signals');
0663 }
0664 $this->skipIfNotEnhancedSigchild();
0665
0666 $process = $this->getProcess('echo foo');
0667 $process->run();
0668 $this->assertFalse($process->hasBeenSignaled());
0669 }
0670
0671 public function testProcessWithoutTermSignal()
0672 {
0673 if ('\\' === \DIRECTORY_SEPARATOR) {
0674 $this->markTestSkipped('Windows does not support POSIX signals');
0675 }
0676 $this->skipIfNotEnhancedSigchild();
0677
0678 $process = $this->getProcess('echo foo');
0679 $process->run();
0680 $this->assertEquals(0, $process->getTermSignal());
0681 }
0682
0683 public function testProcessIsSignaledIfStopped()
0684 {
0685 if ('\\' === \DIRECTORY_SEPARATOR) {
0686 $this->markTestSkipped('Windows does not support POSIX signals');
0687 }
0688 $this->skipIfNotEnhancedSigchild();
0689
0690 $process = $this->getProcessForCode('sleep(32);');
0691 $process->start();
0692 $process->stop();
0693 $this->assertTrue($process->hasBeenSignaled());
0694 $this->assertEquals(15, $process->getTermSignal()); // SIGTERM
0695 }
0696
0697 public function testProcessThrowsExceptionWhenExternallySignaled()
0698 {
0699 $this->expectException('Symfony\Component\Process\Exception\RuntimeException');
0700 $this->expectExceptionMessage('The process has been signaled');
0701 if (!\function_exists('posix_kill')) {
0702 $this->markTestSkipped('Function posix_kill is required.');
0703 }
0704 $this->skipIfNotEnhancedSigchild(false);
0705
0706 $process = $this->getProcessForCode('sleep(32.1);');
0707 $process->start();
0708 posix_kill($process->getPid(), 9); // SIGKILL
0709
0710 $process->wait();
0711 }
0712
0713 public function testRestart()
0714 {
0715 $process1 = $this->getProcessForCode('echo getmypid();');
0716 $process1->run();
0717 $process2 = $process1->restart();
0718
0719 $process2->wait(); // wait for output
0720
0721 // Ensure that both processed finished and the output is numeric
0722 $this->assertFalse($process1->isRunning());
0723 $this->assertFalse($process2->isRunning());
0724 $this->assertIsNumeric($process1->getOutput());
0725 $this->assertIsNumeric($process2->getOutput());
0726
0727 // Ensure that restart returned a new process by check that the output is different
0728 $this->assertNotEquals($process1->getOutput(), $process2->getOutput());
0729 }
0730
0731 public function testRunProcessWithTimeout()
0732 {
0733 $this->expectException('Symfony\Component\Process\Exception\ProcessTimedOutException');
0734 $this->expectExceptionMessage('exceeded the timeout of 0.1 seconds.');
0735 $process = $this->getProcessForCode('sleep(30);');
0736 $process->setTimeout(0.1);
0737 $start = microtime(true);
0738 try {
0739 $process->run();
0740 $this->fail('A RuntimeException should have been raised');
0741 } catch (RuntimeException $e) {
0742 }
0743
0744 $this->assertLessThan(15, microtime(true) - $start);
0745
0746 throw $e;
0747 }
0748
0749 public function testIterateOverProcessWithTimeout()
0750 {
0751 $this->expectException('Symfony\Component\Process\Exception\ProcessTimedOutException');
0752 $this->expectExceptionMessage('exceeded the timeout of 0.1 seconds.');
0753 $process = $this->getProcessForCode('sleep(30);');
0754 $process->setTimeout(0.1);
0755 $start = microtime(true);
0756 try {
0757 $process->start();
0758 foreach ($process as $buffer);
0759 $this->fail('A RuntimeException should have been raised');
0760 } catch (RuntimeException $e) {
0761 }
0762
0763 $this->assertLessThan(15, microtime(true) - $start);
0764
0765 throw $e;
0766 }
0767
0768 public function testCheckTimeoutOnNonStartedProcess()
0769 {
0770 $process = $this->getProcess('echo foo');
0771 $this->assertNull($process->checkTimeout());
0772 }
0773
0774 public function testCheckTimeoutOnTerminatedProcess()
0775 {
0776 $process = $this->getProcess('echo foo');
0777 $process->run();
0778 $this->assertNull($process->checkTimeout());
0779 }
0780
0781 public function testCheckTimeoutOnStartedProcess()
0782 {
0783 $this->expectException('Symfony\Component\Process\Exception\ProcessTimedOutException');
0784 $this->expectExceptionMessage('exceeded the timeout of 0.1 seconds.');
0785 $process = $this->getProcessForCode('sleep(33);');
0786 $process->setTimeout(0.1);
0787
0788 $process->start();
0789 $start = microtime(true);
0790
0791 try {
0792 while ($process->isRunning()) {
0793 $process->checkTimeout();
0794 usleep(100000);
0795 }
0796 $this->fail('A ProcessTimedOutException should have been raised');
0797 } catch (ProcessTimedOutException $e) {
0798 }
0799
0800 $this->assertLessThan(15, microtime(true) - $start);
0801
0802 throw $e;
0803 }
0804
0805 public function testIdleTimeout()
0806 {
0807 $process = $this->getProcessForCode('sleep(34);');
0808 $process->setTimeout(60);
0809 $process->setIdleTimeout(0.1);
0810
0811 try {
0812 $process->run();
0813
0814 $this->fail('A timeout exception was expected.');
0815 } catch (ProcessTimedOutException $e) {
0816 $this->assertTrue($e->isIdleTimeout());
0817 $this->assertFalse($e->isGeneralTimeout());
0818 $this->assertEquals(0.1, $e->getExceededTimeout());
0819 }
0820 }
0821
0822 public function testIdleTimeoutNotExceededWhenOutputIsSent()
0823 {
0824 $process = $this->getProcessForCode('while (true) {echo \'foo \'; usleep(1000);}');
0825 $process->setTimeout(1);
0826 $process->start();
0827
0828 while (false === strpos($process->getOutput(), 'foo')) {
0829 usleep(1000);
0830 }
0831
0832 $process->setIdleTimeout(0.5);
0833
0834 try {
0835 $process->wait();
0836 $this->fail('A timeout exception was expected.');
0837 } catch (ProcessTimedOutException $e) {
0838 $this->assertTrue($e->isGeneralTimeout(), 'A general timeout is expected.');
0839 $this->assertFalse($e->isIdleTimeout(), 'No idle timeout is expected.');
0840 $this->assertEquals(1, $e->getExceededTimeout());
0841 }
0842 }
0843
0844 public function testStartAfterATimeout()
0845 {
0846 $this->expectException('Symfony\Component\Process\Exception\ProcessTimedOutException');
0847 $this->expectExceptionMessage('exceeded the timeout of 0.1 seconds.');
0848 $process = $this->getProcessForCode('sleep(35);');
0849 $process->setTimeout(0.1);
0850
0851 try {
0852 $process->run();
0853 $this->fail('A ProcessTimedOutException should have been raised.');
0854 } catch (ProcessTimedOutException $e) {
0855 }
0856 $this->assertFalse($process->isRunning());
0857 $process->start();
0858 $this->assertTrue($process->isRunning());
0859 $process->stop(0);
0860
0861 throw $e;
0862 }
0863
0864 public function testGetPid()
0865 {
0866 $process = $this->getProcessForCode('sleep(36);');
0867 $process->start();
0868 $this->assertGreaterThan(0, $process->getPid());
0869 $process->stop(0);
0870 }
0871
0872 public function testGetPidIsNullBeforeStart()
0873 {
0874 $process = $this->getProcess('foo');
0875 $this->assertNull($process->getPid());
0876 }
0877
0878 public function testGetPidIsNullAfterRun()
0879 {
0880 $process = $this->getProcess('echo foo');
0881 $process->run();
0882 $this->assertNull($process->getPid());
0883 }
0884
0885 /**
0886 * @requires extension pcntl
0887 */
0888 public function testSignal()
0889 {
0890 $process = $this->getProcess([self::$phpBin, __DIR__.'/SignalListener.php']);
0891 $process->start();
0892
0893 while (false === strpos($process->getOutput(), 'Caught')) {
0894 usleep(1000);
0895 }
0896 $process->signal(\SIGUSR1);
0897 $process->wait();
0898
0899 $this->assertEquals('Caught SIGUSR1', $process->getOutput());
0900 }
0901
0902 /**
0903 * @requires extension pcntl
0904 */
0905 public function testExitCodeIsAvailableAfterSignal()
0906 {
0907 $this->skipIfNotEnhancedSigchild();
0908
0909 $process = $this->getProcess('sleep 4');
0910 $process->start();
0911 $process->signal(\SIGKILL);
0912
0913 while ($process->isRunning()) {
0914 usleep(10000);
0915 }
0916
0917 $this->assertFalse($process->isRunning());
0918 $this->assertTrue($process->hasBeenSignaled());
0919 $this->assertFalse($process->isSuccessful());
0920 $this->assertEquals(137, $process->getExitCode());
0921 }
0922
0923 public function testSignalProcessNotRunning()
0924 {
0925 $this->expectException('Symfony\Component\Process\Exception\LogicException');
0926 $this->expectExceptionMessage('Can not send signal on a non running process.');
0927 $process = $this->getProcess('foo');
0928 $process->signal(1); // SIGHUP
0929 }
0930
0931 /**
0932 * @dataProvider provideMethodsThatNeedARunningProcess
0933 */
0934 public function testMethodsThatNeedARunningProcess($method)
0935 {
0936 $process = $this->getProcess('foo');
0937
0938 $this->expectException('Symfony\Component\Process\Exception\LogicException');
0939 $this->expectExceptionMessage(sprintf('Process must be started before calling "%s()".', $method));
0940
0941 $process->{$method}();
0942 }
0943
0944 public function provideMethodsThatNeedARunningProcess()
0945 {
0946 return [
0947 ['getOutput'],
0948 ['getIncrementalOutput'],
0949 ['getErrorOutput'],
0950 ['getIncrementalErrorOutput'],
0951 ['wait'],
0952 ];
0953 }
0954
0955 /**
0956 * @dataProvider provideMethodsThatNeedATerminatedProcess
0957 */
0958 public function testMethodsThatNeedATerminatedProcess($method)
0959 {
0960 $this->expectException('Symfony\Component\Process\Exception\LogicException');
0961 $this->expectExceptionMessage('Process must be terminated before calling');
0962 $process = $this->getProcessForCode('sleep(37);');
0963 $process->start();
0964 try {
0965 $process->{$method}();
0966 $process->stop(0);
0967 $this->fail('A LogicException must have been thrown');
0968 } catch (\Exception $e) {
0969 }
0970 $process->stop(0);
0971
0972 throw $e;
0973 }
0974
0975 public function provideMethodsThatNeedATerminatedProcess()
0976 {
0977 return [
0978 ['hasBeenSignaled'],
0979 ['getTermSignal'],
0980 ['hasBeenStopped'],
0981 ['getStopSignal'],
0982 ];
0983 }
0984
0985 /**
0986 * @dataProvider provideWrongSignal
0987 */
0988 public function testWrongSignal($signal)
0989 {
0990 if ('\\' === \DIRECTORY_SEPARATOR) {
0991 $this->markTestSkipped('POSIX signals do not work on Windows');
0992 }
0993
0994 if (\PHP_VERSION_ID < 80000 || \is_int($signal)) {
0995 $this->expectException(RuntimeException::class);
0996 } else {
0997 $this->expectException('TypeError');
0998 }
0999
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
1502 $this->assertSame($expected, str_replace('Standard input code', '-', $p->getOutput()));
1503 }
1504
1505 public function provideEscapeArgument()
1506 {
1507 yield ['a"b%c%'];
1508 yield ['a"b^c^'];
1509 yield ["a\nb'c"];
1510 yield ['a^b c!'];
1511 yield ["a!b\tc"];
1512 yield ['a\\\\"\\"'];
1513 yield ['éÉèÈàÀöä'];
1514 yield [null];
1515 yield [1];
1516 yield [1.1];
1517 }
1518
1519 public function testEnvArgument()
1520 {
1521 $env = ['FOO' => 'Foo', 'BAR' => 'Bar'];
1522 $cmd = '\\' === \DIRECTORY_SEPARATOR ? 'echo !FOO! !BAR! !BAZ!' : 'echo $FOO $BAR $BAZ';
1523 $p = new Process($cmd, null, $env);
1524 $p->run(null, ['BAR' => 'baR', 'BAZ' => 'baZ']);
1525
1526 $this->assertSame('Foo baR baZ', rtrim($p->getOutput()));
1527 $this->assertSame($env, $p->getEnv());
1528 }
1529
1530 public function testWaitStoppedDeadProcess()
1531 {
1532 $process = $this->getProcess(self::$phpBin.' '.__DIR__.'/ErrorProcessInitiator.php -e '.self::$phpBin);
1533 $process->start();
1534 $process->setTimeout(2);
1535 $process->wait();
1536 $this->assertFalse($process->isRunning());
1537 }
1538
1539 /**
1540 * @param string $commandline
1541 * @param string|null $cwd
1542 * @param string|null $input
1543 * @param int $timeout
1544 *
1545 * @return Process
1546 */
1547 private function getProcess($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60)
1548 {
1549 $process = new Process($commandline, $cwd, $env, $input, $timeout);
1550 $process->inheritEnvironmentVariables();
1551
1552 if (false !== $enhance = getenv('ENHANCE_SIGCHLD')) {
1553 try {
1554 $process->setEnhanceSigchildCompatibility(false);
1555 $process->getExitCode();
1556 $this->fail('ENHANCE_SIGCHLD must be used together with a sigchild-enabled PHP.');
1557 } catch (RuntimeException $e) {
1558 $this->assertSame('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.', $e->getMessage());
1559 if ($enhance) {
1560 $process->setEnhanceSigchildCompatibility(true);
1561 } else {
1562 self::$notEnhancedSigchild = true;
1563 }
1564 }
1565 }
1566
1567 if (self::$process) {
1568 self::$process->stop(0);
1569 }
1570
1571 return self::$process = $process;
1572 }
1573
1574 /**
1575 * @return Process
1576 */
1577 private function getProcessForCode($code, $cwd = null, array $env = null, $input = null, $timeout = 60)
1578 {
1579 return $this->getProcess([self::$phpBin, '-r', $code], $cwd, $env, $input, $timeout);
1580 }
1581
1582 private function skipIfNotEnhancedSigchild($expectException = true)
1583 {
1584 if (self::$sigchild) {
1585 if (!$expectException) {
1586 $this->markTestSkipped('PHP is compiled with --enable-sigchild.');
1587 } elseif (self::$notEnhancedSigchild) {
1588 $this->expectException('Symfony\Component\Process\Exception\RuntimeException');
1589 $this->expectExceptionMessage('This PHP has been compiled with --enable-sigchild.');
1590 }
1591 }
1592 }
1593 }
1594
1595 class NonStringifiable
1596 {
1597 }
1598