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 af36c83

Browse filesBrowse files
committed
feature #17255 [Console][2.3] ApplicationTester - test stdout and stderr (SpacePossum)
This PR was submitted for the 2.3 branch but it was merged into the 3.1-dev branch instead (closes #17255). Discussion ---------- [Console][2.3] ApplicationTester - test stdout and stderr | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | License | MIT Currently it is not possible to test application output of both `stdout` _and_ `stderr` using the `ApplicationTester`. This makes it hard to check if an application writes to the correct output. Commits ------- 6ff6a28 [Console][2.3] ApplicationTester - test stdout and stderr
2 parents 464a492 + 6ff6a28 commit af36c83
Copy full SHA for af36c83

File tree

Expand file treeCollapse file tree

3 files changed

+98
-41
lines changed
Filter options
Expand file treeCollapse file tree

3 files changed

+98
-41
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
+62-14Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
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;
1718
use Symfony\Component\Console\Output\OutputInterface;
1819
use Symfony\Component\Console\Output\StreamOutput;
1920

@@ -31,14 +32,13 @@ class ApplicationTester
3132
{
3233
private $application;
3334
private $input;
34-
private $output;
3535
private $statusCode;
36-
3736
/**
38-
* Constructor.
39-
*
40-
* @param Application $application An Application instance to test.
37+
* @var OutputInterface
4138
*/
39+
private $output;
40+
private $captureStreamsIndependently = false;
41+
4242
public function __construct(Application $application)
4343
{
4444
$this->application = $application;
@@ -49,9 +49,10 @@ public function __construct(Application $application)
4949
*
5050
* Available options:
5151
*
52-
* * interactive: Sets the input interactive flag
53-
* * decorated: Sets the output decorated flag
54-
* * verbosity: Sets the output verbosity flag
52+
* * interactive: Sets the input interactive flag
53+
* * decorated: Sets the output decorated flag
54+
* * verbosity: Sets the output verbosity flag
55+
* * capture_stderr_separately: Make output of stdOut and stdErr separately available
5556
*
5657
* @param array $input An array of arguments and options
5758
* @param array $options An array of options
@@ -65,12 +66,35 @@ public function run(array $input, $options = array())
6566
$this->input->setInteractive($options['interactive']);
6667
}
6768

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

76100
return $this->statusCode = $this->application->run($this->input, $this->output);
@@ -96,6 +120,30 @@ public function getDisplay($normalize = false)
96120
return $display;
97121
}
98122

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

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Console/Tests/ApplicationTest.php
+28-20Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -485,9 +485,14 @@ public function testSetCatchExceptions()
485485

486486
$application->setCatchExceptions(true);
487487
$this->assertTrue($application->areExceptionsCaught());
488+
488489
$tester->run(array('command' => 'foo'), array('decorated' => false));
489490
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception1.txt', $tester->getDisplay(true), '->setCatchExceptions() sets the catch exception flag');
490491

492+
$tester->run(array('command' => 'foo'), array('decorated' => false, 'capture_stderr_separately' => true));
493+
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception1.txt', $tester->getErrorOutput(true), '->setCatchExceptions() sets the catch exception flag');
494+
$this->assertSame('', $tester->getDisplay(true));
495+
491496
$application->setCatchExceptions(false);
492497
try {
493498
$tester->run(array('command' => 'foo'), array('decorated' => false));
@@ -516,19 +521,19 @@ public function testRenderException()
516521
->will($this->returnValue(120));
517522
$tester = new ApplicationTester($application);
518523

519-
$tester->run(array('command' => 'foo'), array('decorated' => false));
520-
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception1.txt', $tester->getDisplay(true), '->renderException() renders a pretty exception');
524+
$tester->run(array('command' => 'foo'), array('decorated' => false, 'capture_stderr_separately' => true));
525+
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception1.txt', $tester->getErrorOutput(true), '->renderException() renders a pretty exception');
521526

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

525-
$tester->run(array('command' => 'list', '--foo' => true), array('decorated' => false));
526-
$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');
530+
$tester->run(array('command' => 'list', '--foo' => true), array('decorated' => false, 'capture_stderr_separately' => true));
531+
$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');
527532

528533
$application->add(new \Foo3Command());
529534
$tester = new ApplicationTester($application);
530-
$tester->run(array('command' => 'foo3:bar'), array('decorated' => false));
531-
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception3.txt', $tester->getDisplay(true), '->renderException() renders a pretty exceptions with previous exceptions');
535+
$tester->run(array('command' => 'foo3:bar'), array('decorated' => false, 'capture_stderr_separately' => true));
536+
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception3.txt', $tester->getErrorOutput(true), '->renderException() renders a pretty exceptions with previous exceptions');
532537

533538
$tester->run(array('command' => 'foo3:bar'), array('decorated' => false, 'verbosity' => Output::VERBOSITY_VERBOSE));
534539
$this->assertRegExp('/\[Exception\]\s*First exception/', $tester->getDisplay(), '->renderException() renders a pretty exception without code exception when code exception is default and verbosity is verbose');
@@ -538,15 +543,18 @@ public function testRenderException()
538543
$tester->run(array('command' => 'foo3:bar'), array('decorated' => true));
539544
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception3decorated.txt', $tester->getDisplay(true), '->renderException() renders a pretty exceptions with previous exceptions');
540545

546+
$tester->run(array('command' => 'foo3:bar'), array('decorated' => true, 'capture_stderr_separately' => true));
547+
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception3decorated.txt', $tester->getErrorOutput(true), '->renderException() renders a pretty exceptions with previous exceptions');
548+
541549
$application = $this->getMock('Symfony\Component\Console\Application', array('getTerminalWidth'));
542550
$application->setAutoExit(false);
543551
$application->expects($this->any())
544552
->method('getTerminalWidth')
545553
->will($this->returnValue(32));
546554
$tester = new ApplicationTester($application);
547555

548-
$tester->run(array('command' => 'foo'), array('decorated' => false));
549-
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception4.txt', $tester->getDisplay(true), '->renderException() wraps messages when they are bigger than the terminal');
556+
$tester->run(array('command' => 'foo'), array('decorated' => false, 'capture_stderr_separately' => true));
557+
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception4.txt', $tester->getErrorOutput(true), '->renderException() wraps messages when they are bigger than the terminal');
550558
}
551559

552560
public function testRenderExceptionWithDoubleWidthCharacters()
@@ -561,11 +569,11 @@ public function testRenderExceptionWithDoubleWidthCharacters()
561569
});
562570
$tester = new ApplicationTester($application);
563571

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

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

570578
$application = $this->getMock('Symfony\Component\Console\Application', array('getTerminalWidth'));
571579
$application->setAutoExit(false);
@@ -576,8 +584,8 @@ public function testRenderExceptionWithDoubleWidthCharacters()
576584
throw new \Exception('コマンドの実行中にエラーが発生しました。');
577585
});
578586
$tester = new ApplicationTester($application);
579-
$tester->run(array('command' => 'foo'), array('decorated' => false));
580-
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_doublewidth2.txt', $tester->getDisplay(true), '->renderException() wraps messages when they are bigger than the terminal');
587+
$tester->run(array('command' => 'foo'), array('decorated' => false, 'capture_stderr_separately' => true));
588+
$this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_doublewidth2.txt', $tester->getErrorOutput(true), '->renderException() wraps messages when they are bigger than the terminal');
581589
}
582590

583591
public function testRun()
@@ -702,8 +710,8 @@ public function testRunReturnsIntegerExitCode()
702710
$application = $this->getMock('Symfony\Component\Console\Application', array('doRun'));
703711
$application->setAutoExit(false);
704712
$application->expects($this->once())
705-
->method('doRun')
706-
->will($this->throwException($exception));
713+
->method('doRun')
714+
->will($this->throwException($exception));
707715

708716
$exitCode = $application->run(new ArrayInput(array()), new NullOutput());
709717

@@ -717,8 +725,8 @@ public function testRunReturnsExitCodeOneForExceptionCodeZero()
717725
$application = $this->getMock('Symfony\Component\Console\Application', array('doRun'));
718726
$application->setAutoExit(false);
719727
$application->expects($this->once())
720-
->method('doRun')
721-
->will($this->throwException($exception));
728+
->method('doRun')
729+
->will($this->throwException($exception));
722730

723731
$exitCode = $application->run(new ArrayInput(array()), new NullOutput());
724732

0 commit comments

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