AbstractProcessTest.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  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\Process;
  12. use Symfony\Component\Process\Exception\RuntimeException;
  13. /**
  14. * @author Robert Schönthal <seroscho@googlemail.com>
  15. */
  16. abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
  17. {
  18. /**
  19. * @expectedException \InvalidArgumentException
  20. */
  21. public function testNegativeTimeoutFromConstructor()
  22. {
  23. $this->getProcess('', null, null, null, -1);
  24. }
  25. /**
  26. * @expectedException \InvalidArgumentException
  27. */
  28. public function testNegativeTimeoutFromSetter()
  29. {
  30. $p = $this->getProcess('');
  31. $p->setTimeout(-1);
  32. }
  33. public function testNullTimeout()
  34. {
  35. $p = $this->getProcess('');
  36. $p->setTimeout(10);
  37. $p->setTimeout(null);
  38. $this->assertNull($p->getTimeout());
  39. }
  40. public function testStopWithTimeoutIsActuallyWorking()
  41. {
  42. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  43. $this->markTestSkipped('Stop with timeout does not work on windows, it requires posix signals');
  44. }
  45. if (!function_exists('pcntl_signal')) {
  46. $this->markTestSkipped('This test require pcntl_signal function');
  47. }
  48. // exec is mandatory here since we send a signal to the process
  49. // see https://github.com/symfony/symfony/issues/5030 about prepending
  50. // command with exec
  51. $p = $this->getProcess('exec php '.__DIR__.'/NonStopableProcess.php 3');
  52. $p->start();
  53. usleep(100000);
  54. $start = microtime(true);
  55. $p->stop(1.1);
  56. while ($p->isRunning()) {
  57. usleep(1000);
  58. }
  59. $duration = microtime(true) - $start;
  60. $this->assertLessThan(1.3, $duration);
  61. }
  62. /**
  63. * tests results from sub processes
  64. *
  65. * @dataProvider responsesCodeProvider
  66. */
  67. public function testProcessResponses($expected, $getter, $code)
  68. {
  69. $p = $this->getProcess(sprintf('php -r %s', escapeshellarg($code)));
  70. $p->run();
  71. $this->assertSame($expected, $p->$getter());
  72. }
  73. /**
  74. * tests results from sub processes
  75. *
  76. * @dataProvider pipesCodeProvider
  77. */
  78. public function testProcessPipes($code, $size)
  79. {
  80. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  81. $this->markTestSkipped('Test hangs on Windows & PHP due to https://bugs.php.net/bug.php?id=60120 and https://bugs.php.net/bug.php?id=51800');
  82. }
  83. $expected = str_repeat(str_repeat('*', 1024), $size) . '!';
  84. $expectedLength = (1024 * $size) + 1;
  85. $p = $this->getProcess(sprintf('php -r %s', escapeshellarg($code)));
  86. $p->setStdin($expected);
  87. $p->run();
  88. $this->assertEquals($expectedLength, strlen($p->getOutput()));
  89. $this->assertEquals($expectedLength, strlen($p->getErrorOutput()));
  90. }
  91. public function chainedCommandsOutputProvider()
  92. {
  93. return array(
  94. array("1\n1\n", ';', '1'),
  95. array("2\n2\n", '&&', '2'),
  96. );
  97. }
  98. /**
  99. *
  100. * @dataProvider chainedCommandsOutputProvider
  101. */
  102. public function testChainedCommandsOutput($expected, $operator, $input)
  103. {
  104. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  105. $this->markTestSkipped('Does it work on windows ?');
  106. }
  107. $process = $this->getProcess(sprintf('echo %s %s echo %s', $input, $operator, $input));
  108. $process->run();
  109. $this->assertEquals($expected, $process->getOutput());
  110. }
  111. public function testCallbackIsExecutedForOutput()
  112. {
  113. $p = $this->getProcess(sprintf('php -r %s', escapeshellarg('echo \'foo\';')));
  114. $called = false;
  115. $p->run(function ($type, $buffer) use (&$called) {
  116. $called = $buffer === 'foo';
  117. });
  118. $this->assertTrue($called, 'The callback should be executed with the output');
  119. }
  120. public function testExitCodeCommandFailed()
  121. {
  122. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  123. $this->markTestSkipped('Windows does not support POSIX exit code');
  124. }
  125. // such command run in bash return an exitcode 127
  126. $process = $this->getProcess('nonexistingcommandIhopeneversomeonewouldnameacommandlikethis');
  127. $process->run();
  128. $this->assertGreaterThan(0, $process->getExitCode());
  129. }
  130. public function testExitCodeText()
  131. {
  132. $process = $this->getProcess('');
  133. $r = new \ReflectionObject($process);
  134. $p = $r->getProperty('exitcode');
  135. $p->setAccessible(true);
  136. $p->setValue($process, 2);
  137. $this->assertEquals('Misuse of shell builtins', $process->getExitCodeText());
  138. }
  139. public function testStartIsNonBlocking()
  140. {
  141. $process = $this->getProcess('php -r "sleep(4);"');
  142. $start = microtime(true);
  143. $process->start();
  144. $end = microtime(true);
  145. $this->assertLessThan(1 , $end-$start);
  146. }
  147. public function testUpdateStatus()
  148. {
  149. $process = $this->getProcess('php -h');
  150. $process->run();
  151. $this->assertTrue(strlen($process->getOutput()) > 0);
  152. }
  153. public function testGetExitCode()
  154. {
  155. $process = $this->getProcess('php -m');
  156. $process->run();
  157. $this->assertEquals(0, $process->getExitCode());
  158. }
  159. public function testIsRunning()
  160. {
  161. $process = $this->getProcess('php -r "sleep(1);"');
  162. $this->assertFalse($process->isRunning());
  163. $process->start();
  164. $this->assertTrue($process->isRunning());
  165. $process->wait();
  166. $this->assertFalse($process->isRunning());
  167. }
  168. public function testStop()
  169. {
  170. $process = $this->getProcess('php -r "while (true) {}"');
  171. $process->start();
  172. $this->assertTrue($process->isRunning());
  173. $process->stop();
  174. $this->assertFalse($process->isRunning());
  175. }
  176. public function testIsSuccessful()
  177. {
  178. $process = $this->getProcess('php -m');
  179. $process->run();
  180. $this->assertTrue($process->isSuccessful());
  181. }
  182. public function testIsNotSuccessful()
  183. {
  184. $process = $this->getProcess('php -r "while (true) {}"');
  185. $process->start();
  186. $this->assertTrue($process->isRunning());
  187. $process->stop();
  188. $this->assertFalse($process->isSuccessful());
  189. }
  190. public function testProcessIsNotSignaled()
  191. {
  192. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  193. $this->markTestSkipped('Windows does not support POSIX signals');
  194. }
  195. $process = $this->getProcess('php -m');
  196. $process->run();
  197. $this->assertFalse($process->hasBeenSignaled());
  198. }
  199. public function testProcessWithoutTermSignal()
  200. {
  201. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  202. $this->markTestSkipped('Windows does not support POSIX signals');
  203. }
  204. $process = $this->getProcess('php -m');
  205. $process->run();
  206. $this->assertEquals(0, $process->getTermSignal());
  207. }
  208. public function testProcessIsSignaledIfStopped()
  209. {
  210. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  211. $this->markTestSkipped('Windows does not support POSIX signals');
  212. }
  213. $process = $this->getProcess('php -r "while (true) {}"');
  214. $process->start();
  215. $process->stop();
  216. $this->assertTrue($process->hasBeenSignaled());
  217. }
  218. public function testProcessWithTermSignal()
  219. {
  220. if (defined('PHP_WINDOWS_VERSION_BUILD')) {
  221. $this->markTestSkipped('Windows does not support POSIX signals');
  222. }
  223. // SIGTERM is only defined if pcntl extension is present
  224. $termSignal = defined('SIGTERM') ? SIGTERM : 15;
  225. $process = $this->getProcess('php -r "while (true) {}"');
  226. $process->start();
  227. $process->stop();
  228. $this->assertEquals($termSignal, $process->getTermSignal());
  229. }
  230. public function testPhpDeadlock()
  231. {
  232. $this->markTestSkipped('Can course php to hang');
  233. // Sleep doesn't work as it will allow the process to handle signals and close
  234. // file handles from the other end.
  235. $process = $this->getProcess('php -r "while (true) {}"');
  236. $process->start();
  237. // PHP will deadlock when it tries to cleanup $process
  238. }
  239. public function testRunProcessWithTimeout()
  240. {
  241. $timeout = 0.5;
  242. $process = $this->getProcess('sleep 3');
  243. $process->setTimeout($timeout);
  244. $start = microtime(true);
  245. try {
  246. $process->run();
  247. $this->fail('A RuntimeException should have been raised');
  248. } catch (RuntimeException $e) {
  249. }
  250. $duration = microtime(true) - $start;
  251. $this->assertLessThan($timeout + Process::TIMEOUT_PRECISION, $duration);
  252. }
  253. public function testCheckTimeoutOnStartedProcess()
  254. {
  255. $timeout = 0.5;
  256. $precision = 100000;
  257. $process = $this->getProcess('sleep 3');
  258. $process->setTimeout($timeout);
  259. $start = microtime(true);
  260. $process->start();
  261. try {
  262. while ($process->isRunning()) {
  263. $process->checkTimeout();
  264. usleep($precision);
  265. }
  266. $this->fail('A RuntimeException should have been raised');
  267. } catch (RuntimeException $e) {
  268. }
  269. $duration = microtime(true) - $start;
  270. $this->assertLessThan($timeout + $precision, $duration);
  271. }
  272. public function responsesCodeProvider()
  273. {
  274. return array(
  275. //expected output / getter / code to execute
  276. //array(1,'getExitCode','exit(1);'),
  277. //array(true,'isSuccessful','exit();'),
  278. array('output', 'getOutput', 'echo \'output\';'),
  279. );
  280. }
  281. public function pipesCodeProvider()
  282. {
  283. $variations = array(
  284. 'fwrite(STDOUT, $in = file_get_contents(\'php://stdin\')); fwrite(STDERR, $in);',
  285. 'include \'' . __DIR__ . '/ProcessTestHelper.php\';',
  286. );
  287. $codes = array();
  288. foreach (array(1, 16, 64, 1024, 4096) as $size) {
  289. foreach ($variations as $code) {
  290. $codes[] = array($code, $size);
  291. }
  292. }
  293. return $codes;
  294. }
  295. /**
  296. * provides default method names for simple getter/setter
  297. */
  298. public function methodProvider()
  299. {
  300. $defaults = array(
  301. array('CommandLine'),
  302. array('Timeout'),
  303. array('WorkingDirectory'),
  304. array('Env'),
  305. array('Stdin'),
  306. array('Options')
  307. );
  308. return $defaults;
  309. }
  310. /**
  311. * @param string $commandline
  312. * @param null $cwd
  313. * @param array $env
  314. * @param null $stdin
  315. * @param integer $timeout
  316. * @param array $options
  317. *
  318. * @return Process
  319. */
  320. abstract protected function getProcess($commandline, $cwd = null, array $env = null, $stdin = null, $timeout = 60, array $options = array());
  321. }