ILIAS  release_5-2 Revision v5.2.25-18-g3f80b828510
ProcessTest.php
Go to the documentation of this file.
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 
13 
21 
26 {
27  private static $phpBin;
28  private static $process;
29  private static $sigchild;
30  private static $notEnhancedSigchild = false;
31 
32  public static function setUpBeforeClass()
33  {
35  self::$phpBin = getenv('SYMFONY_PROCESS_PHP_TEST_BINARY') ?: ('phpdbg' === PHP_SAPI ? 'php' : $phpBin->find());
36  if ('\\' !== DIRECTORY_SEPARATOR) {
37  // exec is mandatory to deal with sending a signal to the process
38  // see https://github.com/symfony/symfony/issues/5030 about prepending
39  // command with exec
40  self::$phpBin = 'exec '.self::$phpBin;
41  }
42 
43  ob_start();
44  phpinfo(INFO_GENERAL);
45  self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
46  }
47 
48  protected function tearDown()
49  {
50  if (self::$process) {
51  self::$process->stop(0);
52  self::$process = null;
53  }
54  }
55 
57  {
58  if ('\\' === DIRECTORY_SEPARATOR) {
59  $this->markTestSkipped('This test is transient on Windows');
60  }
61  @trigger_error('Test Error', E_USER_NOTICE);
62  $process = $this->getProcess(self::$phpBin." -r 'sleep(3)'");
63  $process->run();
64  $actualError = error_get_last();
65  $this->assertEquals('Test Error', $actualError['message']);
66  $this->assertEquals(E_USER_NOTICE, $actualError['type']);
67  }
68 
73  {
74  $this->getProcess('', null, null, null, -1);
75  }
76 
81  {
82  $p = $this->getProcess('');
83  $p->setTimeout(-1);
84  }
85 
86  public function testFloatAndNullTimeout()
87  {
88  $p = $this->getProcess('');
89 
90  $p->setTimeout(10);
91  $this->assertSame(10.0, $p->getTimeout());
92 
93  $p->setTimeout(null);
94  $this->assertNull($p->getTimeout());
95 
96  $p->setTimeout(0.0);
97  $this->assertNull($p->getTimeout());
98  }
99 
104  {
105  $p = $this->getProcess(self::$phpBin.' '.__DIR__.'/NonStopableProcess.php 30');
106  $p->start();
107 
108  while (false === strpos($p->getOutput(), 'received')) {
109  usleep(1000);
110  }
111  $start = microtime(true);
112  $p->stop(0.1);
113 
114  $p->wait();
115 
116  $this->assertLessThan(15, microtime(true) - $start);
117  }
118 
120  {
121  // this code will result in a maximum of 2 reads of 8192 bytes by calling
122  // start() and isRunning(). by the time getOutput() is called the process
123  // has terminated so the internal pipes array is already empty. normally
124  // the call to start() will not read any data as the process will not have
125  // generated output, but this is non-deterministic so we must count it as
126  // a possibility. therefore we need 2 * PipesInterface::CHUNK_SIZE plus
127  // another byte which will never be read.
128  $expectedOutputSize = PipesInterface::CHUNK_SIZE * 2 + 2;
129 
130  $code = sprintf('echo str_repeat(\'*\', %d);', $expectedOutputSize);
131  $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg($code)));
132 
133  $p->start();
134 
135  // Don't call Process::run nor Process::wait to avoid any read of pipes
136  $h = new \ReflectionProperty($p, 'process');
137  $h->setAccessible(true);
138  $h = $h->getValue($p);
139  $s = @proc_get_status($h);
140 
141  while (!empty($s['running'])) {
142  usleep(1000);
143  $s = proc_get_status($h);
144  }
145 
146  $o = $p->getOutput();
147 
148  $this->assertEquals($expectedOutputSize, strlen($o));
149  }
150 
152  {
153  $process = $this->getProcess('echo foo');
154  $process->start(function ($type, $buffer) use (&$data) {
155  $data .= $buffer;
156  });
157 
158  $process->wait();
159 
160  $this->assertSame('foo'.PHP_EOL, $data);
161  }
162 
168  public function testProcessResponses($expected, $getter, $code)
169  {
170  $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg($code)));
171  $p->run();
172 
173  $this->assertSame($expected, $p->$getter());
174  }
175 
181  public function testProcessPipes($code, $size)
182  {
183  $expected = str_repeat(str_repeat('*', 1024), $size).'!';
184  $expectedLength = (1024 * $size) + 1;
185 
186  $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg($code)));
187  $p->setInput($expected);
188  $p->run();
189 
190  $this->assertEquals($expectedLength, strlen($p->getOutput()));
191  $this->assertEquals($expectedLength, strlen($p->getErrorOutput()));
192  }
193 
197  public function testSetStreamAsInput($code, $size)
198  {
199  $expected = str_repeat(str_repeat('*', 1024), $size).'!';
200  $expectedLength = (1024 * $size) + 1;
201 
202  $stream = fopen('php://temporary', 'w+');
203  fwrite($stream, $expected);
204  rewind($stream);
205 
206  $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg($code)));
207  $p->setInput($stream);
208  $p->run();
209 
210  fclose($stream);
211 
212  $this->assertEquals($expectedLength, strlen($p->getOutput()));
213  $this->assertEquals($expectedLength, strlen($p->getErrorOutput()));
214  }
215 
216  public function testLiveStreamAsInput()
217  {
218  $stream = fopen('php://memory', 'r+');
219  fwrite($stream, 'hello');
220  rewind($stream);
221 
222  $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('stream_copy_to_stream(STDIN, STDOUT);')));
223  $p->setInput($stream);
224  $p->start(function ($type, $data) use ($stream) {
225  if ('hello' === $data) {
226  fclose($stream);
227  }
228  });
229  $p->wait();
230 
231  $this->assertSame('hello', $p->getOutput());
232  }
233 
239  {
240  $process = $this->getProcess(self::$phpBin.' -r "sleep(30);"');
241  $process->start();
242  try {
243  $process->setInput('foobar');
244  $process->stop();
245  $this->fail('A LogicException should have been raised.');
246  } catch (LogicException $e) {
247  }
248  $process->stop();
249 
250  throw $e;
251  }
252 
258  public function testInvalidInput($value)
259  {
260  $process = $this->getProcess('foo');
261  $process->setInput($value);
262  }
263 
264  public function provideInvalidInputValues()
265  {
266  return array(
267  array(array()),
268  array(new NonStringifiable()),
269  );
270  }
271 
275  public function testValidInput($expected, $value)
276  {
277  $process = $this->getProcess('foo');
278  $process->setInput($value);
279  $this->assertSame($expected, $process->getInput());
280  }
281 
282  public function provideInputValues()
283  {
284  return array(
285  array(null, null),
286  array('24.5', 24.5),
287  array('input data', 'input data'),
288  );
289  }
290 
292  {
293  if ('\\' === DIRECTORY_SEPARATOR) {
294  return array(
295  array("2 \r\n2\r\n", '&&', '2'),
296  );
297  }
298 
299  return array(
300  array("1\n1\n", ';', '1'),
301  array("2\n2\n", '&&', '2'),
302  );
303  }
304 
308  public function testChainedCommandsOutput($expected, $operator, $input)
309  {
310  $process = $this->getProcess(sprintf('echo %s %s echo %s', $input, $operator, $input));
311  $process->run();
312  $this->assertEquals($expected, $process->getOutput());
313  }
314 
316  {
317  $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('echo \'foo\';')));
318 
319  $called = false;
320  $p->run(function ($type, $buffer) use (&$called) {
321  $called = $buffer === 'foo';
322  });
323 
324  $this->assertTrue($called, 'The callback should be executed with the output');
325  }
326 
328  {
329  $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('echo \'foo\';')));
330  $p->disableOutput();
331 
332  $called = false;
333  $p->run(function ($type, $buffer) use (&$called) {
334  $called = $buffer === 'foo';
335  });
336 
337  $this->assertTrue($called, 'The callback should be executed with the output');
338  }
339 
340  public function testGetErrorOutput()
341  {
342  $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }')));
343 
344  $p->run();
345  $this->assertEquals(3, preg_match_all('/ERROR/', $p->getErrorOutput(), $matches));
346  }
347 
348  public function testFlushErrorOutput()
349  {
350  $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }')));
351 
352  $p->run();
353  $p->clearErrorOutput();
354  $this->assertEmpty($p->getErrorOutput());
355  }
356 
360  public function testIncrementalOutput($getOutput, $getIncrementalOutput, $uri)
361  {
362  $lock = tempnam(sys_get_temp_dir(), __FUNCTION__);
363 
364  $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('file_put_contents($s = \''.$uri.'\', \'foo\'); flock(fopen('.var_export($lock, true).', \'r\'), LOCK_EX); file_put_contents($s, \'bar\');')));
365 
366  $h = fopen($lock, 'w');
367  flock($h, LOCK_EX);
368 
369  $p->start();
370 
371  foreach (array('foo', 'bar') as $s) {
372  while (false === strpos($p->$getOutput(), $s)) {
373  usleep(1000);
374  }
375 
376  $this->assertSame($s, $p->$getIncrementalOutput());
377  $this->assertSame('', $p->$getIncrementalOutput());
378 
379  flock($h, LOCK_UN);
380  }
381 
382  fclose($h);
383  }
384 
385  public function provideIncrementalOutput()
386  {
387  return array(
388  array('getOutput', 'getIncrementalOutput', 'php://stdout'),
389  array('getErrorOutput', 'getIncrementalErrorOutput', 'php://stderr'),
390  );
391  }
392 
393  public function testGetOutput()
394  {
395  $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { echo \' foo \'; $n++; }')));
396 
397  $p->run();
398  $this->assertEquals(3, preg_match_all('/foo/', $p->getOutput(), $matches));
399  }
400 
401  public function testFlushOutput()
402  {
403  $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n=0;while ($n<3) {echo \' foo \';$n++;}')));
404 
405  $p->run();
406  $p->clearOutput();
407  $this->assertEmpty($p->getOutput());
408  }
409 
410  public function testZeroAsOutput()
411  {
412  if ('\\' === DIRECTORY_SEPARATOR) {
413  // see http://stackoverflow.com/questions/7105433/windows-batch-echo-without-new-line
414  $p = $this->getProcess('echo | set /p dummyName=0');
415  } else {
416  $p = $this->getProcess('printf 0');
417  }
418 
419  $p->run();
420  $this->assertSame('0', $p->getOutput());
421  }
422 
423  public function testExitCodeCommandFailed()
424  {
425  if ('\\' === DIRECTORY_SEPARATOR) {
426  $this->markTestSkipped('Windows does not support POSIX exit code');
427  }
428  $this->skipIfNotEnhancedSigchild();
429 
430  // such command run in bash return an exitcode 127
431  $process = $this->getProcess('nonexistingcommandIhopeneversomeonewouldnameacommandlikethis');
432  $process->run();
433 
434  $this->assertGreaterThan(0, $process->getExitCode());
435  }
436 
437  public function testTTYCommand()
438  {
439  if ('\\' === DIRECTORY_SEPARATOR) {
440  $this->markTestSkipped('Windows does not have /dev/tty support');
441  }
442 
443  $process = $this->getProcess('echo "foo" >> /dev/null && '.self::$phpBin.' -r "usleep(100000);"');
444  $process->setTty(true);
445  $process->start();
446  $this->assertTrue($process->isRunning());
447  $process->wait();
448 
449  $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
450  }
451 
452  public function testTTYCommandExitCode()
453  {
454  if ('\\' === DIRECTORY_SEPARATOR) {
455  $this->markTestSkipped('Windows does have /dev/tty support');
456  }
457  $this->skipIfNotEnhancedSigchild();
458 
459  $process = $this->getProcess('echo "foo" >> /dev/null');
460  $process->setTty(true);
461  $process->run();
462 
463  $this->assertTrue($process->isSuccessful());
464  }
465 
470  public function testTTYInWindowsEnvironment()
471  {
472  if ('\\' !== DIRECTORY_SEPARATOR) {
473  $this->markTestSkipped('This test is for Windows platform only');
474  }
475 
476  $process = $this->getProcess('echo "foo" >> /dev/null');
477  $process->setTty(false);
478  $process->setTty(true);
479  }
480 
482  {
483  $this->skipIfNotEnhancedSigchild();
484 
485  $process = $this->getProcess('');
486  $this->assertNull($process->getExitCodeText());
487  }
488 
489  public function testPTYCommand()
490  {
491  if (!Process::isPtySupported()) {
492  $this->markTestSkipped('PTY is not supported on this operating system.');
493  }
494 
495  $process = $this->getProcess('echo "foo"');
496  $process->setPty(true);
497  $process->run();
498 
499  $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
500  $this->assertEquals("foo\r\n", $process->getOutput());
501  }
502 
503  public function testMustRun()
504  {
505  $this->skipIfNotEnhancedSigchild();
506 
507  $process = $this->getProcess('echo foo');
508 
509  $this->assertSame($process, $process->mustRun());
510  $this->assertEquals('foo'.PHP_EOL, $process->getOutput());
511  }
512 
514  {
515  $this->skipIfNotEnhancedSigchild();
516 
517  $process = $this->getProcess('echo foo')->mustRun();
518  $this->assertEquals(0, $process->getExitCode());
519  }
520 
524  public function testMustRunThrowsException()
525  {
526  $this->skipIfNotEnhancedSigchild();
527 
528  $process = $this->getProcess('exit 1');
529  $process->mustRun();
530  }
531 
532  public function testExitCodeText()
533  {
534  $this->skipIfNotEnhancedSigchild();
535 
536  $process = $this->getProcess('');
537  $r = new \ReflectionObject($process);
538  $p = $r->getProperty('exitcode');
539  $p->setAccessible(true);
540 
541  $p->setValue($process, 2);
542  $this->assertEquals('Misuse of shell builtins', $process->getExitCodeText());
543  }
544 
545  public function testStartIsNonBlocking()
546  {
547  $process = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
548  $start = microtime(true);
549  $process->start();
550  $end = microtime(true);
551  $this->assertLessThan(0.4, $end - $start);
552  $process->stop();
553  }
554 
555  public function testUpdateStatus()
556  {
557  $process = $this->getProcess('echo foo');
558  $process->run();
559  $this->assertTrue(strlen($process->getOutput()) > 0);
560  }
561 
563  {
564  $this->skipIfNotEnhancedSigchild();
565 
566  $process = $this->getProcess(self::$phpBin.' -r "usleep(100000);"');
567  $this->assertNull($process->getExitCode());
568  $process->start();
569  $this->assertNull($process->getExitCode());
570  $process->wait();
571  $this->assertEquals(0, $process->getExitCode());
572  }
573 
575  {
576  $this->skipIfNotEnhancedSigchild();
577 
578  $process = $this->getProcess(self::$phpBin.' -r "usleep(100000);"');
579  $process->run();
580  $this->assertEquals(0, $process->getExitCode());
581  $process->start();
582  $this->assertNull($process->getExitCode());
583  $process->wait();
584  $this->assertEquals(0, $process->getExitCode());
585  }
586 
587  public function testGetExitCode()
588  {
589  $this->skipIfNotEnhancedSigchild();
590 
591  $process = $this->getProcess('echo foo');
592  $process->run();
593  $this->assertSame(0, $process->getExitCode());
594  }
595 
596  public function testStatus()
597  {
598  $process = $this->getProcess(self::$phpBin.' -r "usleep(100000);"');
599  $this->assertFalse($process->isRunning());
600  $this->assertFalse($process->isStarted());
601  $this->assertFalse($process->isTerminated());
602  $this->assertSame(Process::STATUS_READY, $process->getStatus());
603  $process->start();
604  $this->assertTrue($process->isRunning());
605  $this->assertTrue($process->isStarted());
606  $this->assertFalse($process->isTerminated());
607  $this->assertSame(Process::STATUS_STARTED, $process->getStatus());
608  $process->wait();
609  $this->assertFalse($process->isRunning());
610  $this->assertTrue($process->isStarted());
611  $this->assertTrue($process->isTerminated());
612  $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
613  }
614 
615  public function testStop()
616  {
617  $process = $this->getProcess(self::$phpBin.' -r "sleep(31);"');
618  $process->start();
619  $this->assertTrue($process->isRunning());
620  $process->stop();
621  $this->assertFalse($process->isRunning());
622  }
623 
624  public function testIsSuccessful()
625  {
626  $this->skipIfNotEnhancedSigchild();
627 
628  $process = $this->getProcess('echo foo');
629  $process->run();
630  $this->assertTrue($process->isSuccessful());
631  }
632 
634  {
635  $this->skipIfNotEnhancedSigchild();
636 
637  $process = $this->getProcess(self::$phpBin.' -r "usleep(100000);"');
638  $process->start();
639 
640  $this->assertFalse($process->isSuccessful());
641 
642  $process->wait();
643 
644  $this->assertTrue($process->isSuccessful());
645  }
646 
647  public function testIsNotSuccessful()
648  {
649  $this->skipIfNotEnhancedSigchild();
650 
651  $process = $this->getProcess(self::$phpBin.' -r "throw new \Exception(\'BOUM\');"');
652  $process->run();
653  $this->assertFalse($process->isSuccessful());
654  }
655 
656  public function testProcessIsNotSignaled()
657  {
658  if ('\\' === DIRECTORY_SEPARATOR) {
659  $this->markTestSkipped('Windows does not support POSIX signals');
660  }
661  $this->skipIfNotEnhancedSigchild();
662 
663  $process = $this->getProcess('echo foo');
664  $process->run();
665  $this->assertFalse($process->hasBeenSignaled());
666  }
667 
669  {
670  if ('\\' === DIRECTORY_SEPARATOR) {
671  $this->markTestSkipped('Windows does not support POSIX signals');
672  }
673  $this->skipIfNotEnhancedSigchild();
674 
675  $process = $this->getProcess('echo foo');
676  $process->run();
677  $this->assertEquals(0, $process->getTermSignal());
678  }
679 
681  {
682  if ('\\' === DIRECTORY_SEPARATOR) {
683  $this->markTestSkipped('Windows does not support POSIX signals');
684  }
685  $this->skipIfNotEnhancedSigchild();
686 
687  $process = $this->getProcess(self::$phpBin.' -r "sleep(32);"');
688  $process->start();
689  $process->stop();
690  $this->assertTrue($process->hasBeenSignaled());
691  $this->assertEquals(15, $process->getTermSignal()); // SIGTERM
692  }
693 
699  {
700  if (!function_exists('posix_kill')) {
701  $this->markTestSkipped('Function posix_kill is required.');
702  }
703  $this->skipIfNotEnhancedSigchild(false);
704 
705  $process = $this->getProcess(self::$phpBin.' -r "sleep(32.1)"');
706  $process->start();
707  posix_kill($process->getPid(), 9); // SIGKILL
708 
709  $process->wait();
710  }
711 
712  public function testRestart()
713  {
714  $process1 = $this->getProcess(self::$phpBin.' -r "echo getmypid();"');
715  $process1->run();
716  $process2 = $process1->restart();
717 
718  $process2->wait(); // wait for output
719 
720  // Ensure that both processed finished and the output is numeric
721  $this->assertFalse($process1->isRunning());
722  $this->assertFalse($process2->isRunning());
723  $this->assertTrue(is_numeric($process1->getOutput()));
724  $this->assertTrue(is_numeric($process2->getOutput()));
725 
726  // Ensure that restart returned a new process by check that the output is different
727  $this->assertNotEquals($process1->getOutput(), $process2->getOutput());
728  }
729 
734  public function testRunProcessWithTimeout()
735  {
736  $process = $this->getProcess(self::$phpBin.' -r "sleep(30);"');
737  $process->setTimeout(0.1);
738  $start = microtime(true);
739  try {
740  $process->run();
741  $this->fail('A RuntimeException should have been raised');
742  } catch (RuntimeException $e) {
743  }
744 
745  $this->assertLessThan(15, microtime(true) - $start);
746 
747  throw $e;
748  }
749 
751  {
752  $process = $this->getProcess('echo foo');
753  $this->assertNull($process->checkTimeout());
754  }
755 
757  {
758  $process = $this->getProcess('echo foo');
759  $process->run();
760  $this->assertNull($process->checkTimeout());
761  }
762 
768  {
769  $process = $this->getProcess(self::$phpBin.' -r "sleep(33);"');
770  $process->setTimeout(0.1);
771 
772  $process->start();
773  $start = microtime(true);
774 
775  try {
776  while ($process->isRunning()) {
777  $process->checkTimeout();
778  usleep(100000);
779  }
780  $this->fail('A ProcessTimedOutException should have been raised');
781  } catch (ProcessTimedOutException $e) {
782  }
783 
784  $this->assertLessThan(15, microtime(true) - $start);
785 
786  throw $e;
787  }
788 
789  public function testIdleTimeout()
790  {
791  $process = $this->getProcess(self::$phpBin.' -r "sleep(34);"');
792  $process->setTimeout(60);
793  $process->setIdleTimeout(0.1);
794 
795  try {
796  $process->run();
797 
798  $this->fail('A timeout exception was expected.');
799  } catch (ProcessTimedOutException $e) {
800  $this->assertTrue($e->isIdleTimeout());
801  $this->assertFalse($e->isGeneralTimeout());
802  $this->assertEquals(0.1, $e->getExceededTimeout());
803  }
804  }
805 
807  {
808  $process = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('while (true) {echo \'foo \'; usleep(1000);}')));
809  $process->setTimeout(1);
810  $process->start();
811 
812  while (false === strpos($process->getOutput(), 'foo')) {
813  usleep(1000);
814  }
815 
816  $process->setIdleTimeout(0.5);
817 
818  try {
819  $process->wait();
820  $this->fail('A timeout exception was expected.');
821  } catch (ProcessTimedOutException $e) {
822  $this->assertTrue($e->isGeneralTimeout(), 'A general timeout is expected.');
823  $this->assertFalse($e->isIdleTimeout(), 'No idle timeout is expected.');
824  $this->assertEquals(1, $e->getExceededTimeout());
825  }
826  }
827 
832  public function testStartAfterATimeout()
833  {
834  $process = $this->getProcess(self::$phpBin.' -r "sleep(35);"');
835  $process->setTimeout(0.1);
836 
837  try {
838  $process->run();
839  $this->fail('A ProcessTimedOutException should have been raised.');
840  } catch (ProcessTimedOutException $e) {
841  }
842  $this->assertFalse($process->isRunning());
843  $process->start();
844  $this->assertTrue($process->isRunning());
845  $process->stop(0);
846 
847  throw $e;
848  }
849 
850  public function testGetPid()
851  {
852  $process = $this->getProcess(self::$phpBin.' -r "sleep(36);"');
853  $process->start();
854  $this->assertGreaterThan(0, $process->getPid());
855  $process->stop(0);
856  }
857 
858  public function testGetPidIsNullBeforeStart()
859  {
860  $process = $this->getProcess('foo');
861  $this->assertNull($process->getPid());
862  }
863 
864  public function testGetPidIsNullAfterRun()
865  {
866  $process = $this->getProcess('echo foo');
867  $process->run();
868  $this->assertNull($process->getPid());
869  }
870 
874  public function testSignal()
875  {
876  $process = $this->getProcess(self::$phpBin.' '.__DIR__.'/SignalListener.php');
877  $process->start();
878 
879  while (false === strpos($process->getOutput(), 'Caught')) {
880  usleep(1000);
881  }
882  $process->signal(SIGUSR1);
883  $process->wait();
884 
885  $this->assertEquals('Caught SIGUSR1', $process->getOutput());
886  }
887 
892  {
893  $this->skipIfNotEnhancedSigchild();
894 
895  $process = $this->getProcess('sleep 4');
896  $process->start();
897  $process->signal(SIGKILL);
898 
899  while ($process->isRunning()) {
900  usleep(10000);
901  }
902 
903  $this->assertFalse($process->isRunning());
904  $this->assertTrue($process->hasBeenSignaled());
905  $this->assertFalse($process->isSuccessful());
906  $this->assertEquals(137, $process->getExitCode());
907  }
908 
913  public function testSignalProcessNotRunning()
914  {
915  $process = $this->getProcess('foo');
916  $process->signal(1); // SIGHUP
917  }
918 
922  public function testMethodsThatNeedARunningProcess($method)
923  {
924  $process = $this->getProcess('foo');
925  $this->setExpectedException('Symfony\Component\Process\Exception\LogicException', sprintf('Process must be started before calling %s.', $method));
926  $process->{$method}();
927  }
928 
930  {
931  return array(
932  array('getOutput'),
933  array('getIncrementalOutput'),
934  array('getErrorOutput'),
935  array('getIncrementalErrorOutput'),
936  array('wait'),
937  );
938  }
939 
945  public function testMethodsThatNeedATerminatedProcess($method)
946  {
947  $process = $this->getProcess(self::$phpBin.' -r "sleep(37);"');
948  $process->start();
949  try {
950  $process->{$method}();
951  $process->stop(0);
952  $this->fail('A LogicException must have been thrown');
953  } catch (\Exception $e) {
954  }
955  $process->stop(0);
956 
957  throw $e;
958  }
959 
961  {
962  return array(
963  array('hasBeenSignaled'),
964  array('getTermSignal'),
965  array('hasBeenStopped'),
966  array('getStopSignal'),
967  );
968  }
969 
974  public function testWrongSignal($signal)
975  {
976  if ('\\' === DIRECTORY_SEPARATOR) {
977  $this->markTestSkipped('POSIX signals do not work on Windows');
978  }
979 
980  $process = $this->getProcess(self::$phpBin.' -r "sleep(38);"');
981  $process->start();
982  try {
983  $process->signal($signal);
984  $this->fail('A RuntimeException must have been thrown');
985  } catch (RuntimeException $e) {
986  $process->stop(0);
987  }
988 
989  throw $e;
990  }
991 
992  public function provideWrongSignal()
993  {
994  return array(
995  array(-4),
996  array('Céphalopodes'),
997  );
998  }
999 
1001  {
1002  $p = $this->getProcess('foo');
1003  $this->assertFalse($p->isOutputDisabled());
1004  $p->disableOutput();
1005  $this->assertTrue($p->isOutputDisabled());
1006  $p->enableOutput();
1007  $this->assertFalse($p->isOutputDisabled());
1008  }
1009 
1015  {
1016  $p = $this->getProcess(self::$phpBin.' -r "sleep(39);"');
1017  $p->start();
1018  $p->disableOutput();
1019  }
1020 
1026  {
1027  $p = $this->getProcess(self::$phpBin.' -r "sleep(40);"');
1028  $p->disableOutput();
1029  $p->start();
1030  $p->enableOutput();
1031  }
1032 
1034  {
1035  $p = $this->getProcess('echo foo');
1036  $p->disableOutput();
1037  $p->run();
1038  $p->enableOutput();
1039  $p->disableOutput();
1040  $this->assertTrue($p->isOutputDisabled());
1041  }
1042 
1048  {
1049  $process = $this->getProcess('foo');
1050  $process->setIdleTimeout(1);
1051  $process->disableOutput();
1052  }
1053 
1059  {
1060  $process = $this->getProcess('foo');
1061  $process->disableOutput();
1062  $process->setIdleTimeout(1);
1063  }
1064 
1066  {
1067  $process = $this->getProcess('foo');
1068  $process->disableOutput();
1069  $this->assertSame($process, $process->setIdleTimeout(null));
1070  }
1071 
1077  public function testGetOutputWhileDisabled($fetchMethod)
1078  {
1079  $p = $this->getProcess(self::$phpBin.' -r "sleep(41);"');
1080  $p->disableOutput();
1081  $p->start();
1082  $p->{$fetchMethod}();
1083  }
1084 
1086  {
1087  return array(
1088  array('getOutput'),
1089  array('getIncrementalOutput'),
1090  array('getErrorOutput'),
1091  array('getIncrementalErrorOutput'),
1092  );
1093  }
1094 
1096  {
1097  $process = $this->getProcess(self::$phpBin.' -r "echo 123; sleep(42);"');
1098  $process->run(function () use ($process) {
1099  $process->stop();
1100  });
1101  $this->assertTrue(true, 'A call to stop() is not expected to cause wait() to throw a RuntimeException');
1102  }
1103 
1105  {
1106  $process = $this->getProcess(self::$phpBin.' -r "echo 123; sleep(43);"');
1107  $process->run(function () use ($process) {
1108  $process->signal(9); // SIGKILL
1109  });
1110  $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException');
1111  }
1112 
1114  {
1115  $process = $this->getProcess(self::$phpBin.' -r "echo 123; sleep(44);"');
1116  $process->run(function () use ($process) {
1117  $process->signal(15); // SIGTERM
1118  });
1119  $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException');
1120  }
1121 
1122  public function responsesCodeProvider()
1123  {
1124  return array(
1125  //expected output / getter / code to execute
1126  //array(1,'getExitCode','exit(1);'),
1127  //array(true,'isSuccessful','exit();'),
1128  array('output', 'getOutput', 'echo \'output\';'),
1129  );
1130  }
1131 
1132  public function pipesCodeProvider()
1133  {
1134  $variations = array(
1135  'fwrite(STDOUT, $in = file_get_contents(\'php://stdin\')); fwrite(STDERR, $in);',
1136  'include \''.__DIR__.'/PipeStdinInStdoutStdErrStreamSelect.php\';',
1137  );
1138 
1139  if ('\\' === DIRECTORY_SEPARATOR) {
1140  // Avoid XL buffers on Windows because of https://bugs.php.net/bug.php?id=65650
1141  $sizes = array(1, 2, 4, 8);
1142  } else {
1143  $sizes = array(1, 16, 64, 1024, 4096);
1144  }
1145 
1146  $codes = array();
1147  foreach ($sizes as $size) {
1148  foreach ($variations as $code) {
1149  $codes[] = array($code, $size);
1150  }
1151  }
1152 
1153  return $codes;
1154  }
1155 
1159  public function testIncrementalOutputDoesNotRequireAnotherCall($stream, $method)
1160  {
1161  $process = $this->getProcess(self::$phpBin.' -r '.escapeshellarg('$n = 0; while ($n < 3) { file_put_contents(\''.$stream.'\', $n, 1); $n++; usleep(1000); }'), null, null, null, null);
1162  $process->start();
1163  $result = '';
1164  $limit = microtime(true) + 3;
1165  $expected = '012';
1166 
1167  while ($result !== $expected && microtime(true) < $limit) {
1168  $result .= $process->$method();
1169  }
1170 
1171  $this->assertSame($expected, $result);
1172  $process->stop();
1173  }
1174 
1175  public function provideVariousIncrementals()
1176  {
1177  return array(
1178  array('php://stdout', 'getIncrementalOutput'),
1179  array('php://stderr', 'getIncrementalErrorOutput'),
1180  );
1181  }
1182 
1183  public function testIteratorInput()
1184  {
1185  $input = function () {
1186  yield 'ping';
1187  yield 'pong';
1188  };
1189 
1190  $process = $this->getProcess(self::$phpBin.' -r '.escapeshellarg('stream_copy_to_stream(STDIN, STDOUT);'), null, null, $input());
1191  $process->run();
1192  $this->assertSame('pingpong', $process->getOutput());
1193  }
1194 
1195  public function testSimpleInputStream()
1196  {
1197  $input = new InputStream();
1198 
1199  $process = $this->getProcess(self::$phpBin.' -r '.escapeshellarg('echo \'ping\'; stream_copy_to_stream(STDIN, STDOUT);'));
1200  $process->setInput($input);
1201 
1202  $process->start(function ($type, $data) use ($input) {
1203  if ('ping' === $data) {
1204  $input->write('pang');
1205  } elseif (!$input->isClosed()) {
1206  $input->write('pong');
1207  $input->close();
1208  }
1209  });
1210 
1211  $process->wait();
1212  $this->assertSame('pingpangpong', $process->getOutput());
1213  }
1214 
1216  {
1217  $i = 0;
1218  $stream = fopen('php://memory', 'w+');
1219  $stream = function () use ($stream, &$i) {
1220  if ($i < 3) {
1221  rewind($stream);
1222  fwrite($stream, ++$i);
1223  rewind($stream);
1224 
1225  return $stream;
1226  }
1227  };
1228 
1229  $input = new InputStream();
1230  $input->onEmpty($stream);
1231  $input->write($stream());
1232 
1233  $process = $this->getProcess(self::$phpBin.' -r '.escapeshellarg('echo fread(STDIN, 3);'));
1234  $process->setInput($input);
1235  $process->start(function ($type, $data) use ($input) {
1236  $input->close();
1237  });
1238 
1239  $process->wait();
1240  $this->assertSame('123', $process->getOutput());
1241  }
1242 
1244  {
1245  $input = new InputStream();
1246  $input->onEmpty(function ($input) {
1247  yield 'pong';
1248  $input->close();
1249  });
1250 
1251  $process = $this->getProcess(self::$phpBin.' -r '.escapeshellarg('stream_copy_to_stream(STDIN, STDOUT);'));
1252  $process->setInput($input);
1253  $process->start();
1254  $input->write('ping');
1255  $process->wait();
1256  $this->assertSame('pingpong', $process->getOutput());
1257  }
1258 
1259  public function testInputStreamOnEmpty()
1260  {
1261  $i = 0;
1262  $input = new InputStream();
1263  $input->onEmpty(function () use (&$i) {++$i;});
1264 
1265  $process = $this->getProcess(self::$phpBin.' -r '.escapeshellarg('echo 123; echo fread(STDIN, 1); echo 456;'));
1266  $process->setInput($input);
1267  $process->start(function ($type, $data) use ($input) {
1268  if ('123' === $data) {
1269  $input->close();
1270  }
1271  });
1272  $process->wait();
1273 
1274  $this->assertSame(0, $i, 'InputStream->onEmpty callback should be called only when the input *becomes* empty');
1275  $this->assertSame('123456', $process->getOutput());
1276  }
1277 
1278  public function testIteratorOutput()
1279  {
1280  $input = new InputStream();
1281 
1282  $process = $this->getProcess(self::$phpBin.' -r '.escapeshellarg('fwrite(STDOUT, 123); fwrite(STDERR, 234); flush(); usleep(10000); fwrite(STDOUT, fread(STDIN, 3)); fwrite(STDERR, 456);'));
1283  $process->setInput($input);
1284  $process->start();
1285  $output = array();
1286 
1287  foreach ($process as $type => $data) {
1288  $output[] = array($type, $data);
1289  break;
1290  }
1291  $expectedOutput = array(
1292  array($process::OUT, '123'),
1293  );
1294  $this->assertSame($expectedOutput, $output);
1295 
1296  $input->write(345);
1297 
1298  foreach ($process as $type => $data) {
1299  $output[] = array($type, $data);
1300  }
1301 
1302  $this->assertSame('', $process->getOutput());
1303  $this->assertFalse($process->isRunning());
1304 
1305  $expectedOutput = array(
1306  array($process::OUT, '123'),
1307  array($process::ERR, '234'),
1308  array($process::OUT, '345'),
1309  array($process::ERR, '456'),
1310  );
1311  $this->assertSame($expectedOutput, $output);
1312  }
1313 
1315  {
1316  $input = new InputStream();
1317 
1318  $process = $this->getProcess(self::$phpBin.' -r '.escapeshellarg('fwrite(STDOUT, fread(STDIN, 3));'));
1319  $process->setInput($input);
1320  $process->start();
1321  $output = array();
1322 
1323  foreach ($process->getIterator($process::ITER_NON_BLOCKING | $process::ITER_KEEP_OUTPUT) as $type => $data) {
1324  $output[] = array($type, $data);
1325  break;
1326  }
1327  $expectedOutput = array(
1328  array($process::OUT, ''),
1329  );
1330  $this->assertSame($expectedOutput, $output);
1331 
1332  $input->write(123);
1333 
1334  foreach ($process->getIterator($process::ITER_NON_BLOCKING | $process::ITER_KEEP_OUTPUT) as $type => $data) {
1335  if ('' !== $data) {
1336  $output[] = array($type, $data);
1337  }
1338  }
1339 
1340  $this->assertSame('123', $process->getOutput());
1341  $this->assertFalse($process->isRunning());
1342 
1343  $expectedOutput = array(
1344  array($process::OUT, ''),
1345  array($process::OUT, '123'),
1346  );
1347  $this->assertSame($expectedOutput, $output);
1348  }
1349 
1350  public function testChainedProcesses()
1351  {
1352  $p1 = new Process(self::$phpBin.' -r '.escapeshellarg('fwrite(STDERR, 123); fwrite(STDOUT, 456);'));
1353  $p2 = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('stream_copy_to_stream(STDIN, STDOUT);')));
1354  $p2->setInput($p1);
1355 
1356  $p1->start();
1357  $p2->run();
1358 
1359  $this->assertSame('123', $p1->getErrorOutput());
1360  $this->assertSame('', $p1->getOutput());
1361  $this->assertSame('', $p2->getErrorOutput());
1362  $this->assertSame('456', $p2->getOutput());
1363  }
1364 
1375  private function getProcess($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = array())
1376  {
1377  $process = new Process($commandline, $cwd, $env, $input, $timeout, $options);
1378 
1379  if (false !== $enhance = getenv('ENHANCE_SIGCHLD')) {
1380  try {
1381  $process->setEnhanceSigchildCompatibility(false);
1382  $process->getExitCode();
1383  $this->fail('ENHANCE_SIGCHLD must be used together with a sigchild-enabled PHP.');
1384  } catch (RuntimeException $e) {
1385  $this->assertSame('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.', $e->getMessage());
1386  if ($enhance) {
1387  $process->setEnhanceSigchildCompatibility(true);
1388  } else {
1389  self::$notEnhancedSigchild = true;
1390  }
1391  }
1392  }
1393 
1394  if (self::$process) {
1395  self::$process->stop(0);
1396  }
1397 
1398  return self::$process = $process;
1399  }
1400 
1401  private function skipIfNotEnhancedSigchild($expectException = true)
1402  {
1403  if (self::$sigchild) {
1404  if (!$expectException) {
1405  $this->markTestSkipped('PHP is compiled with --enable-sigchild.');
1406  } elseif (self::$notEnhancedSigchild) {
1407  $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild.');
1408  }
1409  }
1410  }
1411 }
1412 
1414 {
1415 }
testSignalProcessNotRunning()
Can not send signal on a non running process.
$size
Definition: RandomTest.php:79
Provides a way to continuously write to the input of a Process until the InputStream is closed...
Definition: InputStream.php:21
$h
$code
Definition: example_050.php:99
Exception that is thrown when a process times out.
testDisableOutputWhileIdleTimeoutIsSet()
Output can not be disabled while an idle timeout is set.
skipIfNotEnhancedSigchild($expectException=true)
testSetInputWhileRunningThrowsAnException()
Input can not be set while the process is running.
testChainedCommandsOutput($expected, $operator, $input)
chainedCommandsOutputProvider
testIncrementalOutputDoesNotRequireAnotherCall($stream, $method)
provideVariousIncrementals
An executable finder specifically designed for the PHP executable.
testEnableOutputWhileRunningThrowsException()
Enabling output while the process is running is not possible.
if(!is_dir( $entity_dir)) exit("Fatal Error ([A-Za-z0-9]+)\+" &#(? foreach( $entity_files as $file) $output
$r
Definition: example_031.php:79
testProcessThrowsExceptionWhenExternallySignaled()
The process has been signaled
if(!is_array($argv)) $options
testMethodsThatNeedARunningProcess($method)
provideMethodsThatNeedARunningProcess
if($is_dev) echo "Review changes write something in WHATSNEW and and then commit with log PHP_EOL
testStopWithTimeoutIsActuallyWorking()
extension pcntl
testMethodsThatNeedATerminatedProcess($method)
provideMethodsThatNeedATerminatedProcess Symfony Process must be terminated before calling ...
testRunProcessWithTimeout()
exceeded the timeout of 0.1 seconds.
getProcess($commandline, $cwd=null, array $env=null, $input=null, $timeout=60, array $options=array())
testTTYInWindowsEnvironment()
TTY mode is not supported on Windows platform.
testProcessPipes($code, $size)
tests results from sub processes.
testWrongSignal($signal)
provideWrongSignal
testDisableOutputWhileRunningThrowsException()
Disabling output while the process is running is not possible.
$n
Definition: RandomTest.php:80
Create styles array
The data for the language used.
LogicException for the Process Component.
testGetOutputWhileDisabled($fetchMethod)
provideOutputFetchingMethods Output has been disabled.
testInvalidInput($value)
provideInvalidInputValues Symfony::setInput only accepts strings, Traversable objects or stream re...
testProcessResponses($expected, $getter, $code)
tests results from sub processes.
static isPtySupported()
Returns whether PTY is supported on the current operating system.
Definition: Process.php:1232
testSetIdleTimeoutWhileOutputIsDisabled()
timeout can not be set while the output is disabled.
testValidInput($expected, $value)
provideInputValues
testCheckTimeoutOnStartedProcess()
exceeded the timeout of 0.1 seconds.
Process is a thin wrapper around proc_* functions to easily start independent PHP processes...
Definition: Process.php:30
testSetStreamAsInput($code, $size)
pipesCodeProvider
testIncrementalOutput($getOutput, $getIncrementalOutput, $uri)
provideIncrementalOutput
testStartAfterATimeout()
exceeded the timeout of 0.1 seconds.