From edc1bfeb2b1a5d2b4cab20e3728b6bc3919e689c Mon Sep 17 00:00:00 2001 From: Romain Neutron Date: Thu, 3 Apr 2014 10:19:54 +0200 Subject: [PATCH] [Console] Add process helper tests --- src/Symfony/Component/Console/Application.php | 4 + src/Symfony/Component/Console/CHANGELOG.md | 8 +- .../Console/Helper/DebugFormatterHelper.php | 57 ++++++++- .../Console/Helper/ProcessHelper.php | 74 +++++++++--- .../Tests/Helper/ProcessHelperTest.php | 108 ++++++++++++++++++ src/Symfony/Component/Console/composer.json | 1 + 6 files changed, 225 insertions(+), 27 deletions(-) create mode 100644 src/Symfony/Component/Console/Tests/Helper/ProcessHelperTest.php diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php index 41d8f93bbb..5b3a0dc286 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -13,6 +13,8 @@ namespace Symfony\Component\Console; use Symfony\Component\Console\Descriptor\TextDescriptor; use Symfony\Component\Console\Descriptor\XmlDescriptor; +use Symfony\Component\Console\Helper\DebugFormatterHelper; +use Symfony\Component\Console\Helper\ProcessHelper; use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\ArgvInput; @@ -962,6 +964,8 @@ class Application new DialogHelper(), new ProgressHelper(), new TableHelper(), + new DebugFormatterHelper(), + new ProcessHelper(), new QuestionHelper(), )); } diff --git a/src/Symfony/Component/Console/CHANGELOG.md b/src/Symfony/Component/Console/CHANGELOG.md index c741f156f7..55ded1f280 100644 --- a/src/Symfony/Component/Console/CHANGELOG.md +++ b/src/Symfony/Component/Console/CHANGELOG.md @@ -1,17 +1,21 @@ CHANGELOG ========= +2.6.0 +----- + + * added a Process helper + * added a DebugFormatter helper + 2.5.0 ----- * deprecated the dialog helper (use the question helper instead) - * added a Process helper * deprecated TableHelper in favor of Table * deprecated ProgressHelper in favor of ProgressBar * added a question helper * added a way to set the process name of a command * added a way to set a default command instead of `ListCommand` - * added a way to set the process title of a command 2.4.0 ----- diff --git a/src/Symfony/Component/Console/Helper/DebugFormatterHelper.php b/src/Symfony/Component/Console/Helper/DebugFormatterHelper.php index c7616cf898..cdb620d168 100644 --- a/src/Symfony/Component/Console/Helper/DebugFormatterHelper.php +++ b/src/Symfony/Component/Console/Helper/DebugFormatterHelper.php @@ -11,8 +11,6 @@ namespace Symfony\Component\Console\Helper; -use Symfony\Component\Console\Helper\Helper; - /** * Helps outputting debug information when running an external program from a command. * @@ -26,6 +24,15 @@ class DebugFormatterHelper extends Helper private $started = array(); private $count = -1; + /** + * Starts a debug formatting session + * + * @param string $id The id of the formatting session + * @param string $message The message to display + * @param string $prefix The prefix to use + * + * @return string + */ public function start($id, $message, $prefix = 'RUN') { $this->started[$id] = array('border' => ++$this->count % count($this->colors)); @@ -33,20 +40,39 @@ class DebugFormatterHelper extends Helper return sprintf("%s %s %s\n", $this->getBorder($id), $prefix, $message); } + /** + * Adds progress to a formatting session + * + * @param string $id The id of the formatting session + * @param string $buffer The message to display + * @param bool $error Whether to consider the buffer as error + * @param string $prefix The prefix for output + * @param string $errorPrefix The prefix for error output + * + * @return string + */ public function progress($id, $buffer, $error = false, $prefix = 'OUT', $errorPrefix = 'ERR') { $message = ''; if ($error) { + if (isset($this->started[$id]['out'])) { + $message .= "\n"; + unset($this->started[$id]['out']); + } if (!isset($this->started[$id]['err'])) { - $message = sprintf("%s %s ", $this->getBorder($id), $errorPrefix); + $message .= sprintf("%s %s ", $this->getBorder($id), $errorPrefix); $this->started[$id]['err'] = true; } $message .= str_replace("\n", sprintf("\n%s %s ", $this->getBorder($id), $errorPrefix), $buffer); } else { + if (isset($this->started[$id]['err'])) { + $message .= "\n"; + unset($this->started[$id]['err']); + } if (!isset($this->started[$id]['out'])) { - $message = sprintf("%s %s ", $this->getBorder($id), $prefix); + $message .= sprintf("%s %s ", $this->getBorder($id), $prefix); $this->started[$id]['out'] = true; } @@ -56,6 +82,16 @@ class DebugFormatterHelper extends Helper return $message; } + /** + * Stops a formatting session + * + * @param string $id The id of the formatting session + * @param string $message The message to display + * @param bool $successful Whether to consider the result as success + * @param string $prefix The prefix for the end output + * + * @return string + */ public function stop($id, $message, $successful, $prefix = 'RES') { $trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : ''; @@ -64,16 +100,25 @@ class DebugFormatterHelper extends Helper return sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); } - return sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); + $message = sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); + + unset($this->started[$id]['out'], $this->started[$id]['err']); + + return $message; } + /** + * @param string $id The id of the formatting session + * + * @return string + */ private function getBorder($id) { return sprintf(' ', $this->colors[$this->started[$id]['border']]); } /** - * {@inheritDoc} + * {@inheritdoc} */ public function getName() { diff --git a/src/Symfony/Component/Console/Helper/ProcessHelper.php b/src/Symfony/Component/Console/Helper/ProcessHelper.php index 30c38c1ece..2ae780b801 100644 --- a/src/Symfony/Component/Console/Helper/ProcessHelper.php +++ b/src/Symfony/Component/Console/Helper/ProcessHelper.php @@ -11,12 +11,13 @@ namespace Symfony\Component\Console\Helper; -use Symfony\Component\Console\Helper\Helper; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Process\Exception\ProcessFailedException; use Symfony\Component\Process\Process; +use Symfony\Component\Process\ProcessBuilder; /** - * The Process class provides helpers to run external processes. + * The ProcessHelper class provides helpers to run external processes. * * @author Fabien Potencier */ @@ -25,40 +26,72 @@ class ProcessHelper extends Helper /** * Runs an external process. * - * @param OutputInterface $output An OutputInterface instance - * @param string|Process $cmd An instance of Process or a command to run - * @param string|null $error An error message that must be displayed if something went wrong - * @param callback|null $callback A PHP callback to run whenever there is some - * output available on STDOUT or STDERR + * @param OutputInterface $output An OutputInterface instance + * @param string|array|Process $cmd An instance of Process or an array of arguments to escape and run or a command to run + * @param string|null $error An error message that must be displayed if something went wrong + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR * * @return Process The process that ran */ public function run(OutputInterface $output, $cmd, $error = null, $callback = null) { - $verbose = $output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE; - $debug = $output->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG; - $formatter = $this->getHelperSet()->get('debug_formatter'); - $process = $cmd instanceof Process ? $cmd : new Process($cmd); + if (is_array($cmd)) { + $process = ProcessBuilder::create($cmd)->getProcess(); + } elseif ($cmd instanceof Process) { + $process = $cmd; + } else { + $process = new Process($cmd); + } - if ($verbose) { + if ($output->isVeryVerbose()) { $output->write($formatter->start(spl_object_hash($process), $process->getCommandLine())); } - if ($debug) { + if ($output->isDebug()) { $callback = $this->wrapCallback($output, $process, $callback); } $process->run($callback); - if ($verbose) { - $message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run sucessfully', $process->getExitCode()); + if ($output->isVeryVerbose()) { + $message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode()); $output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful())); } if (!$process->isSuccessful() && null !== $error) { - $output->writeln(sprintf('%s'), $error); + $output->writeln(sprintf('%s', $error)); + } + + return $process; + } + + /** + * Runs the process. + * + * This is identical to run() except that an exception is thrown if the process + * exits with a non-zero exit code. + * + * @param OutputInterface $output An OutputInterface instance + * @param string|Process $cmd An instance of Process or a command to run + * @param string|null $error An error message that must be displayed if something went wrong + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @return Process The process that ran + * + * @throws ProcessFailedException + * + * @see run() + */ + public function mustRun(OutputInterface $output, $cmd, $error = null, $callback = null) + { + $process = $this->run($output, $cmd, $error, $callback); + + if (!$process->isSuccessful()) { + throw new ProcessFailedException($process); } return $process; @@ -68,23 +101,26 @@ class ProcessHelper extends Helper * Wraps a Process callback to add debugging output. * * @param OutputInterface $output An OutputInterface interface + * @param Process $process The Process * @param callable|null $callback A PHP callable + * + * @return callable */ public function wrapCallback(OutputInterface $output, Process $process, $callback = null) { $formatter = $this->getHelperSet()->get('debug_formatter'); return function ($type, $buffer) use ($output, $process, $callback, $formatter) { - $output->write($formatter->progress(spl_object_hash($process), $buffer, 'err' === $type)); + $output->write($formatter->progress(spl_object_hash($process), $buffer, Process::ERR === $type)); if (null !== $callback) { - $callback($type, $buffer); + call_user_func($callback, $type, $buffer); } }; } /** - * {@inheritDoc} + * {@inheritdoc} */ public function getName() { diff --git a/src/Symfony/Component/Console/Tests/Helper/ProcessHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/ProcessHelperTest.php new file mode 100644 index 0000000000..0ca247f1eb --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Helper/ProcessHelperTest.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Helper; + +use Symfony\Component\Console\Helper\DebugFormatterHelper; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Output\StreamOutput; +use Symfony\Component\Console\Helper\ProcessHelper; +use Symfony\Component\Process\Process; + +class ProcessHelperTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider provideCommandsAndOutput + */ + public function testVariousProcessRuns($expected, $cmd, $verbosity, $error) + { + $helper = new ProcessHelper(); + $helper->setHelperSet(new HelperSet(array(new DebugFormatterHelper()))); + $output = $this->getOutputStream($verbosity); + $helper->run($output, $cmd, $error); + $this->assertEquals($expected, $this->getOutput($output)); + } + + public function testPassedCallbackIsExecuted() + { + $helper = new ProcessHelper(); + $helper->setHelperSet(new HelperSet(array(new DebugFormatterHelper()))); + $output = $this->getOutputStream(StreamOutput::VERBOSITY_NORMAL); + + $executed = false; + $callback = function () use (&$executed) { $executed = true; }; + + $helper->run($output, 'php -r "echo 42;"', null, $callback); + $this->assertTrue($executed); + } + + public function provideCommandsAndOutput() + { + $successOutputVerbose = <<getStream()); + + return stream_get_contents($output->getStream()); + } +} diff --git a/src/Symfony/Component/Console/composer.json b/src/Symfony/Component/Console/composer.json index ec597c558c..4a106d7db6 100644 --- a/src/Symfony/Component/Console/composer.json +++ b/src/Symfony/Component/Console/composer.json @@ -20,6 +20,7 @@ }, "require-dev": { "symfony/event-dispatcher": "~2.1", + "symfony/process": "~2.1", "psr/log": "~1.0" }, "suggest": {