Verzeichnisstruktur phpBB-3.3.16


Veröffentlicht
27.04.2026

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: 01.05.2026, 11:27 - 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