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.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

ProcessTest.php

Zuletzt modifiziert: 02.04.2025, 15:03 - Dateigröße: 48.27 KiB


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