[ Index ]

PHP Cross Reference of phpBB-3.3.14-deutsch

title

Body

[close]

/vendor/symfony/process/Tests/ -> ProcessTest.php (source)

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


Generated: Mon Nov 25 19:05:08 2024 Cross-referenced by PHPXref 0.7.1