8000 [Console] added a Process helper by romainneutron · Pull Request #10627 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[Console] added a Process helper #10627

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 11, 2014
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
[Console] Add process helper tests
  • Loading branch information
romainneutron committed Jun 11, 2014
commit edc1bfeb2b1a5d2b4cab20e3728b6bc3919e689c
4 changes: 4 additions & 0 deletions src/Symfony/Component/Console/Application.php
Ori 10000 ginal file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

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;
Expand Down Expand Up @@ -962,6 +964,8 @@ protected function getDefaultHelperSet()
new DialogHelper(),
new ProgressHelper(),
new TableHelper(),
new DebugFormatterHelper(),
new ProcessHelper(),
new QuestionHelper(),
));
}
Expand Down
8 changes: 6 additions & 2 deletions src/Symfony/Component/Console/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
CHANGELOG
=========

2.6.0
-----

* added a Process helper
* added a DebugFormatter helper
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those changes can't land in 2.5 as it was already released, it can land only in 2.6+.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right :) it's fixed


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
-----
Expand Down
57 changes: 51 additions & 6 deletions src/Symfony/Component/Console/Helper/DebugFormatterHelper.php
EDBE
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -26,27 +24,55 @@ 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')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add the phpdoc for the methods

{
$this->started[$id] = array('border' => ++$this->count % count($this->colors));

return sprintf("%s<bg=blue;fg=white> %s </> <fg=blue>%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<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix);
$message .= sprintf("%s<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix);
$this->started[$id]['err'] = true;
}

$message .= str_replace("\n", sprintf("\n%s<bg=red;fg=white> %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<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix);
$message .= sprintf("%s<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix);
$this->started[$id]['out'] = true;
}

Expand All @@ -56,6 +82,16 @@ public function progress($id, $buffer, $error = false, $prefix = 'OUT', $errorPr
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" : '';
Expand All @@ -64,16 +100,25 @@ public function stop($id, $message, $successful, $prefix = 'RES')
return sprintf("%s%s<bg=green;fg=white> %s </> <fg=green>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
}

return sprintf("%s%s<bg=red;fg=white> %s </> <fg=red>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
$message = sprintf("%s%s<bg=red;fg=white> %s </> <fg=red>%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('<bg=%s> </>', $this->colors[$this->started[$id]['border']]);
}

/**
* {@inheritDoc}
* {@inheritdoc}
*/
public function getName()
{
Expand Down
74 changes: 55 additions & 19 deletions src/Symfony/Component/Console/Helper/ProcessHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Output\OutputInterface;
use Symfon 1E0A y\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 <fabien@symfony.com>
*/
Expand All @@ -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('<error>%s</error>'), $error);
$output->writeln(sprintf('<error>%s</error>', $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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does not seem to exist?!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

however, the use statement is indeed missing

}

return $process;
Expand All @@ -68,23 +101,26 @@ public function run(OutputInterface $output, $cmd, $error = null, $callback = nu
* 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()
{
Expand Down
108 changes: 108 additions & 0 deletions src/Symfony/Component/Console/Tests/Helper/ProcessHelperTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;< 10000 /span>
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 = <<<EOT
RUN php -r "echo 42;"
RES Command ran successfully

EOT;
$successOutputDebug = <<<EOT
RUN php -r "echo 42;"
OUT 42
RES Command ran successfully

EOT;
$successOutputProcessDebug = <<<EOT
RUN 'php' '-r' 'echo 42;'
OUT 42
RES Command ran successfully

EOT;
$syntaxErrorOutputVerbose = <<<EOT
RUN php -r "fwrite(STDERR, 'error message');usleep(50000);fwrite(STDOUT, 'out message');exit(252);"
RES 252 Command did not run successfully

EOT;
$syntaxErrorOutputDebug = <<<EOT
RUN php -r "fwrite(STDERR, 'error message');usleep(50000);fwrite(STDOUT, 'out message');exit(252);"
ERR error message
OUT out message
RES 252 Command did not run successfully

EOT;

$errorMessage = 'An error occurred';

return array(
array('', 'php -r "echo 42;"', StreamOutput::VERBOSITY_VERBOSE, null),
array($successOutputVerbose, 'php -r "echo 42;"', StreamOutput::VERBOSITY_VERY_VERBOSE, null),
array($successOutputDebug, 'php -r "echo 42;"', StreamOutput::VERBOSITY_DEBUG, null),
array('', 'php -r "syntax error"', StreamOutput::VERBOSITY_VERBOSE, null),
array($syntaxErrorOutputVerbose, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERY_VERBOSE, null),
array($syntaxErrorOutputDebug, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_DEBUG, null),
array($errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERBOSE, $errorMessage),
array($syntaxErrorOutputVerbose.$errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERY_VERBOSE, $errorMessage),
array($syntaxErrorOutputDebug.$errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_DEBUG, $errorMessage),
array($successOutputProcessDebug, array('php', '-r', 'echo 42;'), StreamOutput::VERBOSITY_DEBUG, null),
array($successOutputDebug, new Process('php -r "echo 42;"'), StreamOutput::VERBOSITY_DEBUG, null),
);
}

private function getOutputStream($verbosity)
{
return new StreamOutput(fopen('php://memory', 'r+', false), $verbosity, false);
}

private function getOutput(StreamOutput $output)
{
rewind($output->getStream());

return stream_get_contents($output->getStream());
}
}
Loading
0