From e7cc4aa715175b37c3fb215580f3f392eef97f11 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 8 Dec 2015 17:23:26 +0100 Subject: [PATCH] [Process] Enhance compatiblity with --enable-sigchild --- .travis.yml | 3 + src/Symfony/Component/Process/Process.php | 118 ++++----- .../Process/Tests/PhpProcessTest.php | 14 +- .../Tests/ProcessInSigchildEnvironment.php | 22 -- ...bstractProcessTest.php => ProcessTest.php} | 140 ++++++++--- .../Tests/SigchildDisabledProcessTest.php | 225 ------------------ .../Tests/SigchildEnabledProcessTest.php | 152 ------------ .../Process/Tests/SimpleProcessTest.php | 216 ----------------- 8 files changed, 184 insertions(+), 706 deletions(-) delete mode 100644 src/Symfony/Component/Process/Tests/ProcessInSigchildEnvironment.php rename src/Symfony/Component/Process/Tests/{AbstractProcessTest.php => ProcessTest.php} (87%) delete mode 100644 src/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php delete mode 100644 src/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php delete mode 100644 src/Symfony/Component/Process/Tests/SimpleProcessTest.php diff --git a/.travis.yml b/.travis.yml index c2933ed76e..00f8bf15ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ addons: cache: directories: - .phpunit + - php-5.3.9 matrix: include: @@ -32,6 +33,7 @@ env: before_install: - if [[ "$deps" = "no" ]] && [[ "$TRAVIS_PHP_VERSION" =~ 5.[45] ]] && [[ "$TRAVIS_PULL_REQUEST" != "false" ]]; then export deps=skip; fi; + - if [[ $deps = no && $TRAVIS_PHP_VERSION = 5.3 && ! -d php-5.3.9/sapi ]]; then wget http://museum.php.net/php5/php-5.3.9.tar.bz2; tar -xjf php-5.3.9.tar.bz2; (cd php-5.3.9; ./configure --enable-sigchild --enable-pcntl; make -j2); fi; - if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then INI_FILE=~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; else INI_FILE=/etc/hhvm/php.ini; fi; - echo "memory_limit = -1" >> $INI_FILE - echo "session.gc_probability = 0" >> $INI_FILE @@ -54,6 +56,7 @@ install: script: - if [ "$deps" = "no" ]; then echo "$COMPONENTS" | parallel --gnu '$PHPUNIT --exclude-group tty,benchmark,intl-data {}'; fi; - if [ "$deps" = "no" ]; then echo -e "\\nRunning tests requiring tty"; $PHPUNIT --group tty; fi; + - if [[ $deps = no && $TRAVIS_PHP_VERSION = 5.3 ]]; then echo -e "1\\n0" | parallel --gnu 'echo -e "\\nPHP --enable-sigchild enhanced={}" && ENHANCE_SIGCHLD={} php-5.3.9/sapi/cli/php .phpunit/phpunit-4.8/phpunit --colors=always src/Symfony/Component/Process/'; fi; - if [ "$deps" = "high" ]; then echo "$COMPONENTS" | parallel --gnu -j10% 'cd {}; composer --prefer-source update; $PHPUNIT --exclude-group tty,benchmark,intl-data'; fi; - if [ "$deps" = "low" ]; then echo "$COMPONENTS" | parallel --gnu -j10% 'cd {}; composer --prefer-source --prefer-lowest --prefer-stable update; $PHPUNIT --exclude-group tty,benchmark,intl-data'; fi; - if [ "$deps" = "skip" ]; then echo 'This matrix line is skipped for pull requests.'; fi; diff --git a/src/Symfony/Component/Process/Process.php b/src/Symfony/Component/Process/Process.php index 2b7da31d2f..d2af621d43 100644 --- a/src/Symfony/Component/Process/Process.php +++ b/src/Symfony/Component/Process/Process.php @@ -46,7 +46,7 @@ class Process private $timeout; private $options; private $exitcode; - private $fallbackExitcode; + private $fallbackStatus = array(); private $processInformation; private $stdout; private $stderr; @@ -65,6 +65,14 @@ class Process private $latestSignal; private static $sigchild; + private static $posixSignals = array( + 1 => 1, // SIGHUP + 2 => 2, // SIGINT + 3 => 3, // SIGQUIT + 6 => 6, // SIGABRT + 14 => 14, // SIGALRM + 15 => 15, // SIGTERM + ); /** * Exit codes translation table. @@ -339,17 +347,9 @@ class Process * Returns the Pid (process identifier), if applicable. * * @return int|null The process id if running, null otherwise - * - * @throws RuntimeException In case --enable-sigchild is activated */ public function getPid() { - if ($this->isSigchildEnabled()) { - throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.'); - } - - $this->updateStatus(false); - return $this->isRunning() ? $this->processInformation['pid'] : null; } @@ -361,7 +361,7 @@ class Process * @return Process * * @throws LogicException In case the process is not running - * @throws RuntimeException In case --enable-sigchild is activated + * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed * @throws RuntimeException In case of failure */ public function signal($signal) @@ -467,7 +467,9 @@ class Process */ public function getExitCode() { - if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) { + if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + $this->stop(0); + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); } @@ -484,8 +486,6 @@ class Process * * @return null|string A string representation for the exit status code, null if the Process is not terminated. * - * @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled - * * @see http://tldp.org/LDP/abs/html/exitcodes.html * @see http://en.wikipedia.org/wiki/Unix_signal */ @@ -522,12 +522,12 @@ class Process { $this->requireProcessIsTerminated(__FUNCTION__); - if ($this->isSigchildEnabled()) { + if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + $this->stop(0); + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); } - $this->updateStatus(false); - return $this->processInformation['signaled']; } @@ -545,12 +545,12 @@ class Process { $this->requireProcessIsTerminated(__FUNCTION__); - if ($this->isSigchildEnabled()) { + if ($this->isSigchildEnabled() && (!$this->enhanceSigchildCompatibility || -1 === $this->processInformation['termsig'])) { + $this->stop(0); + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); } - $this->updateStatus(false); - return $this->processInformation['termsig']; } @@ -567,8 +567,6 @@ class Process { $this->requireProcessIsTerminated(__FUNCTION__); - $this->updateStatus(false); - return $this->processInformation['stopped']; } @@ -585,8 +583,6 @@ class Process { $this->requireProcessIsTerminated(__FUNCTION__); - $this->updateStatus(false); - return $this->processInformation['stopsig']; } @@ -660,7 +656,7 @@ class Process usleep(1000); } while ($this->isRunning() && microtime(true) < $timeoutMicro); - if ($this->isRunning() && !$this->isSigchildEnabled()) { + if ($this->isRunning()) { // Avoid exception here: process is supposed to be running, but it might have stopped just // after this line. In any case, let's silently discard the error, we cannot do anything. $this->doSignal($signal ?: 9, false); @@ -998,9 +994,15 @@ class Process if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { // last exit code is output on the fourth pipe and caught to work around --enable-sigchild - $descriptors = array_merge($descriptors, array(array('pipe', 'w'))); + $descriptors[3] = array('pipe', 'w'); - $this->commandline = '('.$this->commandline.') 3>/dev/null; code=$?; echo $code >&3; exit $code'; + $trap = ''; + foreach (self::$posixSignals as $s) { + $trap .= "trap 'echo s$s >&3' $s;"; + } + + $this->commandline = $trap.'{ ('.$this->commandline.') <&3 3<&- 3>/dev/null & } 3<&0;'; + $this->commandline .= 'pid=$!; echo p$pid >&3; wait $pid; code=$?; echo x$code >&3; exit $code'; } return $descriptors; @@ -1047,10 +1049,13 @@ class Process } $this->processInformation = proc_get_status($this->process); - $this->captureExitCode(); $this->readPipes($blocking, '\\' === DIRECTORY_SEPARATOR ? !$this->processInformation['running'] : true); + if ($this->fallbackStatus && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + $this->processInformation = $this->fallbackStatus + $this->processInformation; + } + if (!$this->processInformation['running']) { $this->close(); } @@ -1067,7 +1072,7 @@ class Process return self::$sigchild; } - if (!function_exists('phpinfo')) { + if (!function_exists('phpinfo') || defined('HHVM_VERSION')) { return self::$sigchild = false; } @@ -1093,24 +1098,24 @@ class Process $callback = $this->callback; foreach ($result as $type => $data) { - if (3 == $type) { - $this->fallbackExitcode = (int) $data; + if (3 === $type) { + foreach (explode("\n", substr($data, 0, -1)) as $data) { + if ('p' === $data[0]) { + $this->fallbackStatus['pid'] = (int) substr($data, 1); + } elseif ('s' === $data[0]) { + $this->fallbackStatus['signaled'] = true; + $this->fallbackStatus['exitcode'] = -1; + $this->fallbackStatus['termsig'] = (int) substr($data, 1); + } elseif ('x' === $data[0] && !isset($this->fallbackStatus['signaled'])) { + $this->fallbackStatus['exitcode'] = (int) substr($data, 1); + } + } } else { $callback($type === self::STDOUT ? self::OUT : self::ERR, $data); } } } - /** - * Captures the exitcode if mentioned in the process information. - */ - private function captureExitCode() - { - if (isset($this->processInformation['exitcode']) && -1 != $this->processInformation['exitcode']) { - $this->exitcode = $this->processInformation['exitcode']; - } - } - /** * Closes process resource, closes file handles, sets the exitcode. * @@ -1120,19 +1125,19 @@ class Process { $this->processPipes->close(); if (is_resource($this->process)) { - $exitcode = proc_close($this->process); - } else { - $exitcode = -1; + proc_close($this->process); } - - $this->exitcode = -1 !== $exitcode ? $exitcode : (null !== $this->exitcode ? $this->exitcode : -1); + $this->exitcode = $this->processInformation['exitcode']; $this->status = self::STATUS_TERMINATED; - if (-1 === $this->exitcode && null !== $this->fallbackExitcode) { - $this->exitcode = $this->fallbackExitcode; - } elseif (-1 === $this->exitcode && $this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) { - // if process has been signaled, no exitcode but a valid termsig, apply Unix convention - $this->exitcode = 128 + $this->processInformation['termsig']; + if (-1 === $this->exitcode) { + if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) { + // if process has been signaled, no exitcode but a valid termsig, apply Unix convention + $this->exitcode = 128 + $this->processInformation['termsig']; + } elseif ($this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + $this->processInformation['signaled'] = true; + $this->processInformation['termsig'] = -1; + } } // Free memory from self-reference callback created by buildCallback @@ -1151,7 +1156,7 @@ class Process $this->starttime = null; $this->callback = null; $this->exitcode = null; - $this->fallbackExitcode = null; + $this->fallbackStatus = array(); $this->processInformation = null; $this->stdout = null; $this->stderr = null; @@ -1171,7 +1176,7 @@ class Process * @return bool True if the signal was sent successfully, false otherwise * * @throws LogicException In case the process is not running - * @throws RuntimeException In case --enable-sigchild is activated + * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed * @throws RuntimeException In case of failure */ private function doSignal($signal, $throwException) @@ -1184,9 +1189,9 @@ class Process return false; } - if ($this->isSigchildEnabled()) { + if ($this->enhanceSigchildCompatibility && $this->isSigchildEnabled() && !isset(self::$posixSignals[$signal]) && !(function_exists('posix_kill') && @posix_kill($this->getPid(), $signal))) { if ($throwException) { - throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); + throw new RuntimeException('This PHP has been compiled with --enable-sigchild and posix_kill() is not available.'); } return false; @@ -1211,7 +1216,10 @@ class Process return false; } - $this->latestSignal = $signal; + $this->latestSignal = (int) $signal; + $this->fallbackStatus['signaled'] = true; + $this->fallbackStatus['exitcode'] = -1; + $this->fallbackStatus['termsig'] = $this->latestSignal; return true; } diff --git a/src/Symfony/Component/Process/Tests/PhpProcessTest.php b/src/Symfony/Component/Process/Tests/PhpProcessTest.php index 2cf79aa1a6..9c0f04d3d2 100644 --- a/src/Symfony/Component/Process/Tests/PhpProcessTest.php +++ b/src/Symfony/Component/Process/Tests/PhpProcessTest.php @@ -30,24 +30,20 @@ PHP public function testCommandLine() { - if ('phpdbg' === PHP_SAPI) { - $this->markTestSkipped('phpdbg SAPI is not supported by this test.'); - } - $process = new PhpProcess(<<find(); + $commandLine = $process->getCommandLine(); - $this->assertSame($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP before start'); + $f = new PhpExecutableFinder(); + $this->assertContains($f->find(), $commandLine, '::getCommandLine() returns the command line of PHP before start'); $process->start(); - $this->assertSame($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after start'); + $this->assertContains($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after start'); $process->wait(); - $this->assertSame($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after wait'); + $this->assertContains($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after wait'); } } diff --git a/src/Symfony/Component/Process/Tests/ProcessInSigchildEnvironment.php b/src/Symfony/Component/Process/Tests/ProcessInSigchildEnvironment.php deleted file mode 100644 index 3977bcdcf1..0000000000 --- a/src/Symfony/Component/Process/Tests/ProcessInSigchildEnvironment.php +++ /dev/null @@ -1,22 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Process\Tests; - -use Symfony\Component\Process\Process; - -class ProcessInSigchildEnvironment extends Process -{ - protected function isSigchildEnabled() - { - return true; - } -} diff --git a/src/Symfony/Component/Process/Tests/AbstractProcessTest.php b/src/Symfony/Component/Process/Tests/ProcessTest.php similarity index 87% rename from src/Symfony/Component/Process/Tests/AbstractProcessTest.php rename to src/Symfony/Component/Process/Tests/ProcessTest.php index ef1c28eef0..a49ededd7d 100644 --- a/src/Symfony/Component/Process/Tests/AbstractProcessTest.php +++ b/src/Symfony/Component/Process/Tests/ProcessTest.php @@ -20,9 +20,10 @@ use Symfony\Component\Process\ProcessPipes; /** * @author Robert Schönthal */ -abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase +class ProcessTest extends \PHPUnit_Framework_TestCase { - protected static $phpBin; + private static $phpBin; + private static $notEnhancedSigchild = false; public static function setUpBeforeClass() { @@ -71,12 +72,11 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase $this->assertNull($p->getTimeout()); } + /** + * @requires extension pcntl + */ public function testStopWithTimeoutIsActuallyWorking() { - if (!extension_loaded('pcntl')) { - $this->markTestSkipped('Extension pcntl is required.'); - } - // exec is mandatory here since we send a signal to the process // see https://github.com/symfony/symfony/issues/5030 about prepending // command with exec @@ -124,7 +124,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase { $data = ''; - $process = $this->getProcess('echo foo && php -r "sleep(1);" && echo foo'); + $process = $this->getProcess('echo foo && '.self::$phpBin.' -r "sleep(1);" && echo foo'); $process->start(function ($type, $buffer) use (&$data) { $data .= $buffer; }); @@ -401,6 +401,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase if ('\\' === DIRECTORY_SEPARATOR) { $this->markTestSkipped('Windows does not support POSIX exit code'); } + $this->skipIfNotEnhancedSigchild(); // such command run in bash return an exitcode 127 $process = $this->getProcess('nonexistingcommandIhopeneversomeonewouldnameacommandlikethis'); @@ -429,6 +430,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase if ('\\' === DIRECTORY_SEPARATOR) { $this->markTestSkipped('Windows does have /dev/tty support'); } + $this->skipIfNotEnhancedSigchild(); $process = $this->getProcess('echo "foo" >> /dev/null'); $process->setTty(true); @@ -437,6 +439,10 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase $this->assertTrue($process->isSuccessful()); } + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage TTY mode is not supported on Windows platform. + */ public function testTTYInWindowsEnvironment() { if ('\\' !== DIRECTORY_SEPARATOR) { @@ -445,18 +451,21 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase $process = $this->getProcess('echo "foo" >> /dev/null'); $process->setTty(false); - $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'TTY mode is not supported on Windows platform.'); $process->setTty(true); } public function testExitCodeTextIsNullWhenExitCodeIsNull() { + $this->skipIfNotEnhancedSigchild(); + $process = $this->getProcess(''); $this->assertNull($process->getExitCodeText()); } public function testExitCodeText() { + $this->skipIfNotEnhancedSigchild(); + $process = $this->getProcess(''); $r = new \ReflectionObject($process); $p = $r->getProperty('exitcode'); @@ -485,6 +494,8 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase public function testGetExitCodeIsNullOnStart() { + $this->skipIfNotEnhancedSigchild(); + $process = $this->getProcess(self::$phpBin.' -r "usleep(200000);"'); $this->assertNull($process->getExitCode()); $process->start(); @@ -495,6 +506,8 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase public function testGetExitCodeIsNullOnWhenStartingAgain() { + $this->skipIfNotEnhancedSigchild(); + $process = $this->getProcess(self::$phpBin.' -r "usleep(200000);"'); $process->run(); $this->assertEquals(0, $process->getExitCode()); @@ -506,6 +519,8 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase public function testGetExitCode() { + $this->skipIfNotEnhancedSigchild(); + $process = $this->getProcess(self::$phpBin.' -v'); $process->run(); $this->assertSame(0, $process->getExitCode()); @@ -541,6 +556,8 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase public function testIsSuccessful() { + $this->skipIfNotEnhancedSigchild(); + $process = $this->getProcess(self::$phpBin.' -v'); $process->run(); $this->assertTrue($process->isSuccessful()); @@ -548,6 +565,8 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase public function testIsSuccessfulOnlyAfterTerminated() { + $this->skipIfNotEnhancedSigchild(); + $process = $this->getProcess(self::$phpBin.' -r "sleep(1);"'); $process->start(); @@ -562,6 +581,8 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase public function testIsNotSuccessful() { + $this->skipIfNotEnhancedSigchild(); + $process = $this->getProcess(self::$phpBin.' -r "usleep(500000);throw new \Exception(\'BOUM\');"'); $process->start(); $this->assertTrue($process->isRunning()); @@ -574,6 +595,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase if ('\\' === DIRECTORY_SEPARATOR) { $this->markTestSkipped('Windows does not support POSIX signals'); } + $this->skipIfNotEnhancedSigchild(); $process = $this->getProcess(self::$phpBin.' -v'); $process->run(); @@ -585,6 +607,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase if ('\\' === DIRECTORY_SEPARATOR) { $this->markTestSkipped('Windows does not support POSIX signals'); } + $this->skipIfNotEnhancedSigchild(); $process = $this->getProcess(self::$phpBin.' -v'); $process->run(); @@ -596,6 +619,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase if ('\\' === DIRECTORY_SEPARATOR) { $this->markTestSkipped('Windows does not support POSIX signals'); } + $this->skipIfNotEnhancedSigchild(); $process = $this->getProcess(self::$phpBin.' -v'); $process->run(); @@ -607,6 +631,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase if ('\\' === DIRECTORY_SEPARATOR) { $this->markTestSkipped('Windows does not support POSIX signals'); } + $this->skipIfNotEnhancedSigchild(); $process = $this->getProcess(self::$phpBin.' -r "sleep(4);"'); $process->start(); @@ -619,22 +644,25 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase if ('\\' === DIRECTORY_SEPARATOR) { $this->markTestSkipped('Windows does not support POSIX signals'); } - - // SIGTERM is only defined if pcntl extension is present - $termSignal = defined('SIGTERM') ? SIGTERM : 15; + $this->skipIfNotEnhancedSigchild(); $process = $this->getProcess(self::$phpBin.' -r "sleep(4);"'); $process->start(); $process->stop(); - $this->assertEquals($termSignal, $process->getTermSignal()); + $this->assertEquals(15, $process->getTermSignal()); // SIGTERM } + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage The process has been signaled + */ public function testProcessThrowsExceptionWhenExternallySignaled() { if (!function_exists('posix_kill')) { $this->markTestSkipped('Function posix_kill is required.'); } + $this->skipIfNotEnhancedSigchild(false); $termSignal = defined('SIGKILL') ? SIGKILL : 9; @@ -642,7 +670,6 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase $process->start(); posix_kill($process->getPid(), $termSignal); - $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'The process has been signaled with signal "9".'); $process->wait(); } @@ -725,7 +752,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase } $duration = microtime(true) - $start; - $this->assertLessThan(1, $duration); + $this->assertLessThan(3, $duration); throw $e; } @@ -771,12 +798,11 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase $this->assertNull($process->getPid()); } + /** + * @requires extension pcntl + */ public function testSignal() { - if (!extension_loaded('pcntl')) { - $this->markTestSkipped('Extension pcntl is required.'); - } - $process = $this->getProcess('exec '.self::$phpBin.' '.__DIR__.'/SignalListener.php'); $process->start(); @@ -789,11 +815,12 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase $this->assertEquals('Caught SIGUSR1', $process->getOutput()); } + /** + * @requires extension pcntl + */ public function testExitCodeIsAvailableAfterSignal() { - if (!extension_loaded('pcntl')) { - $this->markTestSkipped('Extension pcntl is required.'); - } + $this->skipIfNotEnhancedSigchild(); $process = $this->getProcess('sleep 4'); $process->start(); @@ -811,15 +838,12 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase /** * @expectedException \Symfony\Component\Process\Exception\LogicException + * @expectedExceptionMessage Can not send signal on a non running process. */ public function testSignalProcessNotRunning() { - if (!extension_loaded('pcntl')) { - $this->markTestSkipped('Extension pcntl is required.'); - } - $process = $this->getProcess(self::$phpBin.' -v'); - $process->signal(SIGHUP); + $process->signal(1); // SIGHUP } /** @@ -901,6 +925,37 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase $process->signal('Céphalopodes'); } + public function testStopTerminatesProcessCleanly() + { + $process = $this->getProcess(self::$phpBin.' -r "echo \'foo\'; sleep(1); echo \'bar\';"'); + $process->run(function () use ($process) { + $process->stop(); + }); + $this->assertTrue(true, 'A call to stop() is not expected to cause wait() to throw a RuntimeException'); + } + + public function testKillSignalTerminatesProcessCleanly() + { + $process = $this->getProcess(self::$phpBin.' -r "echo \'foo\'; sleep(1); echo \'bar\';"'); + $process->run(function () use ($process) { + if ($process->isRunning()) { + $process->signal(9); // SIGKILL + } + }); + $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException'); + } + + public function testTermSignalTerminatesProcessCleanly() + { + $process = $this->getProcess(self::$phpBin.' -r "echo \'foo\'; sleep(1); echo \'bar\';"'); + $process->run(function () use ($process) { + if ($process->isRunning()) { + $process->signal(15); // SIGTERM + } + }); + $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException'); + } + public function responsesCodeProvider() { return array( @@ -962,7 +1017,38 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase * * @return Process */ - abstract protected function getProcess($commandline, $cwd = null, array $env = null, $stdin = null, $timeout = 60, array $options = array()); + private function getProcess($commandline, $cwd = null, array $env = null, $stdin = null, $timeout = 60, array $options = array()) + { + $process = new Process($commandline, $cwd, $env, $stdin, $timeout, $options); + + if (false !== $enhance = getenv('ENHANCE_SIGCHLD')) { + try { + $process->setEnhanceSigchildCompatibility(false); + $process->getExitCode(); + $this->fail('ENHANCE_SIGCHLD must be used together with a sigchild-enabled PHP.'); + } catch (RuntimeException $e) { + $this->assertSame('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.', $e->getMessage()); + if ($enhance) { + $process->setEnhanceSigChildCompatibility(true); + } else { + self::$notEnhancedSigchild = true; + } + } + } + + return $process; + } + + private function skipIfNotEnhancedSigchild($expectException = true) + { + if (self::$notEnhancedSigchild) { + if ($expectException) { + $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild.'); + } else { + $this->markTestSkipped('PHP is compiled with --enable-sigchild and enhanced mode is disabled.'); + } + } + } } class Stringifiable diff --git a/src/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php b/src/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php deleted file mode 100644 index 7e41b83529..0000000000 --- a/src/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php +++ /dev/null @@ -1,225 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Process\Tests; - -class SigchildDisabledProcessTest extends AbstractProcessTest -{ - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. - */ - public function testGetExitCode() - { - parent::testGetExitCode(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. - */ - public function testGetExitCodeIsNullOnStart() - { - parent::testGetExitCodeIsNullOnStart(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. - */ - public function testGetExitCodeIsNullOnWhenStartingAgain() - { - parent::testGetExitCodeIsNullOnWhenStartingAgain(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. - */ - public function testExitCodeCommandFailed() - { - parent::testExitCodeCommandFailed(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved. - */ - public function testProcessIsSignaledIfStopped() - { - parent::testProcessIsSignaledIfStopped(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved. - */ - public function testProcessWithTermSignal() - { - parent::testProcessWithTermSignal(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved. - */ - public function testProcessIsNotSignaled() - { - parent::testProcessIsNotSignaled(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved. - */ - public function testProcessWithoutTermSignal() - { - parent::testProcessWithoutTermSignal(); - } - - public function testCheckTimeoutOnStartedProcess() - { - $this->markTestSkipped('Stopping with signal is not supported in sigchild environment'); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved. - */ - public function testGetPid() - { - parent::testGetPid(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved. - */ - public function testGetPidIsNullBeforeStart() - { - parent::testGetPidIsNullBeforeStart(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved. - */ - public function testGetPidIsNullAfterRun() - { - parent::testGetPidIsNullAfterRun(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. - */ - public function testExitCodeText() - { - $process = $this->getProcess('qdfsmfkqsdfmqmsd'); - $process->run(); - - $process->getExitCodeText(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. - */ - public function testExitCodeTextIsNullWhenExitCodeIsNull() - { - parent::testExitCodeTextIsNullWhenExitCodeIsNull(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. - */ - public function testIsSuccessful() - { - parent::testIsSuccessful(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. - */ - public function testIsSuccessfulOnlyAfterTerminated() - { - parent::testIsSuccessfulOnlyAfterTerminated(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. - */ - public function testIsNotSuccessful() - { - parent::testIsNotSuccessful(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method. - */ - public function testTTYCommandExitCode() - { - parent::testTTYCommandExitCode(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process can not be signaled. - */ - public function testSignal() - { - parent::testSignal(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved. - */ - public function testProcessWithoutTermSignalIsNotSignaled() - { - parent::testProcessWithoutTermSignalIsNotSignaled(); - } - - public function testStopWithTimeoutIsActuallyWorking() - { - $this->markTestSkipped('Stopping with signal is not supported in sigchild environment'); - } - - public function testProcessThrowsExceptionWhenExternallySignaled() - { - $this->markTestSkipped('Retrieving Pid is not supported in sigchild environment'); - } - - public function testExitCodeIsAvailableAfterSignal() - { - $this->markTestSkipped('Signal is not supported in sigchild environment'); - } - - public function testRunProcessWithTimeout() - { - $this->markTestSkipped('Signal (required for timeout) is not supported in sigchild environment'); - } - - /** - * {@inheritdoc} - */ - protected function getProcess($commandline, $cwd = null, array $env = null, $stdin = null, $timeout = 60, array $options = array()) - { - $process = new ProcessInSigchildEnvironment($commandline, $cwd, $env, $stdin, $timeout, $options); - $process->setEnhanceSigchildCompatibility(false); - - return $process; - } -} diff --git a/src/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php b/src/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php deleted file mode 100644 index 300560e9b5..0000000000 --- a/src/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php +++ /dev/null @@ -1,152 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Process\Tests; - -class SigchildEnabledProcessTest extends AbstractProcessTest -{ - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved. - */ - public function testProcessIsSignaledIfStopped() - { - parent::testProcessIsSignaledIfStopped(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved. - */ - public function testProcessWithTermSignal() - { - parent::testProcessWithTermSignal(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved. - */ - public function testProcessIsNotSignaled() - { - parent::testProcessIsNotSignaled(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved. - */ - public function testProcessWithoutTermSignal() - { - parent::testProcessWithoutTermSignal(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved. - */ - public function testGetPid() - { - parent::testGetPid(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved. - */ - public function testGetPidIsNullBeforeStart() - { - parent::testGetPidIsNullBeforeStart(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved. - */ - public function testGetPidIsNullAfterRun() - { - parent::testGetPidIsNullAfterRun(); - } - - public function testExitCodeText() - { - $process = $this->getProcess('qdfsmfkqsdfmqmsd'); - $process->run(); - - $this->assertInternalType('string', $process->getExitCodeText()); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. The process can not be signaled. - */ - public function testSignal() - { - parent::testSignal(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved. - */ - public function testProcessWithoutTermSignalIsNotSignaled() - { - parent::testProcessWithoutTermSignalIsNotSignaled(); - } - - public function testProcessThrowsExceptionWhenExternallySignaled() - { - $this->markTestSkipped('Retrieving Pid is not supported in sigchild environment'); - } - - public function testExitCodeIsAvailableAfterSignal() - { - $this->markTestSkipped('Signal is not supported in sigchild environment'); - } - - /** - * @expectedException Symfony\Component\Process\Exception\RuntimeException - * @expectedExceptionMessage The process timed-out. - */ - public function testStartAfterATimeout() - { - if ('\\' === DIRECTORY_SEPARATOR) { - $this->markTestSkipped('Restarting a timed-out process on Windows is not supported in sigchild environment'); - } - parent::testStartAfterATimeout(); - } - - public function testStopWithTimeoutIsActuallyWorking() - { - $this->markTestSkipped('Stopping with signal is not supported in sigchild environment'); - } - - public function testRunProcessWithTimeout() - { - $this->markTestSkipped('Signal (required for timeout) is not supported in sigchild environment'); - } - - public function testCheckTimeoutOnStartedProcess() - { - $this->markTestSkipped('Signal (required for timeout) is not supported in sigchild environment'); - } - - /** - * {@inheritdoc} - */ - protected function getProcess($commandline, $cwd = null, array $env = null, $stdin = null, $timeout = 60, array $options = array()) - { - $process = new ProcessInSigchildEnvironment($commandline, $cwd, $env, $stdin, $timeout, $options); - $process->setEnhanceSigchildCompatibility(true); - - return $process; - } -} diff --git a/src/Symfony/Component/Process/Tests/SimpleProcessTest.php b/src/Symfony/Component/Process/Tests/SimpleProcessTest.php deleted file mode 100644 index 4419581167..0000000000 --- a/src/Symfony/Component/Process/Tests/SimpleProcessTest.php +++ /dev/null @@ -1,216 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Process\Tests; - -use Symfony\Component\Process\Process; - -class SimpleProcessTest extends AbstractProcessTest -{ - private $enabledSigchild = false; - - protected function setUp() - { - ob_start(); - phpinfo(INFO_GENERAL); - - $this->enabledSigchild = false !== strpos(ob_get_clean(), '--enable-sigchild'); - } - - public function testGetExitCode() - { - $this->skipIfPHPSigchild(); // This test use exitcode that is not available in this case - parent::testGetExitCode(); - } - - public function testExitCodeCommandFailed() - { - $this->skipIfPHPSigchild(); // This test use exitcode that is not available in this case - parent::testExitCodeCommandFailed(); - } - - public function testProcessIsSignaledIfStopped() - { - $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved'); - parent::testProcessIsSignaledIfStopped(); - } - - public function testProcessWithTermSignal() - { - $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved'); - parent::testProcessWithTermSignal(); - } - - public function testProcessIsNotSignaled() - { - $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved'); - parent::testProcessIsNotSignaled(); - } - - public function testProcessWithoutTermSignal() - { - $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved'); - parent::testProcessWithoutTermSignal(); - } - - public function testExitCodeText() - { - $this->skipIfPHPSigchild(); // This test use exitcode that is not available in this case - parent::testExitCodeText(); - } - - public function testIsSuccessful() - { - $this->skipIfPHPSigchild(); // This test use PID that is not available in this case - parent::testIsSuccessful(); - } - - public function testIsNotSuccessful() - { - $this->skipIfPHPSigchild(); // This test use PID that is not available in this case - parent::testIsNotSuccessful(); - } - - public function testGetPid() - { - $this->skipIfPHPSigchild(); // This test use PID that is not available in this case - parent::testGetPid(); - } - - public function testGetPidIsNullBeforeStart() - { - $this->skipIfPHPSigchild(); // This test use PID that is not available in this case - parent::testGetPidIsNullBeforeStart(); - } - - public function testGetPidIsNullAfterRun() - { - $this->skipIfPHPSigchild(); // This test use PID that is not available in this case - parent::testGetPidIsNullAfterRun(); - } - - public function testSignal() - { - $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); - parent::testSignal(); - } - - public function testProcessWithoutTermSignalIsNotSignaled() - { - $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved'); - parent::testProcessWithoutTermSignalIsNotSignaled(); - } - - public function testProcessThrowsExceptionWhenExternallySignaled() - { - $this->skipIfPHPSigchild(); // This test use PID that is not available in this case - parent::testProcessThrowsExceptionWhenExternallySignaled(); - } - - public function testExitCodeIsAvailableAfterSignal() - { - $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); - parent::testExitCodeIsAvailableAfterSignal(); - } - - /** - * @expectedException \Symfony\Component\Process\Exception\LogicException - * @expectedExceptionMessage Can not send signal on a non running process. - */ - public function testSignalProcessNotRunning() - { - parent::testSignalProcessNotRunning(); - } - - public function testSignalWithWrongIntSignal() - { - if ($this->enabledSigchild) { - $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); - } else { - $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'Error while sending signal `-4`.'); - } - parent::testSignalWithWrongIntSignal(); - } - - public function testSignalWithWrongNonIntSignal() - { - if ($this->enabledSigchild) { - $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); - } else { - $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'Error while sending signal `Céphalopodes`.'); - } - parent::testSignalWithWrongNonIntSignal(); - } - - public function testStopTerminatesProcessCleanly() - { - $process = $this->getProcess(self::$phpBin.' -r "echo \'foo\'; sleep(1); echo \'bar\';"'); - $process->run(function () use ($process) { - $process->stop(); - }); - $this->assertTrue(true, 'A call to stop() is not expected to cause wait() to throw a RuntimeException'); - } - - public function testKillSignalTerminatesProcessCleanly() - { - $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); - - $process = $this->getProcess(self::$phpBin.' -r "echo \'foo\'; sleep(1); echo \'bar\';"'); - $process->run(function () use ($process) { - if ($process->isRunning()) { - $process->signal(defined('SIGKILL') ? SIGKILL : 9); - } - }); - $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException'); - } - - public function testTermSignalTerminatesProcessCleanly() - { - $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); - - $process = $this->getProcess(self::$phpBin.' -r "echo \'foo\'; sleep(1); echo \'bar\';"'); - $process->run(function () use ($process) { - if ($process->isRunning()) { - $process->signal(defined('SIGTERM') ? SIGTERM : 15); - } - }); - $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException'); - } - - public function testStopWithTimeoutIsActuallyWorking() - { - $this->skipIfPHPSigchild(); - - parent::testStopWithTimeoutIsActuallyWorking(); - } - - /** - * {@inheritdoc} - */ - protected function getProcess($commandline, $cwd = null, array $env = null, $stdin = null, $timeout = 60, array $options = array()) - { - return new Process($commandline, $cwd, $env, $stdin, $timeout, $options); - } - - private function skipIfPHPSigchild() - { - if ($this->enabledSigchild) { - $this->markTestSkipped('Your PHP has been compiled with --enable-sigchild, this test can not be executed'); - } - } - - private function expectExceptionIfPHPSigchild($classname, $message) - { - if ($this->enabledSigchild) { - $this->setExpectedException($classname, $message); - } - } -}