PageRenderTime 52ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/symfony/symfony/src/Symfony/Component/Process/Tests/AbstractProcessTest.php

https://gitlab.com/TouirMohamedMarwen/Symfony2
PHP | 1125 lines | 834 code | 176 blank | 115 comment | 35 complexity | 6ecbad1eb5bb8a8457e176792d5d7da3 MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Process\Tests;
  11. use Symfony\Component\Process\Exception\ProcessTimedOutException;
  12. use Symfony\Component\Process\Exception\LogicException;
  13. use Symfony\Component\Process\Pipes\PipesInterface;
  14. use Symfony\Component\Process\Process;
  15. use Symfony\Component\Process\Exception\RuntimeException;
  16. /**
  17. * @author Robert Schönthal <seroscho@googlemail.com>
  18. */
  19. abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
  20. {
  21. public function testThatProcessDoesNotThrowWarningDuringRun()
  22. {
  23. @trigger_error('Test Error', E_USER_NOTICE);
  24. $process = $this->getProcess("php -r 'sleep(3)'");
  25. $process->run();
  26. $actualError = error_get_last();
  27. $this->assertEquals('Test Error', $actualError['message']);
  28. $this->assertEquals(E_USER_NOTICE, $actualError['type']);
  29. }
  30. /**
  31. * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
  32. */
  33. public function testNegativeTimeoutFromConstructor()
  34. {
  35. $this->getProcess('', null, null, null, -1);
  36. }
  37. /**
  38. * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
  39. */
  40. public function testNegativeTimeoutFromSetter()
  41. {
  42. $p = $this->getProcess('');
  43. $p->setTimeout(-1);
  44. }
  45. public function testFloatAndNullTimeout()
  46. {
  47. $p = $this->getProcess('');
  48. $p->setTimeout(10);
  49. $this->assertSame(10.0, $p->getTimeout());
  50. $p->setTimeout(null);
  51. $this->assertNull($p->getTimeout());
  52. $p->setTimeout(0.0);
  53. $this->assertNull($p->getTimeout());
  54. }
  55. public function testStopWithTimeoutIsActuallyWorking()
  56. {
  57. $this->verifyPosixIsEnabled();
  58. // exec is mandatory here since we send a signal to the process
  59. // see https://github.com/symfony/symfony/issues/5030 about prepending
  60. // command with exec
  61. $p = $this->getProcess('exec php '.__DIR__.'/NonStopableProcess.php 3');
  62. $p->start();
  63. usleep(100000);
  64. $start = microtime(true);
  65. $p->stop(1.1, SIGKILL);
  66. while ($p->isRunning()) {
  67. usleep(1000);
  68. }
  69. $duration = microtime(true) - $start;
  70. $this->assertLessThan(4, $duration);
  71. }
  72. public function testAllOutputIsActuallyReadOnTermination()
  73. {
  74. // this code will result in a maximum of 2 reads of 8192 bytes by calling
  75. // start() and isRunning(). by the time getOutput() is called the process
  76. // has terminated so the internal pipes array is already empty. normally
  77. // the call to start() will not read any data as the process will not have
  78. // generated output, but this is non-deterministic so we must count it as
  79. // a possibility. therefore we need 2 * PipesInterface::CHUNK_SIZE plus
  80. // another byte which will never be read.
  81. $expectedOutputSize = PipesInterface::CHUNK_SIZE * 2 + 2;
  82. $code = sprintf('echo str_repeat(\'*\', %d);', $expectedOutputSize);
  83. $p = $this->getProcess(sprintf('php -r %s', escapeshellarg($code)));
  84. $p->start();
  85. // Let's wait enough time for process to finish...
  86. // Here we don't call Process::run or Process::wait to avoid any read of pipes
  87. usleep(500000);
  88. if ($p->isRunning()) {
  89. $this->markTestSkipped('Process execution did not complete in the required time frame');
  90. }
  91. $o = $p->getOutput();
  92. $this->assertEquals($expectedOutputSize, strlen($o));
  93. }
  94. public function testCallbacksAreExecutedWithStart()
  95. {
  96. $data = '';
  97. $process = $this->getProcess('echo foo && php -r "sleep(1);" && echo foo');
  98. $process->start(function ($type, $buffer) use (&$data) {
  99. $data .= $buffer;
  100. });
  101. while ($process->isRunning()) {
  102. usleep(10000);
  103. }
  104. $this->assertEquals(2, preg_match_all('/foo/', $data, $matches));
  105. }
  106. /**
  107. * tests results from sub processes
  108. *
  109. * @dataProvider responsesCodeProvider
  110. */
  111. public function testProcessResponses($expected, $getter, $code)
  112. {
  113. $p = $this->getProcess(sprintf('php -r %s', escapeshellarg($code)));
  114. $p->run();
  115. $this->assertSame($expected, $p->$getter());
  116. }
  117. /**
  118. * tests results from sub processes
  119. *
  120. * @dataProvider pipesCodeProvider
  121. */
  122. public function testProcessPipes($code, $size)
  123. {
  124. $expected = str_repeat(str_repeat('*', 1024), $size).'!';
  125. $expectedLength = (1024 * $size) + 1;
  126. $p = $this->getProcess(sprintf('php -r %s', escapeshellarg($code)));
  127. $p->setInput($expected);
  128. $p->run();
  129. $this->assertEquals($expectedLength, strlen($p->getOutput()));
  130. $this->assertEquals($expectedLength, strlen($p->getErrorOutput()));
  131. }
  132. /**
  133. * @dataProvider pipesCodeProvider
  134. */
  135. public function testSetStreamAsInput($code, $size)
  136. {
  137. $expected = str_repeat(str_repeat('*', 1024), $size).'!';
  138. $expectedLength = (1024 * $size) + 1;
  139. $stream = fopen('php://temporary', 'w+');
  140. fwrite($stream, $expected);
  141. rewind($stream);
  142. $p = $this->getProcess(sprintf('php -r %s', escapeshellarg($code)));
  143. $p->setInput($stream);
  144. $p->run();
  145. fclose($stream);
  146. $this->assertEquals($expectedLength, strlen($p->getOutput()));
  147. $this->assertEquals($expectedLength, strlen($p->getErrorOutput()));
  148. }
  149. public function testSetInputWhileRunningThrowsAnException()
  150. {
  151. $process = $this->getProcess('php -r "usleep(500000);"');
  152. $process->start();
  153. try {
  154. $process->setInput('foobar');
  155. $process->stop();
  156. $this->fail('A LogicException should have been raised.');
  157. } catch (LogicException $e) {
  158. $this->assertEquals('Input can not be set while the process is running.', $e->getMessage());
  159. }
  160. $process->stop();
  161. }
  162. /**
  163. * @dataProvider provideInvalidInputValues
  164. * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
  165. * @expectedExceptionMessage Symfony\Component\Process\Process::setInput only accepts strings or stream resources.
  166. */
  167. public function testInvalidInput($value)
  168. {
  169. $process = $this->getProcess('php -v');
  170. $process->setInput($value);
  171. }
  172. public function provideInvalidInputValues()
  173. {
  174. return array(
  175. array(array()),
  176. array(new NonStringifiable()),
  177. );
  178. }
  179. /**
  180. * @dataProvider provideInputValues
  181. */
  182. public function testValidInput($expected, $value)
  183. {
  184. $process = $this->getProcess('php -v');
  185. $process->setInput($value);
  186. $this->assertSame($expected, $process->getInput());
  187. }
  188. public function provideInputValues()
  189. {
  190. return array(
  191. array(null, null),
  192. array('24.5', 24.5),
  193. array('input data', 'input data'),
  194. // to maintain BC, supposed to be removed in 3.0
  195. array('stringifiable', new Stringifiable()),
  196. );
  197. }
  198. public function chainedCommandsOutputProvider()
  199. {
  200. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  201. return array(
  202. array("2 \r\n2\r\n", '&&', '2'),
  203. );
  204. }
  205. return array(
  206. array("1\n1\n", ';', '1'),
  207. array("2\n2\n", '&&', '2'),
  208. );
  209. }
  210. /**
  211. *
  212. * @dataProvider chainedCommandsOutputProvider
  213. */
  214. public function testChainedCommandsOutput($expected, $operator, $input)
  215. {
  216. $process = $this->getProcess(sprintf('echo %s %s echo %s', $input, $operator, $input));
  217. $process->run();
  218. $this->assertEquals($expected, $process->getOutput());
  219. }
  220. public function testCallbackIsExecutedForOutput()
  221. {
  222. $p = $this->getProcess(sprintf('php -r %s', escapeshellarg('echo \'foo\';')));
  223. $called = false;
  224. $p->run(function ($type, $buffer) use (&$called) {
  225. $called = $buffer === 'foo';
  226. });
  227. $this->assertTrue($called, 'The callback should be executed with the output');
  228. }
  229. public function testGetErrorOutput()
  230. {
  231. $p = $this->getProcess(sprintf('php -r %s', escapeshellarg('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }')));
  232. $p->run();
  233. $this->assertEquals(3, preg_match_all('/ERROR/', $p->getErrorOutput(), $matches));
  234. }
  235. public function testGetIncrementalErrorOutput()
  236. {
  237. // use a lock file to toggle between writing ("W") and reading ("R") the
  238. // error stream
  239. $lock = tempnam(sys_get_temp_dir(), get_class($this).'Lock');
  240. file_put_contents($lock, 'W');
  241. $p = $this->getProcess(sprintf('php -r %s', escapeshellarg('$n = 0; while ($n < 3) { if (\'W\' === file_get_contents('.var_export($lock, true).')) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; file_put_contents('.var_export($lock, true).', \'R\'); } usleep(100); }')));
  242. $p->start();
  243. while ($p->isRunning()) {
  244. if ('R' === file_get_contents($lock)) {
  245. $this->assertLessThanOrEqual(1, preg_match_all('/ERROR/', $p->getIncrementalErrorOutput(), $matches));
  246. file_put_contents($lock, 'W');
  247. }
  248. usleep(100);
  249. }
  250. unlink($lock);
  251. }
  252. public function testFlushErrorOutput()
  253. {
  254. $p = $this->getProcess(sprintf('php -r %s', escapeshellarg('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }')));
  255. $p->run();
  256. $p->clearErrorOutput();
  257. $this->assertEmpty($p->getErrorOutput());
  258. }
  259. public function testGetOutput()
  260. {
  261. $p = $this->getProcess(sprintf('php -r %s', escapeshellarg('$n = 0; while ($n < 3) { echo \' foo \'; $n++; }')));
  262. $p->run();
  263. $this->assertEquals(3, preg_match_all('/foo/', $p->getOutput(), $matches));
  264. }
  265. public function testGetIncrementalOutput()
  266. {
  267. // use a lock file to toggle between writing ("W") and reading ("R") the
  268. // output stream
  269. $lock = tempnam(sys_get_temp_dir(), get_class($this).'Lock');
  270. file_put_contents($lock, 'W');
  271. $p = $this->getProcess(sprintf('php -r %s', escapeshellarg('$n = 0; while ($n < 3) { if (\'W\' === file_get_contents('.var_export($lock, true).')) { echo \' foo \'; $n++; file_put_contents('.var_export($lock, true).', \'R\'); } usleep(100); }')));
  272. $p->start();
  273. while ($p->isRunning()) {
  274. if ('R' === file_get_contents($lock)) {
  275. $this->assertLessThanOrEqual(1, preg_match_all('/foo/', $p->getIncrementalOutput(), $matches));
  276. file_put_contents($lock, 'W');
  277. }
  278. usleep(100);
  279. }
  280. unlink($lock);
  281. }
  282. public function testFlushOutput()
  283. {
  284. $p = $this->getProcess(sprintf('php -r %s', escapeshellarg('$n=0;while ($n<3) {echo \' foo \';$n++;}')));
  285. $p->run();
  286. $p->clearOutput();
  287. $this->assertEmpty($p->getOutput());
  288. }
  289. public function testZeroAsOutput()
  290. {
  291. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  292. // see http://stackoverflow.com/questions/7105433/windows-batch-echo-without-new-line
  293. $p = $this->getProcess('echo | set /p dummyName=0');
  294. } else {
  295. $p = $this->getProcess('printf 0');
  296. }
  297. $p->run();
  298. $this->assertSame('0', $p->getOutput());
  299. }
  300. public function testExitCodeCommandFailed()
  301. {
  302. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  303. $this->markTestSkipped('Windows does not support POSIX exit code');
  304. }
  305. // such command run in bash return an exitcode 127
  306. $process = $this->getProcess('nonexistingcommandIhopeneversomeonewouldnameacommandlikethis');
  307. $process->run();
  308. $this->assertGreaterThan(0, $process->getExitCode());
  309. }
  310. public function testTTYCommand()
  311. {
  312. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  313. $this->markTestSkipped('Windows does have /dev/tty support');
  314. }
  315. $process = $this->getProcess('echo "foo" >> /dev/null && php -r "usleep(100000);"');
  316. $process->setTty(true);
  317. $process->start();
  318. $this->assertTrue($process->isRunning());
  319. $process->wait();
  320. $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
  321. }
  322. public function testTTYCommandExitCode()
  323. {
  324. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  325. $this->markTestSkipped('Windows does have /dev/tty support');
  326. }
  327. $process = $this->getProcess('echo "foo" >> /dev/null');
  328. $process->setTty(true);
  329. $process->run();
  330. $this->assertTrue($process->isSuccessful());
  331. }
  332. public function testTTYInWindowsEnvironment()
  333. {
  334. if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
  335. $this->markTestSkipped('This test is for Windows platform only');
  336. }
  337. $process = $this->getProcess('echo "foo" >> /dev/null');
  338. $process->setTty(false);
  339. $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'TTY mode is not supported on Windows platform.');
  340. $process->setTty(true);
  341. }
  342. public function testExitCodeTextIsNullWhenExitCodeIsNull()
  343. {
  344. $process = $this->getProcess('');
  345. $this->assertNull($process->getExitCodeText());
  346. }
  347. public function testPTYCommand()
  348. {
  349. if (!Process::isPtySupported()) {
  350. $this->markTestSkipped('PTY is not supported on this operating system.');
  351. }
  352. $process = $this->getProcess('echo "foo"');
  353. $process->setPty(true);
  354. $process->run();
  355. $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
  356. $this->assertEquals("foo\r\n", $process->getOutput());
  357. }
  358. public function testMustRun()
  359. {
  360. $process = $this->getProcess('echo foo');
  361. $this->assertSame($process, $process->mustRun());
  362. $this->assertEquals("foo".PHP_EOL, $process->getOutput());
  363. }
  364. public function testSuccessfulMustRunHasCorrectExitCode()
  365. {
  366. $process = $this->getProcess('echo foo')->mustRun();
  367. $this->assertEquals(0, $process->getExitCode());
  368. }
  369. /**
  370. * @expectedException \Symfony\Component\Process\Exception\ProcessFailedException
  371. */
  372. public function testMustRunThrowsException()
  373. {
  374. $process = $this->getProcess('exit 1');
  375. $process->mustRun();
  376. }
  377. public function testExitCodeText()
  378. {
  379. $process = $this->getProcess('');
  380. $r = new \ReflectionObject($process);
  381. $p = $r->getProperty('exitcode');
  382. $p->setAccessible(true);
  383. $p->setValue($process, 2);
  384. $this->assertEquals('Misuse of shell builtins', $process->getExitCodeText());
  385. }
  386. public function testStartIsNonBlocking()
  387. {
  388. $process = $this->getProcess('php -r "usleep(500000);"');
  389. $start = microtime(true);
  390. $process->start();
  391. $end = microtime(true);
  392. $this->assertLessThan(0.2, $end-$start);
  393. $process->wait();
  394. }
  395. public function testUpdateStatus()
  396. {
  397. $process = $this->getProcess('php -h');
  398. $process->run();
  399. $this->assertTrue(strlen($process->getOutput()) > 0);
  400. }
  401. public function testGetExitCodeIsNullOnStart()
  402. {
  403. $process = $this->getProcess('php -r "usleep(200000);"');
  404. $this->assertNull($process->getExitCode());
  405. $process->start();
  406. $this->assertNull($process->getExitCode());
  407. $process->wait();
  408. $this->assertEquals(0, $process->getExitCode());
  409. }
  410. public function testGetExitCodeIsNullOnWhenStartingAgain()
  411. {
  412. $process = $this->getProcess('php -r "usleep(200000);"');
  413. $process->run();
  414. $this->assertEquals(0, $process->getExitCode());
  415. $process->start();
  416. $this->assertNull($process->getExitCode());
  417. $process->wait();
  418. $this->assertEquals(0, $process->getExitCode());
  419. }
  420. public function testGetExitCode()
  421. {
  422. $process = $this->getProcess('php -m');
  423. $process->run();
  424. $this->assertSame(0, $process->getExitCode());
  425. }
  426. public function testStatus()
  427. {
  428. $process = $this->getProcess('php -r "usleep(500000);"');
  429. $this->assertFalse($process->isRunning());
  430. $this->assertFalse($process->isStarted());
  431. $this->assertFalse($process->isTerminated());
  432. $this->assertSame(Process::STATUS_READY, $process->getStatus());
  433. $process->start();
  434. $this->assertTrue($process->isRunning());
  435. $this->assertTrue($process->isStarted());
  436. $this->assertFalse($process->isTerminated());
  437. $this->assertSame(Process::STATUS_STARTED, $process->getStatus());
  438. $process->wait();
  439. $this->assertFalse($process->isRunning());
  440. $this->assertTrue($process->isStarted());
  441. $this->assertTrue($process->isTerminated());
  442. $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
  443. }
  444. public function testStop()
  445. {
  446. $process = $this->getProcess('php -r "sleep(4);"');
  447. $process->start();
  448. $this->assertTrue($process->isRunning());
  449. $process->stop();
  450. $this->assertFalse($process->isRunning());
  451. }
  452. public function testIsSuccessful()
  453. {
  454. $process = $this->getProcess('php -m');
  455. $process->run();
  456. $this->assertTrue($process->isSuccessful());
  457. }
  458. public function testIsSuccessfulOnlyAfterTerminated()
  459. {
  460. $process = $this->getProcess('php -r "sleep(1);"');
  461. $process->start();
  462. while ($process->isRunning()) {
  463. $this->assertFalse($process->isSuccessful());
  464. usleep(300000);
  465. }
  466. $this->assertTrue($process->isSuccessful());
  467. }
  468. public function testIsNotSuccessful()
  469. {
  470. $process = $this->getProcess('php -r "usleep(500000);throw new \Exception(\'BOUM\');"');
  471. $process->start();
  472. $this->assertTrue($process->isRunning());
  473. $process->wait();
  474. $this->assertFalse($process->isSuccessful());
  475. }
  476. public function testProcessIsNotSignaled()
  477. {
  478. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  479. $this->markTestSkipped('Windows does not support POSIX signals');
  480. }
  481. $process = $this->getProcess('php -m');
  482. $process->run();
  483. $this->assertFalse($process->hasBeenSignaled());
  484. }
  485. public function testProcessWithoutTermSignalIsNotSignaled()
  486. {
  487. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  488. $this->markTestSkipped('Windows does not support POSIX signals');
  489. }
  490. $process = $this->getProcess('php -m');
  491. $process->run();
  492. $this->assertFalse($process->hasBeenSignaled());
  493. }
  494. public function testProcessWithoutTermSignal()
  495. {
  496. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  497. $this->markTestSkipped('Windows does not support POSIX signals');
  498. }
  499. $process = $this->getProcess('php -m');
  500. $process->run();
  501. $this->assertEquals(0, $process->getTermSignal());
  502. }
  503. public function testProcessIsSignaledIfStopped()
  504. {
  505. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  506. $this->markTestSkipped('Windows does not support POSIX signals');
  507. }
  508. $process = $this->getProcess('php -r "sleep(4);"');
  509. $process->start();
  510. $process->stop();
  511. $this->assertTrue($process->hasBeenSignaled());
  512. }
  513. public function testProcessWithTermSignal()
  514. {
  515. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  516. $this->markTestSkipped('Windows does not support POSIX signals');
  517. }
  518. // SIGTERM is only defined if pcntl extension is present
  519. $termSignal = defined('SIGTERM') ? SIGTERM : 15;
  520. $process = $this->getProcess('php -r "sleep(4);"');
  521. $process->start();
  522. $process->stop();
  523. $this->assertEquals($termSignal, $process->getTermSignal());
  524. }
  525. public function testProcessThrowsExceptionWhenExternallySignaled()
  526. {
  527. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  528. $this->markTestSkipped('Windows does not support POSIX signals');
  529. }
  530. if (!function_exists('posix_kill')) {
  531. $this->markTestSkipped('posix_kill is required for this test');
  532. }
  533. $termSignal = defined('SIGKILL') ? SIGKILL : 9;
  534. $process = $this->getProcess('exec php -r "while (true) {}"');
  535. $process->start();
  536. posix_kill($process->getPid(), $termSignal);
  537. $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'The process has been signaled with signal "9".');
  538. $process->wait();
  539. }
  540. public function testRestart()
  541. {
  542. $process1 = $this->getProcess('php -r "echo getmypid();"');
  543. $process1->run();
  544. $process2 = $process1->restart();
  545. $process2->wait(); // wait for output
  546. // Ensure that both processed finished and the output is numeric
  547. $this->assertFalse($process1->isRunning());
  548. $this->assertFalse($process2->isRunning());
  549. $this->assertTrue(is_numeric($process1->getOutput()));
  550. $this->assertTrue(is_numeric($process2->getOutput()));
  551. // Ensure that restart returned a new process by check that the output is different
  552. $this->assertNotEquals($process1->getOutput(), $process2->getOutput());
  553. }
  554. public function testPhpDeadlock()
  555. {
  556. $this->markTestSkipped('Can cause PHP to hang');
  557. // Sleep doesn't work as it will allow the process to handle signals and close
  558. // file handles from the other end.
  559. $process = $this->getProcess('php -r "while (true) {}"');
  560. $process->start();
  561. // PHP will deadlock when it tries to cleanup $process
  562. }
  563. public function testRunProcessWithTimeout()
  564. {
  565. $timeout = 0.5;
  566. $process = $this->getProcess('php -r "usleep(600000);"');
  567. $process->setTimeout($timeout);
  568. $start = microtime(true);
  569. try {
  570. $process->run();
  571. $this->fail('A RuntimeException should have been raised');
  572. } catch (RuntimeException $e) {
  573. }
  574. $duration = microtime(true) - $start;
  575. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  576. // Windows is a bit slower as it read file handles, then allow twice the precision
  577. $maxDuration = $timeout + 2 * Process::TIMEOUT_PRECISION;
  578. } else {
  579. $maxDuration = $timeout + Process::TIMEOUT_PRECISION;
  580. }
  581. $this->assertLessThan($maxDuration, $duration);
  582. }
  583. public function testCheckTimeoutOnNonStartedProcess()
  584. {
  585. $process = $this->getProcess('php -r "sleep(3);"');
  586. $process->checkTimeout();
  587. }
  588. public function testCheckTimeoutOnTerminatedProcess()
  589. {
  590. $process = $this->getProcess('php -v');
  591. $process->run();
  592. $process->checkTimeout();
  593. }
  594. public function testCheckTimeoutOnStartedProcess()
  595. {
  596. $timeout = 0.5;
  597. $precision = 100000;
  598. $process = $this->getProcess('php -r "sleep(3);"');
  599. $process->setTimeout($timeout);
  600. $start = microtime(true);
  601. $process->start();
  602. try {
  603. while ($process->isRunning()) {
  604. $process->checkTimeout();
  605. usleep($precision);
  606. }
  607. $this->fail('A RuntimeException should have been raised');
  608. } catch (RuntimeException $e) {
  609. }
  610. $duration = microtime(true) - $start;
  611. $this->assertLessThan($timeout + $precision, $duration);
  612. $this->assertFalse($process->isSuccessful());
  613. }
  614. /**
  615. * @group idle-timeout
  616. */
  617. public function testIdleTimeout()
  618. {
  619. $process = $this->getProcess('php -r "sleep(3);"');
  620. $process->setTimeout(10);
  621. $process->setIdleTimeout(0.5);
  622. try {
  623. $process->run();
  624. $this->fail('A timeout exception was expected.');
  625. } catch (ProcessTimedOutException $ex) {
  626. $this->assertTrue($ex->isIdleTimeout());
  627. $this->assertFalse($ex->isGeneralTimeout());
  628. $this->assertEquals(0.5, $ex->getExceededTimeout());
  629. }
  630. }
  631. /**
  632. * @group idle-timeout
  633. */
  634. public function testIdleTimeoutNotExceededWhenOutputIsSent()
  635. {
  636. $process = $this->getProcess('php -r "echo \'foo\'; sleep(1); echo \'foo\'; sleep(1); echo \'foo\'; sleep(1); "');
  637. $process->setTimeout(2);
  638. $process->setIdleTimeout(1.5);
  639. try {
  640. $process->run();
  641. $this->fail('A timeout exception was expected.');
  642. } catch (ProcessTimedOutException $ex) {
  643. $this->assertTrue($ex->isGeneralTimeout());
  644. $this->assertFalse($ex->isIdleTimeout());
  645. $this->assertEquals(2, $ex->getExceededTimeout());
  646. }
  647. }
  648. public function testStartAfterATimeout()
  649. {
  650. $process = $this->getProcess('php -r "$n = 1000; while ($n--) {echo \'\'; usleep(1000); }"');
  651. $process->setTimeout(0.1);
  652. try {
  653. $process->run();
  654. $this->fail('An exception should have been raised.');
  655. } catch (\Exception $e) {
  656. }
  657. $process->start();
  658. usleep(1000);
  659. $process->stop();
  660. }
  661. public function testGetPid()
  662. {
  663. $process = $this->getProcess('php -r "usleep(500000);"');
  664. $process->start();
  665. $this->assertGreaterThan(0, $process->getPid());
  666. $process->wait();
  667. }
  668. public function testGetPidIsNullBeforeStart()
  669. {
  670. $process = $this->getProcess('php -r "sleep(1);"');
  671. $this->assertNull($process->getPid());
  672. }
  673. public function testGetPidIsNullAfterRun()
  674. {
  675. $process = $this->getProcess('php -m');
  676. $process->run();
  677. $this->assertNull($process->getPid());
  678. }
  679. public function testSignal()
  680. {
  681. $this->verifyPosixIsEnabled();
  682. $process = $this->getProcess('exec php -f '.__DIR__.'/SignalListener.php');
  683. $process->start();
  684. usleep(500000);
  685. $process->signal(SIGUSR1);
  686. while ($process->isRunning() && false === strpos($process->getOutput(), 'Caught SIGUSR1')) {
  687. usleep(10000);
  688. }
  689. $this->assertEquals('Caught SIGUSR1', $process->getOutput());
  690. }
  691. public function testExitCodeIsAvailableAfterSignal()
  692. {
  693. $this->verifyPosixIsEnabled();
  694. $process = $this->getProcess('sleep 4');
  695. $process->start();
  696. $process->signal(SIGKILL);
  697. while ($process->isRunning()) {
  698. usleep(10000);
  699. }
  700. $this->assertFalse($process->isRunning());
  701. $this->assertTrue($process->hasBeenSignaled());
  702. $this->assertFalse($process->isSuccessful());
  703. $this->assertEquals(137, $process->getExitCode());
  704. }
  705. /**
  706. * @expectedException \Symfony\Component\Process\Exception\LogicException
  707. */
  708. public function testSignalProcessNotRunning()
  709. {
  710. $this->verifyPosixIsEnabled();
  711. $process = $this->getProcess('php -m');
  712. $process->signal(SIGHUP);
  713. }
  714. /**
  715. * @dataProvider provideMethodsThatNeedARunningProcess
  716. */
  717. public function testMethodsThatNeedARunningProcess($method)
  718. {
  719. $process = $this->getProcess('php -m');
  720. $this->setExpectedException('Symfony\Component\Process\Exception\LogicException', sprintf('Process must be started before calling %s.', $method));
  721. call_user_func(array($process, $method));
  722. }
  723. public function provideMethodsThatNeedARunningProcess()
  724. {
  725. return array(
  726. array('getOutput'),
  727. array('getIncrementalOutput'),
  728. array('getErrorOutput'),
  729. array('getIncrementalErrorOutput'),
  730. array('wait'),
  731. );
  732. }
  733. /**
  734. * @dataProvider provideMethodsThatNeedATerminatedProcess
  735. */
  736. public function testMethodsThatNeedATerminatedProcess($method)
  737. {
  738. $process = $this->getProcess('php -r "sleep(1);"');
  739. $process->start();
  740. try {
  741. call_user_func(array($process, $method));
  742. $process->stop(0);
  743. $this->fail('A LogicException must have been thrown');
  744. } catch (\Exception $e) {
  745. $this->assertInstanceOf('Symfony\Component\Process\Exception\LogicException', $e);
  746. $this->assertEquals(sprintf('Process must be terminated before calling %s.', $method), $e->getMessage());
  747. }
  748. $process->stop(0);
  749. }
  750. public function provideMethodsThatNeedATerminatedProcess()
  751. {
  752. return array(
  753. array('hasBeenSignaled'),
  754. array('getTermSignal'),
  755. array('hasBeenStopped'),
  756. array('getStopSignal'),
  757. );
  758. }
  759. private function verifyPosixIsEnabled()
  760. {
  761. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  762. $this->markTestSkipped('POSIX signals do not work on Windows');
  763. }
  764. if (!defined('SIGUSR1')) {
  765. $this->markTestSkipped('The pcntl extension is not enabled');
  766. }
  767. }
  768. /**
  769. * @expectedException \Symfony\Component\Process\Exception\RuntimeException
  770. */
  771. public function testSignalWithWrongIntSignal()
  772. {
  773. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  774. $this->markTestSkipped('POSIX signals do not work on Windows');
  775. }
  776. $process = $this->getProcess('php -r "sleep(3);"');
  777. $process->start();
  778. $process->signal(-4);
  779. }
  780. /**
  781. * @expectedException \Symfony\Component\Process\Exception\RuntimeException
  782. */
  783. public function testSignalWithWrongNonIntSignal()
  784. {
  785. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  786. $this->markTestSkipped('POSIX signals do not work on Windows');
  787. }
  788. $process = $this->getProcess('php -r "sleep(3);"');
  789. $process->start();
  790. $process->signal('CĂ©phalopodes');
  791. }
  792. public function testDisableOutputDisablesTheOutput()
  793. {
  794. $p = $this->getProcess('php -r "usleep(500000);"');
  795. $this->assertFalse($p->isOutputDisabled());
  796. $p->disableOutput();
  797. $this->assertTrue($p->isOutputDisabled());
  798. $p->enableOutput();
  799. $this->assertFalse($p->isOutputDisabled());
  800. }
  801. public function testDisableOutputWhileRunningThrowsException()
  802. {
  803. $p = $this->getProcess('php -r "usleep(500000);"');
  804. $p->start();
  805. $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'Disabling output while the process is running is not possible.');
  806. $p->disableOutput();
  807. }
  808. public function testEnableOutputWhileRunningThrowsException()
  809. {
  810. $p = $this->getProcess('php -r "usleep(500000);"');
  811. $p->disableOutput();
  812. $p->start();
  813. $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'Enabling output while the process is running is not possible.');
  814. $p->enableOutput();
  815. }
  816. public function testEnableOrDisableOutputAfterRunDoesNotThrowException()
  817. {
  818. $p = $this->getProcess('php -r "usleep(500000);"');
  819. $p->disableOutput();
  820. $p->start();
  821. $p->wait();
  822. $p->enableOutput();
  823. $p->disableOutput();
  824. }
  825. public function testDisableOutputWhileIdleTimeoutIsSet()
  826. {
  827. $process = $this->getProcess('sleep 3');
  828. $process->setIdleTimeout(1);
  829. $this->setExpectedException('Symfony\Component\Process\Exception\LogicException', 'Output can not be disabled while an idle timeout is set.');
  830. $process->disableOutput();
  831. }
  832. public function testSetIdleTimeoutWhileOutputIsDisabled()
  833. {
  834. $process = $this->getProcess('sleep 3');
  835. $process->disableOutput();
  836. $this->setExpectedException('Symfony\Component\Process\Exception\LogicException', 'Idle timeout can not be set while the output is disabled.');
  837. $process->setIdleTimeout(1);
  838. }
  839. public function testSetNullIdleTimeoutWhileOutputIsDisabled()
  840. {
  841. $process = $this->getProcess('sleep 3');
  842. $process->disableOutput();
  843. $process->setIdleTimeout(null);
  844. }
  845. /**
  846. * @dataProvider provideStartMethods
  847. */
  848. public function testStartWithACallbackAndDisabledOutput($startMethod, $exception, $exceptionMessage)
  849. {
  850. $p = $this->getProcess('php -r "usleep(500000);"');
  851. $p->disableOutput();
  852. $this->setExpectedException($exception, $exceptionMessage);
  853. call_user_func(array($p, $startMethod), function () {});
  854. }
  855. public function provideStartMethods()
  856. {
  857. return array(
  858. array('start', 'Symfony\Component\Process\Exception\LogicException', 'Output has been disabled, enable it to allow the use of a callback.'),
  859. array('run', 'Symfony\Component\Process\Exception\LogicException', 'Output has been disabled, enable it to allow the use of a callback.'),
  860. array('mustRun', 'Symfony\Component\Process\Exception\LogicException', 'Output has been disabled, enable it to allow the use of a callback.'),
  861. );
  862. }
  863. /**
  864. * @dataProvider provideOutputFetchingMethods
  865. */
  866. public function testGetOutputWhileDisabled($fetchMethod)
  867. {
  868. $p = $this->getProcess('php -r "usleep(500000);"');
  869. $p->disableOutput();
  870. $p->start();
  871. $this->setExpectedException('Symfony\Component\Process\Exception\LogicException', 'Output has been disabled.');
  872. call_user_func(array($p, $fetchMethod));
  873. }
  874. public function provideOutputFetchingMethods()
  875. {
  876. return array(
  877. array('getOutput'),
  878. array('getIncrementalOutput'),
  879. array('getErrorOutput'),
  880. array('getIncrementalErrorOutput'),
  881. );
  882. }
  883. public function responsesCodeProvider()
  884. {
  885. return array(
  886. //expected output / getter / code to execute
  887. //array(1,'getExitCode','exit(1);'),
  888. //array(true,'isSuccessful','exit();'),
  889. array('output', 'getOutput', 'echo \'output\';'),
  890. );
  891. }
  892. public function pipesCodeProvider()
  893. {
  894. $variations = array(
  895. 'fwrite(STDOUT, $in = file_get_contents(\'php://stdin\')); fwrite(STDERR, $in);',
  896. 'include \''.__DIR__.'/PipeStdinInStdoutStdErrStreamSelect.php\';',
  897. );
  898. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  899. // Avoid XL buffers on Windows because of https://bugs.php.net/bug.php?id=65650
  900. $sizes = array(1, 2, 4, 8);
  901. } else {
  902. $sizes = array(1, 16, 64, 1024, 4096);
  903. }
  904. $codes = array();
  905. foreach ($sizes as $size) {
  906. foreach ($variations as $code) {
  907. $codes[] = array($code, $size);
  908. }
  909. }
  910. return $codes;
  911. }
  912. /**
  913. * provides default method names for simple getter/setter
  914. */
  915. public function methodProvider()
  916. {
  917. $defaults = array(
  918. array('CommandLine'),
  919. array('Timeout'),
  920. array('WorkingDirectory'),
  921. array('Env'),
  922. array('Stdin'),
  923. array('Input'),
  924. array('Options'),
  925. );
  926. return $defaults;
  927. }
  928. /**
  929. * @param string $commandline
  930. * @param null|string $cwd
  931. * @param null|array $env
  932. * @param null|string $input
  933. * @param int $timeout
  934. * @param array $options
  935. *
  936. * @return Process
  937. */
  938. abstract protected function getProcess($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = array());
  939. }
  940. class Stringifiable
  941. {
  942. public function __toString()
  943. {
  944. return 'stringifiable';
  945. }
  946. }
  947. class NonStringifiable
  948. {
  949. }