Skip to content

Commit f41913c

Browse files
committed
[Console] Add process helper tests
1 parent c670801 commit f41913c

File tree

6 files changed

+225
-27
lines changed

6 files changed

+225
-27
lines changed

Application.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
use Symfony\Component\Console\Descriptor\TextDescriptor;
1515
use Symfony\Component\Console\Descriptor\XmlDescriptor;
16+
use Symfony\Component\Console\Helper\DebugFormatterHelper;
17+
use Symfony\Component\Console\Helper\ProcessHelper;
1618
use Symfony\Component\Console\Helper\QuestionHelper;
1719
use Symfony\Component\Console\Input\InputInterface;
1820
use Symfony\Component\Console\Input\ArgvInput;
@@ -962,6 +964,8 @@ protected function getDefaultHelperSet()
962964
new DialogHelper(),
963965
new ProgressHelper(),
964966
new TableHelper(),
967+
new DebugFormatterHelper(),
968+
new ProcessHelper(),
965969
new QuestionHelper(),
966970
));
967971
}

CHANGELOG.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
CHANGELOG
22
=========
33

4+
2.6.0
5+
-----
6+
7+
* added a Process helper
8+
* added a DebugFormatter helper
9+
410
2.5.0
511
-----
612

713
* deprecated the dialog helper (use the question helper instead)
8-
* added a Process helper
914
* deprecated TableHelper in favor of Table
1015
* deprecated ProgressHelper in favor of ProgressBar
1116
* added a question helper
1217
* added a way to set the process name of a command
1318
* added a way to set a default command instead of `ListCommand`
14-
* added a way to set the process title of a command
1519

1620
2.4.0
1721
-----

Helper/DebugFormatterHelper.php

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111

1212
namespace Symfony\Component\Console\Helper;
1313

14-
use Symfony\Component\Console\Helper\Helper;
15-
1614
/**
1715
* Helps outputting debug information when running an external program from a command.
1816
*
@@ -26,27 +24,55 @@ class DebugFormatterHelper extends Helper
2624
private $started = array();
2725
private $count = -1;
2826

27+
/**
28+
* Starts a debug formatting session
29+
*
30+
* @param string $id The id of the formatting session
31+
* @param string $message The message to display
32+
* @param string $prefix The prefix to use
33+
*
34+
* @return string
35+
*/
2936
public function start($id, $message, $prefix = 'RUN')
3037
{
3138
$this->started[$id] = array('border' => ++$this->count % count($this->colors));
3239

3340
return sprintf("%s<bg=blue;fg=white> %s </> <fg=blue>%s</>\n", $this->getBorder($id), $prefix, $message);
3441
}
3542

43+
/**
44+
* Adds progress to a formatting session
45+
*
46+
* @param string $id The id of the formatting session
47+
* @param string $buffer The message to display
48+
* @param bool $error Whether to consider the buffer as error
49+
* @param string $prefix The prefix for output
50+
* @param string $errorPrefix The prefix for error output
51+
*
52+
* @return string
53+
*/
3654
public function progress($id, $buffer, $error = false, $prefix = 'OUT', $errorPrefix = 'ERR')
3755
{
3856
$message = '';
3957

4058
if ($error) {
59+
if (isset($this->started[$id]['out'])) {
60+
$message .= "\n";
61+
unset($this->started[$id]['out']);
62+
}
4163
if (!isset($this->started[$id]['err'])) {
42-
$message = sprintf("%s<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix);
64+
$message .= sprintf("%s<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix);
4365
$this->started[$id]['err'] = true;
4466
}
4567

4668
$message .= str_replace("\n", sprintf("\n%s<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix), $buffer);
4769
} else {
70+
if (isset($this->started[$id]['err'])) {
71+
$message .= "\n";
72+
unset($this->started[$id]['err']);
73+
}
4874
if (!isset($this->started[$id]['out'])) {
49-
$message = sprintf("%s<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix);
75+
$message .= sprintf("%s<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix);
5076
$this->started[$id]['out'] = true;
5177
}
5278

@@ -56,6 +82,16 @@ public function progress($id, $buffer, $error = false, $prefix = 'OUT', $errorPr
5682
return $message;
5783
}
5884

85+
/**
86+
* Stops a formatting session
87+
*
88+
* @param string $id The id of the formatting session
89+
* @param string $message The message to display
90+
* @param bool $successful Whether to consider the result as success
91+
* @param string $prefix The prefix for the end output
92+
*
93+
* @return string
94+
*/
5995
public function stop($id, $message, $successful, $prefix = 'RES')
6096
{
6197
$trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : '';
@@ -64,16 +100,25 @@ public function stop($id, $message, $successful, $prefix = 'RES')
64100
return sprintf("%s%s<bg=green;fg=white> %s </> <fg=green>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
65101
}
66102

67-
return sprintf("%s%s<bg=red;fg=white> %s </> <fg=red>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
103+
$message = sprintf("%s%s<bg=red;fg=white> %s </> <fg=red>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
104+
105+
unset($this->started[$id]['out'], $this->started[$id]['err']);
106+
107+
return $message;
68108
}
69109

110+
/**
111+
* @param string $id The id of the formatting session
112+
*
113+
* @return string
114+
*/
70115
private function getBorder($id)
71116
{
72117
return sprintf('<bg=%s> </>', $this->colors[$this->started[$id]['border']]);
73118
}
74119

75120
/**
76-
* {@inheritDoc}
121+
* {@inheritdoc}
77122
*/
78123
public function getName()
79124
{

Helper/ProcessHelper.php

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@
1111

1212
namespace Symfony\Component\Console\Helper;
1313

14-
use Symfony\Component\Console\Helper\Helper;
1514
use Symfony\Component\Console\Output\OutputInterface;
15+
use Symfony\Component\Process\Exception\ProcessFailedException;
1616
use Symfony\Component\Process\Process;
17+
use Symfony\Component\Process\ProcessBuilder;
1718

1819
/**
19-
* The Process class provides helpers to run external processes.
20+
* The ProcessHelper class provides helpers to run external processes.
2021
*
2122
* @author Fabien Potencier <[email protected]>
2223
*/
@@ -25,40 +26,72 @@ class ProcessHelper extends Helper
2526
/**
2627
* Runs an external process.
2728
*
28-
* @param OutputInterface $output An OutputInterface instance
29-
* @param string|Process $cmd An instance of Process or a command to run
30-
* @param string|null $error An error message that must be displayed if something went wrong
31-
* @param callback|null $callback A PHP callback to run whenever there is some
32-
* output available on STDOUT or STDERR
29+
* @param OutputInterface $output An OutputInterface instance
30+
* @param string|array|Process $cmd An instance of Process or an array of arguments to escape and run or a command to run
31+
* @param string|null $error An error message that must be displayed if something went wrong
32+
* @param callable|null $callback A PHP callback to run whenever there is some
33+
* output available on STDOUT or STDERR
3334
*
3435
* @return Process The process that ran
3536
*/
3637
public function run(OutputInterface $output, $cmd, $error = null, $callback = null)
3738
{
38-
$verbose = $output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE;
39-
$debug = $output->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG;
40-
4139
$formatter = $this->getHelperSet()->get('debug_formatter');
4240

43-
$process = $cmd instanceof Process ? $cmd : new Process($cmd);
41+
if (is_array($cmd)) {
42+
$process = ProcessBuilder::create($cmd)->getProcess();
43+
} elseif ($cmd instanceof Process) {
44+
$process = $cmd;
45+
} else {
46+
$process = new Process($cmd);
47+
}
4448

45-
if ($verbose) {
49+
if ($output->isVeryVerbose()) {
4650
$output->write($formatter->start(spl_object_hash($process), $process->getCommandLine()));
4751
}
4852

49-
if ($debug) {
53+
if ($output->isDebug()) {
5054
$callback = $this->wrapCallback($output, $process, $callback);
5155
}
5256

5357
$process->run($callback);
5458

55-
if ($verbose) {
56-
$message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run sucessfully', $process->getExitCode());
59+
if ($output->isVeryVerbose()) {
60+
$message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode());
5761
$output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful()));
5862
}
5963

6064
if (!$process->isSuccessful() && null !== $error) {
61-
$output->writeln(sprintf('<error>%s</error>'), $error);
65+
$output->writeln(sprintf('<error>%s</error>', $error));
66+
}
67+
68+
return $process;
69+
}
70+
71+
/**
72+
* Runs the process.
73+
*
74+
* This is identical to run() except that an exception is thrown if the process
75+
* exits with a non-zero exit code.
76+
*
77+
* @param OutputInterface $output An OutputInterface instance
78+
* @param string|Process $cmd An instance of Process or a command to run
79+
* @param string|null $error An error message that must be displayed if something went wrong
80+
* @param callable|null $callback A PHP callback to run whenever there is some
81+
* output available on STDOUT or STDERR
82+
*
83+
* @return Process The process that ran
84+
*
85+
* @throws ProcessFailedException
86+
*
87+
* @see run()
88+
*/
89+
public function mustRun(OutputInterface $output, $cmd, $error = null, $callback = null)
90+
{
91+
$process = $this->run($output, $cmd, $error, $callback);
92+
93+
if (!$process->isSuccessful()) {
94+
throw new ProcessFailedException($process);
6295
}
6396

6497
return $process;
@@ -68,23 +101,26 @@ public function run(OutputInterface $output, $cmd, $error = null, $callback = nu
68101
* Wraps a Process callback to add debugging output.
69102
*
70103
* @param OutputInterface $output An OutputInterface interface
104+
* @param Process $process The Process
71105
* @param callable|null $callback A PHP callable
106+
*
107+
* @return callable
72108
*/
73109
public function wrapCallback(OutputInterface $output, Process $process, $callback = null)
74110
{
75111
$formatter = $this->getHelperSet()->get('debug_formatter');
76112

77113
return function ($type, $buffer) use ($output, $process, $callback, $formatter) {
78-
$output->write($formatter->progress(spl_object_hash($process), $buffer, 'err' === $type));
114+
$output->write($formatter->progress(spl_object_hash($process), $buffer, Process::ERR === $type));
79115

80116
if (null !== $callback) {
81-
$callback($type, $buffer);
117+
call_user_func($callback, $type, $buffer);
82118
}
83119
};
84120
}
85121

86122
/**
87-
* {@inheritDoc}
123+
* {@inheritdoc}
88124
*/
89125
public function getName()
90126
{

Tests/Helper/ProcessHelperTest.php

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Console\Tests\Helper;
13+
14+
use Symfony\Component\Console\Helper\DebugFormatterHelper;
15+
use Symfony\Component\Console\Helper\HelperSet;
16+
use Symfony\Component\Console\Helper\Helper;
17+
use Symfony\Component\Console\Output\StreamOutput;
18+
use Symfony\Component\Console\Helper\ProcessHelper;
19+
use Symfony\Component\Process\Process;
20+
21+
class ProcessHelperTest extends \PHPUnit_Framework_TestCase
22+
{
23+
/**
24+
* @dataProvider provideCommandsAndOutput
25+
*/
26+
public function testVariousProcessRuns($expected, $cmd, $verbosity, $error)
27+
{
28+
$helper = new ProcessHelper();
29+
$helper->setHelperSet(new HelperSet(array(new DebugFormatterHelper())));
30+
$output = $this->getOutputStream($verbosity);
31+
$helper->run($output, $cmd, $error);
32+
$this->assertEquals($expected, $this->getOutput($output));
33+
}
34+
35+
public function testPassedCallbackIsExecuted()
36+
{
37+
$helper = new ProcessHelper();
38+
$helper->setHelperSet(new HelperSet(array(new DebugFormatterHelper())));
39+
$output = $this->getOutputStream(StreamOutput::VERBOSITY_NORMAL);
40+
41+
$executed = false;
42+
$callback = function () use (&$executed) { $executed = true; };
43+
44+
$helper->run($output, 'php -r "echo 42;"', null, $callback);
45+
$this->assertTrue($executed);
46+
}
47+
48+
public function provideCommandsAndOutput()
49+
{
50+
$successOutputVerbose = <<<EOT
51+
RUN php -r "echo 42;"
52+
RES Command ran successfully
53+
54+
EOT;
55+
$successOutputDebug = <<<EOT
56+
RUN php -r "echo 42;"
57+
OUT 42
58+
RES Command ran successfully
59+
60+
EOT;
61+
$successOutputProcessDebug = <<<EOT
62+
RUN 'php' '-r' 'echo 42;'
63+
OUT 42
64+
RES Command ran successfully
65+
66+
EOT;
67+
$syntaxErrorOutputVerbose = <<<EOT
68+
RUN php -r "fwrite(STDERR, 'error message');usleep(50000);fwrite(STDOUT, 'out message');exit(252);"
69+
RES 252 Command did not run successfully
70+
71+
EOT;
72+
$syntaxErrorOutputDebug = <<<EOT
73+
RUN php -r "fwrite(STDERR, 'error message');usleep(50000);fwrite(STDOUT, 'out message');exit(252);"
74+
ERR error message
75+
OUT out message
76+
RES 252 Command did not run successfully
77+
78+
EOT;
79+
80+
$errorMessage = 'An error occurred';
81+
82+
return array(
83+
array('', 'php -r "echo 42;"', StreamOutput::VERBOSITY_VERBOSE, null),
84+
array($successOutputVerbose, 'php -r "echo 42;"', StreamOutput::VERBOSITY_VERY_VERBOSE, null),
85+
array($successOutputDebug, 'php -r "echo 42;"', StreamOutput::VERBOSITY_DEBUG, null),
86+
array('', 'php -r "syntax error"', StreamOutput::VERBOSITY_VERBOSE, null),
87+
array($syntaxErrorOutputVerbose, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERY_VERBOSE, null),
88+
array($syntaxErrorOutputDebug, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_DEBUG, null),
89+
array($errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERBOSE, $errorMessage),
90+
array($syntaxErrorOutputVerbose.$errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERY_VERBOSE, $errorMessage),
91+
array($syntaxErrorOutputDebug.$errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_DEBUG, $errorMessage),
92+
array($successOutputProcessDebug, array('php', '-r', 'echo 42;'), StreamOutput::VERBOSITY_DEBUG, null),
93+
array($successOutputDebug, new Process('php -r "echo 42;"'), StreamOutput::VERBOSITY_DEBUG, null),
94+
);
95+
}
96+
97+
private function getOutputStream($verbosity)
98+
{
99+
return new StreamOutput(fopen('php://memory', 'r+', false), $verbosity, false);
100+
}
101+
102+
private function getOutput(StreamOutput $output)
103+
{
104+
rewind($output->getStream());
105+
106+
return stream_get_contents($output->getStream());
107+
}
108+
}

0 commit comments

Comments
 (0)