Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 3b75524

Browse filesBrowse files
committed
Add option to capture stderr and stdout separately.
1 parent 5d8067f commit 3b75524
Copy full SHA for 3b75524

File tree

3 files changed

+96
-44
lines changed
Filter options

3 files changed

+96
-44
lines changed

‎src/Symfony/Component/Console/Output/ConsoleOutput.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Output/ConsoleOutput.php
+8-7Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,16 @@
1414
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
1515

1616
/**
17-
* ConsoleOutput is the default class for all CLI output. It uses STDOUT.
17+
* ConsoleOutput is the default class for all CLI output. It uses STDOUT and STDERR.
1818
*
19-
* This class is a convenient wrapper around `StreamOutput`.
19+
* This class is a convenient wrapper around `StreamOutput` for both STDOUT and STDERR.
2020
*
2121
* $output = new ConsoleOutput();
2222
*
2323
* This is equivalent to:
2424
*
2525
* $output = new StreamOutput(fopen('php://stdout', 'w'));
26+
* $stdErr = new StreamOutput(fopen('php://stderr', 'w'));
2627
*
2728
* @author Fabien Potencier <fabien@symfony.com>
2829
*/
@@ -139,18 +140,18 @@ function_exists('php_uname') ? php_uname('s') : '',
139140
*/
140141
private function openOutputStream()
141142
{
142-
$outputStream = $this->hasStdoutSupport() ? 'php://stdout' : 'php://output';
143+
if (!$this->hasStdoutSupport()) {
144+
return fopen('php://output', 'w');
145+
}
143146

144-
return @fopen($outputStream, 'w') ?: fopen('php://output', 'w');
147+
return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w');
145148
}
146149

147150
/**
148151
* @return resource
149152
*/
150153
private function openErrorStream()
151154
{
152-
$errorStream = $this->hasStderrSupport() ? 'php://stderr' : 'php://output';
153-
154-
return fopen($errorStream, 'w');
155+
return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w');
155156
}
156157
}

‎src/Symfony/Component/Console/Tester/ApplicationTester.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Tester/ApplicationTester.php
+63-13Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
use Symfony\Component\Console\Application;
1515
use Symfony\Component\Console\Input\ArrayInput;
1616
use Symfony\Component\Console\Input\InputInterface;
17+
use Symfony\Component\Console\Output\ConsoleOutput;
18+
use Symfony\Component\Console\Output\ConsoleOutputInterface;
1719
use Symfony\Component\Console\Output\OutputInterface;
1820
use Symfony\Component\Console\Output\StreamOutput;
1921

@@ -31,13 +33,13 @@ class ApplicationTester
3133
{
3234
private $application;
3335
private $input;
34-
private $output;
3536

3637
/**
37-
* Constructor.
38-
*
39-
* @param Application $application An Application instance to test.
38+
* @var OutputInterface
4039
*/
40+
private $output;
41+
private $captureOutputStreamsIndependent = false;
42+
4143
public function __construct(Application $application)
4244
{
4345
$this->application = $application;
@@ -48,9 +50,10 @@ public function __construct(Application $application)
4850
*
4951
* Available options:
5052
*
51-
* * interactive: Sets the input interactive flag
52-
* * decorated: Sets the output decorated flag
53-
* * verbosity: Sets the output verbosity flag
53+
* * interactive: Sets the input interactive flag
54+
* * decorated: Sets the output decorated flag
55+
* * verbosity: Sets the output verbosity flag
56+
* * capture_stderr_separately: Make output of stdOut and stdErr separately available
5457
*
5558
* @param array $input An array of arguments and options
5659
* @param array $options An array of options
@@ -64,12 +67,35 @@ public function run(array $input, $options = array())
6467
$this->input->setInteractive($options['interactive']);
6568
}
6669

67-
$this->output = new StreamOutput(fopen('php://memory', 'w', false));
68-
if (isset($options['decorated'])) {
69-
$this->output->setDecorated($options['decorated']);
70-
}
71-
if (isset($options['verbosity'])) {
72-
$this->output->setVerbosity($options['verbosity']);
70+
$this->captureOutputStreamsIndependent = array_key_exists('capture_stderr_separately', $options) && $options['capture_stderr_separately'];
71+
if (!$this->captureOutputStreamsIndependent) {
72+
$this->output = new StreamOutput(fopen('php://memory', 'w', false));
73+
if (isset($options['decorated'])) {
74+
$this->output->setDecorated($options['decorated']);
75+
}
76+
if (isset($options['verbosity'])) {
77+
$this->output->setVerbosity($options['verbosity']);
78+
}
79+
} else {
80+
$this->output = new ConsoleOutput(
81+
isset($options['verbosity']) ? $options['verbosity'] : ConsoleOutput::VERBOSITY_NORMAL,
82+
isset($options['decorated']) ? $options['decorated'] : null
83+
);
84+
85+
$errorOutput = new StreamOutput(fopen('php://memory', 'w', false));
86+
$errorOutput->setFormatter($this->output->getFormatter());
87+
$errorOutput->setVerbosity($this->output->getVerbosity());
88+
$errorOutput->setDecorated($this->output->isDecorated());
89+
90+
$reflectedOutput = new \ReflectionObject($this->output);
91+
$strErrProperty = $reflectedOutput->getProperty('stderr');
92+
$strErrProperty->setAccessible(true);
93+
$strErrProperty->setValue($this->output, $errorOutput);
94+
95+
$reflectedParent = $reflectedOutput->getParentClass();
96+
$streamProperty = $reflectedParent->getProperty('stream');
97+
$streamProperty->setAccessible(true);
98+
$streamProperty->setValue($this->output, fopen('php://memory', 'w', false));
7399
}
74100

75101
return $this->application->run($this->input, $this->output);
@@ -95,6 +121,30 @@ public function getDisplay($normalize = false)
95121
return $display;
96122
}
97123

124+
/**
125+
* Gets the output written to STDERR by the application.
126+
*
127+
* @param bool $normalize Whether to normalize end of lines to \n or not
128+
*
129+
* @return string
130+
*/
131+
public function getErrorOutput($normalize = false)
132+
{
133+
if (!$this->captureOutputStreamsIndependent) {
134+
throw new \LogicException('Error output not available when tester run without "capture_stderr_separately" option set.');
135+
}
136+
137+
rewind($this->output->getErrorOutput()->getStream());
138+
139+
$display = stream_get_contents($this->output->getErrorOutput()->getStream());
140+
141+
if ($normalize) {
142+
$display = str_replace(PHP_EOL, "\n", $display);
143+
}
144+
145+
return $display;
146+
}
147+
98148
/**
99149
* Gets the input instance used by the last execution of the application.
100150
*

‎src/Symfony/Component/Console/Tests/ApplicationTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Tests/ApplicationTest.php
+25-24Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -411,8 +411,9 @@ public function testSetCatchExceptions()
411411
$tester = new ApplicationTester($application);
412412

413413
$application->setCatchExceptions(true);
414-
$tester->run(array('command' => 'foo'), array('decorated' => false));
415-
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception1.txt', $tester->getDisplay(true), '->setCatchExceptions() sets the catch exception flag');
414+
$tester->run(array('command' => 'foo'), array('decorated' => false, 'capture_stderr_separately' => true));
415+
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception1.txt', $tester->getErrorOutput(true), '->setCatchExceptions() sets the catch exception flag');
416+
$this->assertSame('', $tester->getDisplay(true));
416417

417418
$application->setCatchExceptions(false);
418419
try {
@@ -461,22 +462,22 @@ public function testRenderException()
461462
->will($this->returnValue(120));
462463
$tester = new ApplicationTester($application);
463464

464-
$tester->run(array('command' => 'foo'), array('decorated' => false));
465-
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception1.txt', $tester->getDisplay(true), '->renderException() renders a pretty exception');
465+
$tester->run(array('command' => 'foo'), array('decorated' => false, 'capture_stderr_separately' => true));
466+
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception1.txt', $tester->getErrorOutput(true), '->renderException() renders a pretty exception');
466467

467-
$tester->run(array('command' => 'foo'), array('decorated' => false, 'verbosity' => Output::VERBOSITY_VERBOSE));
468-
$this->assertContains('Exception trace', $tester->getDisplay(), '->renderException() renders a pretty exception with a stack trace when verbosity is verbose');
468+
$tester->run(array('command' => 'foo'), array('decorated' => false, 'verbosity' => Output::VERBOSITY_VERBOSE, 'capture_stderr_separately' => true));
469+
$this->assertContains('Exception trace', $tester->getErrorOutput(), '->renderException() renders a pretty exception with a stack trace when verbosity is verbose');
469470

470-
$tester->run(array('command' => 'list', '--foo' => true), array('decorated' => false));
471-
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception2.txt', $tester->getDisplay(true), '->renderException() renders the command synopsis when an exception occurs in the context of a command');
471+
$tester->run(array('command' => 'list', '--foo' => true), array('decorated' => false, 'capture_stderr_separately' => true));
472+
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception2.txt', $tester->getErrorOutput(true), '->renderException() renders the command synopsis when an exception occurs in the context of a command');
472473

473474
$application->add(new \Foo3Command());
474475
$tester = new ApplicationTester($application);
475-
$tester->run(array('command' => 'foo3:bar'), array('decorated' => false));
476-
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception3.txt', $tester->getDisplay(true), '->renderException() renders a pretty exceptions with previous exceptions');
476+
$tester->run(array('command' => 'foo3:bar'), array('decorated' => false, 'capture_stderr_separately' => true));
477+
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception3.txt', $tester->getErrorOutput(true), '->renderException() renders a pretty exceptions with previous exceptions');
477478

478-
$tester->run(array('command' => 'foo3:bar'), array('decorated' => true));
479-
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception3decorated.txt', $tester->getDisplay(true), '->renderException() renders a pretty exceptions with previous exceptions');
479+
$tester->run(array('command' => 'foo3:bar'), array('decorated' => true, 'capture_stderr_separately' => true));
480+
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception3decorated.txt', $tester->getErrorOutput(true), '->renderException() renders a pretty exceptions with previous exceptions');
480481

481482
$application = $this->getMock('Symfony\Component\Console\Application', array('getTerminalWidth'));
482483
$application->setAutoExit(false);
@@ -485,8 +486,8 @@ public function testRenderException()
485486
->will($this->returnValue(32));
486487
$tester = new ApplicationTester($application);
487488

488-
$tester->run(array('command' => 'foo'), array('decorated' => false));
489-
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception4.txt', $tester->getDisplay(true), '->renderException() wraps messages when they are bigger than the terminal');
489+
$tester->run(array('command' => 'foo'), array('decorated' => false, 'capture_stderr_separately' => true));
490+
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception4.txt', $tester->getErrorOutput(true), '->renderException() wraps messages when they are bigger than the terminal');
490491
}
491492

492493
/**
@@ -504,11 +505,11 @@ public function testRenderExceptionWithDoubleWidthCharacters()
504505
});
505506
$tester = new ApplicationTester($application);
506507

507-
$tester->run(array('command' => 'foo'), array('decorated' => false));
508-
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_doublewidth1.txt', $tester->getDisplay(true), '->renderException() renders a pretty exceptions with previous exceptions');
508+
$tester->run(array('command' => 'foo'), array('decorated' => false, 'capture_stderr_separately' => true));
509+
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_doublewidth1.txt', $tester->getErrorOutput(true), '->renderException() renders a pretty exceptions with previous exceptions');
509510

510-
$tester->run(array('command' => 'foo'), array('decorated' => true));
511-
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_doublewidth1decorated.txt', $tester->getDisplay(true), '->renderException() renders a pretty exceptions with previous exceptions');
511+
$tester->run(array('command' => 'foo'), array('decorated' => true, 'capture_stderr_separately' => true));
512+
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_doublewidth1decorated.txt', $tester->getErrorOutput(true), '->renderException() renders a pretty exceptions with previous exceptions');
512513

513514
$application = $this->getMock('Symfony\Component\Console\Application', array('getTerminalWidth'));
514515
$application->setAutoExit(false);
@@ -519,8 +520,8 @@ public function testRenderExceptionWithDoubleWidthCharacters()
519520
throw new \Exception('コマンドの実行中にエラーが発生しました。');
520521
});
521522
$tester = new ApplicationTester($application);
522-
$tester->run(array('command' => 'foo'), array('decorated' => false));
523-
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_doublewidth2.txt', $tester->getDisplay(true), '->renderException() wraps messages when they are bigger than the terminal');
523+
$tester->run(array('command' => 'foo'), array('decorated' => false, 'capture_stderr_separately' => true));
524+
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_doublewidth2.txt', $tester->getErrorOutput(true), '->renderException() wraps messages when they are bigger than the terminal');
524525
}
525526

526527
public function testRun()
@@ -622,8 +623,8 @@ public function testRunReturnsIntegerExitCode()
622623
$application = $this->getMock('Symfony\Component\Console\Application', array('doRun'));
623624
$application->setAutoExit(false);
624625
$application->expects($this->once())
625-
->method('doRun')
626-
->will($this->throwException($exception));
626+
->method('doRun')
627+
->will($this->throwException($exception));
627628

628629
$exitCode = $application->run(new ArrayInput(array()), new NullOutput());
629630

@@ -637,8 +638,8 @@ public function testRunReturnsExitCodeOneForExceptionCodeZero()
637638
$application = $this->getMock('Symfony\Component\Console\Application', array('doRun'));
638639
$application->setAutoExit(false);
639640
$application->expects($this->once())
640-
->method('doRun')
641-
->will($this->throwException($exception));
641+
->method('doRun')
642+
->will($this->throwException($exception));
642643

643644
$exitCode = $application->run(new ArrayInput(array()), new NullOutput());
644645

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.