[ Index ]

PHP Cross Reference of phpBB-3.3.0-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          $this->expectException('Symfony\Component\Process\Exception\RuntimeException');
 991          if ('\\' === \DIRECTORY_SEPARATOR) {
 992              $this->markTestSkipped('POSIX signals do not work on Windows');
 993          }
 994  
 995          $process = $this->getProcessForCode('sleep(38);');
 996          $process->start();
 997          try {
 998              $process->signal($signal);
 999              $this->fail('A RuntimeException must have been thrown');
1000          } catch (RuntimeException $e) {
1001              $process->stop(0);
1002          }
1003  
1004          throw $e;
1005      }
1006  
1007      public function provideWrongSignal()
1008      {
1009          return [
1010              [-4],
1011              ['Céphalopodes'],
1012          ];
1013      }
1014  
1015      public function testDisableOutputDisablesTheOutput()
1016      {
1017          $p = $this->getProcess('foo');
1018          $this->assertFalse($p->isOutputDisabled());
1019          $p->disableOutput();
1020          $this->assertTrue($p->isOutputDisabled());
1021          $p->enableOutput();
1022          $this->assertFalse($p->isOutputDisabled());
1023      }
1024  
1025      public function testDisableOutputWhileRunningThrowsException()
1026      {
1027          $this->expectException('Symfony\Component\Process\Exception\RuntimeException');
1028          $this->expectExceptionMessage('Disabling output while the process is running is not possible.');
1029          $p = $this->getProcessForCode('sleep(39);');
1030          $p->start();
1031          $p->disableOutput();
1032      }
1033  
1034      public function testEnableOutputWhileRunningThrowsException()
1035      {
1036          $this->expectException('Symfony\Component\Process\Exception\RuntimeException');
1037          $this->expectExceptionMessage('Enabling output while the process is running is not possible.');
1038          $p = $this->getProcessForCode('sleep(40);');
1039          $p->disableOutput();
1040          $p->start();
1041          $p->enableOutput();
1042      }
1043  
1044      public function testEnableOrDisableOutputAfterRunDoesNotThrowException()
1045      {
1046          $p = $this->getProcess('echo foo');
1047          $p->disableOutput();
1048          $p->run();
1049          $p->enableOutput();
1050          $p->disableOutput();
1051          $this->assertTrue($p->isOutputDisabled());
1052      }
1053  
1054      public function testDisableOutputWhileIdleTimeoutIsSet()
1055      {
1056          $this->expectException('Symfony\Component\Process\Exception\LogicException');
1057          $this->expectExceptionMessage('Output can not be disabled while an idle timeout is set.');
1058          $process = $this->getProcess('foo');
1059          $process->setIdleTimeout(1);
1060          $process->disableOutput();
1061      }
1062  
1063      public function testSetIdleTimeoutWhileOutputIsDisabled()
1064      {
1065          $this->expectException('Symfony\Component\Process\Exception\LogicException');
1066          $this->expectExceptionMessage('timeout can not be set while the output is disabled.');
1067          $process = $this->getProcess('foo');
1068          $process->disableOutput();
1069          $process->setIdleTimeout(1);
1070      }
1071  
1072      public function testSetNullIdleTimeoutWhileOutputIsDisabled()
1073      {
1074          $process = $this->getProcess('foo');
1075          $process->disableOutput();
1076          $this->assertSame($process, $process->setIdleTimeout(null));
1077      }
1078  
1079      /**
1080       * @dataProvider provideOutputFetchingMethods
1081       */
1082      public function testGetOutputWhileDisabled($fetchMethod)
1083      {
1084          $this->expectException('Symfony\Component\Process\Exception\LogicException');
1085          $this->expectExceptionMessage('Output has been disabled.');
1086          $p = $this->getProcessForCode('sleep(41);');
1087          $p->disableOutput();
1088          $p->start();
1089          $p->{$fetchMethod}();
1090      }
1091  
1092      public function provideOutputFetchingMethods()
1093      {
1094          return [
1095              ['getOutput'],
1096              ['getIncrementalOutput'],
1097              ['getErrorOutput'],
1098              ['getIncrementalErrorOutput'],
1099          ];
1100      }
1101  
1102      public function testStopTerminatesProcessCleanly()
1103      {
1104          $process = $this->getProcessForCode('echo 123; sleep(42);');
1105          $process->run(function () use ($process) {
1106              $process->stop();
1107          });
1108          $this->assertTrue(true, 'A call to stop() is not expected to cause wait() to throw a RuntimeException');
1109      }
1110  
1111      public function testKillSignalTerminatesProcessCleanly()
1112      {
1113          $process = $this->getProcessForCode('echo 123; sleep(43);');
1114          $process->run(function () use ($process) {
1115              $process->signal(9); // SIGKILL
1116          });
1117          $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException');
1118      }
1119  
1120      public function testTermSignalTerminatesProcessCleanly()
1121      {
1122          $process = $this->getProcessForCode('echo 123; sleep(44);');
1123          $process->run(function () use ($process) {
1124              $process->signal(15); // SIGTERM
1125          });
1126          $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException');
1127      }
1128  
1129      public function responsesCodeProvider()
1130      {
1131          return [
1132              //expected output / getter / code to execute
1133              //[1,'getExitCode','exit(1);'],
1134              //[true,'isSuccessful','exit();'],
1135              ['output', 'getOutput', 'echo \'output\';'],
1136          ];
1137      }
1138  
1139      public function pipesCodeProvider()
1140      {
1141          $variations = [
1142              'fwrite(STDOUT, $in = file_get_contents(\'php://stdin\')); fwrite(STDERR, $in);',
1143              'include \''.__DIR__.'/PipeStdinInStdoutStdErrStreamSelect.php\';',
1144          ];
1145  
1146          if ('\\' === \DIRECTORY_SEPARATOR) {
1147              // Avoid XL buffers on Windows because of https://bugs.php.net/65650
1148              $sizes = [1, 2, 4, 8];
1149          } else {
1150              $sizes = [1, 16, 64, 1024, 4096];
1151          }
1152  
1153          $codes = [];
1154          foreach ($sizes as $size) {
1155              foreach ($variations as $code) {
1156                  $codes[] = [$code, $size];
1157              }
1158          }
1159  
1160          return $codes;
1161      }
1162  
1163      /**
1164       * @dataProvider provideVariousIncrementals
1165       */
1166      public function testIncrementalOutputDoesNotRequireAnotherCall($stream, $method)
1167      {
1168          $process = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\''.$stream.'\', $n, 1); $n++; usleep(1000); }', null, null, null, null);
1169          $process->start();
1170          $result = '';
1171          $limit = microtime(true) + 3;
1172          $expected = '012';
1173  
1174          while ($result !== $expected && microtime(true) < $limit) {
1175              $result .= $process->$method();
1176          }
1177  
1178          $this->assertSame($expected, $result);
1179          $process->stop();
1180      }
1181  
1182      public function provideVariousIncrementals()
1183      {
1184          return [
1185              ['php://stdout', 'getIncrementalOutput'],
1186              ['php://stderr', 'getIncrementalErrorOutput'],
1187          ];
1188      }
1189  
1190      public function testIteratorInput()
1191      {
1192          $input = function () {
1193              yield 'ping';
1194              yield 'pong';
1195          };
1196  
1197          $process = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);', null, null, $input());
1198          $process->run();
1199          $this->assertSame('pingpong', $process->getOutput());
1200      }
1201  
1202      public function testSimpleInputStream()
1203      {
1204          $input = new InputStream();
1205  
1206          $process = $this->getProcessForCode('echo \'ping\'; echo fread(STDIN, 4); echo fread(STDIN, 4);');
1207          $process->setInput($input);
1208  
1209          $process->start(function ($type, $data) use ($input) {
1210              if ('ping' === $data) {
1211                  $input->write('pang');
1212              } elseif (!$input->isClosed()) {
1213                  $input->write('pong');
1214                  $input->close();
1215              }
1216          });
1217  
1218          $process->wait();
1219          $this->assertSame('pingpangpong', $process->getOutput());
1220      }
1221  
1222      public function testInputStreamWithCallable()
1223      {
1224          $i = 0;
1225          $stream = fopen('php://memory', 'w+');
1226          $stream = function () use ($stream, &$i) {
1227              if ($i < 3) {
1228                  rewind($stream);
1229                  fwrite($stream, ++$i);
1230                  rewind($stream);
1231  
1232                  return $stream;
1233              }
1234  
1235              return null;
1236          };
1237  
1238          $input = new InputStream();
1239          $input->onEmpty($stream);
1240          $input->write($stream());
1241  
1242          $process = $this->getProcessForCode('echo fread(STDIN, 3);');
1243          $process->setInput($input);
1244          $process->start(function ($type, $data) use ($input) {
1245              $input->close();
1246          });
1247  
1248          $process->wait();
1249          $this->assertSame('123', $process->getOutput());
1250      }
1251  
1252      public function testInputStreamWithGenerator()
1253      {
1254          $input = new InputStream();
1255          $input->onEmpty(function ($input) {
1256              yield 'pong';
1257              $input->close();
1258          });
1259  
1260          $process = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
1261          $process->setInput($input);
1262          $process->start();
1263          $input->write('ping');
1264          $process->wait();
1265          $this->assertSame('pingpong', $process->getOutput());
1266      }
1267  
1268      public function testInputStreamOnEmpty()
1269      {
1270          $i = 0;
1271          $input = new InputStream();
1272          $input->onEmpty(function () use (&$i) { ++$i; });
1273  
1274          $process = $this->getProcessForCode('echo 123; echo fread(STDIN, 1); echo 456;');
1275          $process->setInput($input);
1276          $process->start(function ($type, $data) use ($input) {
1277              if ('123' === $data) {
1278                  $input->close();
1279              }
1280          });
1281          $process->wait();
1282  
1283          $this->assertSame(0, $i, 'InputStream->onEmpty callback should be called only when the input *becomes* empty');
1284          $this->assertSame('123456', $process->getOutput());
1285      }
1286  
1287      public function testIteratorOutput()
1288      {
1289          $input = new InputStream();
1290  
1291          $process = $this->getProcessForCode('fwrite(STDOUT, 123); fwrite(STDERR, 234); flush(); usleep(10000); fwrite(STDOUT, fread(STDIN, 3)); fwrite(STDERR, 456);');
1292          $process->setInput($input);
1293          $process->start();
1294          $output = [];
1295  
1296          foreach ($process as $type => $data) {
1297              $output[] = [$type, $data];
1298              break;
1299          }
1300          $expectedOutput = [
1301              [$process::OUT, '123'],
1302          ];
1303          $this->assertSame($expectedOutput, $output);
1304  
1305          $input->write(345);
1306  
1307          foreach ($process as $type => $data) {
1308              $output[] = [$type, $data];
1309          }
1310  
1311          $this->assertSame('', $process->getOutput());
1312          $this->assertFalse($process->isRunning());
1313  
1314          $expectedOutput = [
1315              [$process::OUT, '123'],
1316              [$process::ERR, '234'],
1317              [$process::OUT, '345'],
1318              [$process::ERR, '456'],
1319          ];
1320          $this->assertSame($expectedOutput, $output);
1321      }
1322  
1323      public function testNonBlockingNorClearingIteratorOutput()
1324      {
1325          $input = new InputStream();
1326  
1327          $process = $this->getProcessForCode('fwrite(STDOUT, fread(STDIN, 3));');
1328          $process->setInput($input);
1329          $process->start();
1330          $output = [];
1331  
1332          foreach ($process->getIterator($process::ITER_NON_BLOCKING | $process::ITER_KEEP_OUTPUT) as $type => $data) {
1333              $output[] = [$type, $data];
1334              break;
1335          }
1336          $expectedOutput = [
1337              [$process::OUT, ''],
1338          ];
1339          $this->assertSame($expectedOutput, $output);
1340  
1341          $input->write(123);
1342  
1343          foreach ($process->getIterator($process::ITER_NON_BLOCKING | $process::ITER_KEEP_OUTPUT) as $type => $data) {
1344              if ('' !== $data) {
1345                  $output[] = [$type, $data];
1346              }
1347          }
1348  
1349          $this->assertSame('123', $process->getOutput());
1350          $this->assertFalse($process->isRunning());
1351  
1352          $expectedOutput = [
1353              [$process::OUT, ''],
1354              [$process::OUT, '123'],
1355          ];
1356          $this->assertSame($expectedOutput, $output);
1357      }
1358  
1359      public function testChainedProcesses()
1360      {
1361          $p1 = $this->getProcessForCode('fwrite(STDERR, 123); fwrite(STDOUT, 456);');
1362          $p2 = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
1363          $p2->setInput($p1);
1364  
1365          $p1->start();
1366          $p2->run();
1367  
1368          $this->assertSame('123', $p1->getErrorOutput());
1369          $this->assertSame('', $p1->getOutput());
1370          $this->assertSame('', $p2->getErrorOutput());
1371          $this->assertSame('456', $p2->getOutput());
1372      }
1373  
1374      public function testSetBadEnv()
1375      {
1376          $process = $this->getProcess('echo hello');
1377          $process->setEnv(['bad%%' => '123']);
1378          $process->inheritEnvironmentVariables(true);
1379  
1380          $process->run();
1381  
1382          $this->assertSame('hello'.PHP_EOL, $process->getOutput());
1383          $this->assertSame('', $process->getErrorOutput());
1384      }
1385  
1386      public function testEnvBackupDoesNotDeleteExistingVars()
1387      {
1388          putenv('existing_var=foo');
1389          $_ENV['existing_var'] = 'foo';
1390          $process = $this->getProcess('php -r "echo getenv(\'new_test_var\');"');
1391          $process->setEnv(['existing_var' => 'bar', 'new_test_var' => 'foo']);
1392          $process->inheritEnvironmentVariables();
1393  
1394          $process->run();
1395  
1396          $this->assertSame('foo', $process->getOutput());
1397          $this->assertSame('foo', getenv('existing_var'));
1398          $this->assertFalse(getenv('new_test_var'));
1399  
1400          putenv('existing_var');
1401          unset($_ENV['existing_var']);
1402      }
1403  
1404      public function testEnvIsInherited()
1405      {
1406          $process = $this->getProcessForCode('echo serialize($_SERVER);', null, ['BAR' => 'BAZ', 'EMPTY' => '']);
1407  
1408          putenv('FOO=BAR');
1409          $_ENV['FOO'] = 'BAR';
1410  
1411          $process->run();
1412  
1413          $expected = ['BAR' => 'BAZ', 'EMPTY' => '', 'FOO' => 'BAR'];
1414          $env = array_intersect_key(unserialize($process->getOutput()), $expected);
1415  
1416          $this->assertEquals($expected, $env);
1417  
1418          putenv('FOO');
1419          unset($_ENV['FOO']);
1420      }
1421  
1422      /**
1423       * @group legacy
1424       */
1425      public function testInheritEnvDisabled()
1426      {
1427          $process = $this->getProcessForCode('echo serialize($_SERVER);', null, ['BAR' => 'BAZ']);
1428  
1429          putenv('FOO=BAR');
1430          $_ENV['FOO'] = 'BAR';
1431  
1432          $this->assertSame($process, $process->inheritEnvironmentVariables(false));
1433          $this->assertFalse($process->areEnvironmentVariablesInherited());
1434  
1435          $process->run();
1436  
1437          $expected = ['BAR' => 'BAZ', 'FOO' => 'BAR'];
1438          $env = array_intersect_key(unserialize($process->getOutput()), $expected);
1439          unset($expected['FOO']);
1440  
1441          $this->assertSame($expected, $env);
1442  
1443          putenv('FOO');
1444          unset($_ENV['FOO']);
1445      }
1446  
1447      public function testGetCommandLine()
1448      {
1449          $p = new Process(['/usr/bin/php']);
1450  
1451          $expected = '\\' === \DIRECTORY_SEPARATOR ? '"/usr/bin/php"' : "'/usr/bin/php'";
1452          $this->assertSame($expected, $p->getCommandLine());
1453      }
1454  
1455      /**
1456       * @dataProvider provideEscapeArgument
1457       */
1458      public function testEscapeArgument($arg)
1459      {
1460          $p = new Process([self::$phpBin, '-r', 'echo $argv[1];', $arg]);
1461          $p->run();
1462  
1463          $this->assertSame((string) $arg, $p->getOutput());
1464      }
1465  
1466      /**
1467       * @dataProvider provideEscapeArgument
1468       * @group legacy
1469       */
1470      public function testEscapeArgumentWhenInheritEnvDisabled($arg)
1471      {
1472          $p = new Process([self::$phpBin, '-r', 'echo $argv[1];', $arg], null, ['BAR' => 'BAZ']);
1473          $p->inheritEnvironmentVariables(false);
1474          $p->run();
1475  
1476          $this->assertSame((string) $arg, $p->getOutput());
1477      }
1478  
1479      public function testRawCommandLine()
1480      {
1481          $p = new Process(sprintf('"%s" -r %s "a" "" "b"', self::$phpBin, escapeshellarg('print_r($argv);')));
1482          $p->run();
1483  
1484          $expected = <<<EOTXT
1485  Array
1486  (
1487      [0] => -
1488      [1] => a
1489      [2] => 
1490      [3] => b
1491  )
1492  
1493  EOTXT;
1494          $this->assertSame($expected, str_replace('Standard input code', '-', $p->getOutput()));
1495      }
1496  
1497      public function provideEscapeArgument()
1498      {
1499          yield ['a"b%c%'];
1500          yield ['a"b^c^'];
1501          yield ["a\nb'c"];
1502          yield ['a^b c!'];
1503          yield ["a!b\tc"];
1504          yield ['a\\\\"\\"'];
1505          yield ['éÉèÈàÀöä'];
1506          yield [null];
1507          yield [1];
1508          yield [1.1];
1509      }
1510  
1511      public function testEnvArgument()
1512      {
1513          $env = ['FOO' => 'Foo', 'BAR' => 'Bar'];
1514          $cmd = '\\' === \DIRECTORY_SEPARATOR ? 'echo !FOO! !BAR! !BAZ!' : 'echo $FOO $BAR $BAZ';
1515          $p = new Process($cmd, null, $env);
1516          $p->run(null, ['BAR' => 'baR', 'BAZ' => 'baZ']);
1517  
1518          $this->assertSame('Foo baR baZ', rtrim($p->getOutput()));
1519          $this->assertSame($env, $p->getEnv());
1520      }
1521  
1522      public function testWaitStoppedDeadProcess()
1523      {
1524          $process = $this->getProcess(self::$phpBin.' '.__DIR__.'/ErrorProcessInitiator.php -e '.self::$phpBin);
1525          $process->start();
1526          $process->setTimeout(2);
1527          $process->wait();
1528          $this->assertFalse($process->isRunning());
1529      }
1530  
1531      /**
1532       * @param string      $commandline
1533       * @param string|null $cwd
1534       * @param string|null $input
1535       * @param int         $timeout
1536       *
1537       * @return Process
1538       */
1539      private function getProcess($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60)
1540      {
1541          $process = new Process($commandline, $cwd, $env, $input, $timeout);
1542          $process->inheritEnvironmentVariables();
1543  
1544          if (false !== $enhance = getenv('ENHANCE_SIGCHLD')) {
1545              try {
1546                  $process->setEnhanceSigchildCompatibility(false);
1547                  $process->getExitCode();
1548                  $this->fail('ENHANCE_SIGCHLD must be used together with a sigchild-enabled PHP.');
1549              } catch (RuntimeException $e) {
1550                  $this->assertSame('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.', $e->getMessage());
1551                  if ($enhance) {
1552                      $process->setEnhanceSigchildCompatibility(true);
1553                  } else {
1554                      self::$notEnhancedSigchild = true;
1555                  }
1556              }
1557          }
1558  
1559          if (self::$process) {
1560              self::$process->stop(0);
1561          }
1562  
1563          return self::$process = $process;
1564      }
1565  
1566      /**
1567       * @return Process
1568       */
1569      private function getProcessForCode($code, $cwd = null, array $env = null, $input = null, $timeout = 60)
1570      {
1571          return $this->getProcess([self::$phpBin, '-r', $code], $cwd, $env, $input, $timeout);
1572      }
1573  
1574      private function skipIfNotEnhancedSigchild($expectException = true)
1575      {
1576          if (self::$sigchild) {
1577              if (!$expectException) {
1578                  $this->markTestSkipped('PHP is compiled with --enable-sigchild.');
1579              } elseif (self::$notEnhancedSigchild) {
1580                  $this->expectException('Symfony\Component\Process\Exception\RuntimeException');
1581                  $this->expectExceptionMessage('This PHP has been compiled with --enable-sigchild.');
1582              }
1583          }
1584      }
1585  }
1586  
1587  class NonStringifiable
1588  {
1589  }


Generated: Tue Apr 7 19:44:41 2020 Cross-referenced by PHPXref 0.7.1