Skip to content

Commit 317713e

Browse files
MartinMystikJonasdg
authored andcommitted
Capture stderr of test via temp file and output it in test results [Closes #420] (#438)
1 parent 795f0a9 commit 317713e

25 files changed

+194
-54
lines changed

src/Runner/CliTester.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public function run(): ?int
5050

5151
if ($this->options['--info']) {
5252
$job = new Job(new Test(__DIR__ . '/info.php'), $this->interpreter);
53+
$job->setTempDirectory($this->options['--temp']);
5354
$job->run();
5455
echo $job->getTest()->stdout;
5556
return null;

src/Runner/Job.php

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@ class Job
2626

2727
/** waiting time between process activity check in microseconds */
2828
public const RunSleep = 10000;
29-
public const
30-
RunAsync = 1,
31-
RunCollectErrors = 2;
29+
public const RunAsync = 1;
3230

3331
private Test $test;
3432
private PhpInterpreter $interpreter;
@@ -41,9 +39,7 @@ class Job
4139

4240
/** @var resource|null */
4341
private $stdout;
44-
45-
/** @var resource|null */
46-
private $stderr;
42+
private ?string $stderrFile;
4743
private int $exitCode = self::CodeNone;
4844

4945
/** @var string[] output headers */
@@ -66,6 +62,14 @@ public function __construct(Test $test, PhpInterpreter $interpreter, ?array $env
6662
}
6763

6864

65+
public function setTempDirectory(?string $path): void
66+
{
67+
$this->stderrFile = $path === null
68+
? null
69+
: $path . DIRECTORY_SEPARATOR . 'Job.pid-' . getmypid() . '.' . uniqid() . '.stderr';
70+
}
71+
72+
6973
public function setEnvironmentVariable(string $name, string $value): void
7074
{
7175
$this->envVars[$name] = $value;
@@ -80,7 +84,7 @@ public function getEnvironmentVariable(string $name): string
8084

8185
/**
8286
* Runs single test.
83-
* @param int $flags self::RUN_ASYNC | self::RUN_COLLECT_ERRORS
87+
* @param int $flags self::RUN_ASYNC
8488
*/
8589
public function run(int $flags = 0): void
8690
{
@@ -102,7 +106,7 @@ public function run(int $flags = 0): void
102106
[
103107
['pipe', 'r'],
104108
['pipe', 'w'],
105-
['pipe', 'w'],
109+
$this->stderrFile ? ['file', $this->stderrFile, 'w'] : ['pipe', 'w'],
106110
],
107111
$pipes,
108112
dirname($this->test->getFile()),
@@ -114,19 +118,15 @@ public function run(int $flags = 0): void
114118
putenv($name);
115119
}
116120

117-
[$stdin, $this->stdout, $stderr] = $pipes;
121+
[$stdin, $this->stdout] = $pipes;
118122
fclose($stdin);
119-
if ($flags & self::RunCollectErrors) {
120-
$this->stderr = $stderr;
121-
} else {
122-
fclose($stderr);
123+
124+
if (isset($pipes[2])) {
125+
fclose($pipes[2]);
123126
}
124127

125128
if ($flags & self::RunAsync) {
126129
stream_set_blocking($this->stdout, false); // on Windows does not work with proc_open()
127-
if ($this->stderr) {
128-
stream_set_blocking($this->stderr, false);
129-
}
130130
} else {
131131
while ($this->isRunning()) {
132132
usleep(self::RunSleep); // stream_select() doesn't work with proc_open()
@@ -145,9 +145,6 @@ public function isRunning(): bool
145145
}
146146

147147
$this->test->stdout .= stream_get_contents($this->stdout);
148-
if ($this->stderr) {
149-
$this->test->stderr .= stream_get_contents($this->stderr);
150-
}
151148

152149
$status = proc_get_status($this->proc);
153150
if ($status['running']) {
@@ -157,8 +154,9 @@ public function isRunning(): bool
157154
$this->duration += microtime(true);
158155

159156
fclose($this->stdout);
160-
if ($this->stderr) {
161-
fclose($this->stderr);
157+
if ($this->stderrFile) {
158+
$this->test->stderr .= file_get_contents($this->stderrFile);
159+
unlink($this->stderrFile);
162160
}
163161

164162
$code = proc_close($this->proc);

src/Runner/Output/ConsolePrinter.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ public function finish(Test $test): void
9898

9999
$title = ($test->title ? "$test->title | " : '') . substr($test->getSignature(), strlen($this->baseDir));
100100
$message = ' ' . str_replace("\n", "\n ", trim((string) $test->message)) . "\n\n";
101+
$message = preg_replace('/^ $/m', '', $message);
101102
if ($test->getResult() === Test::Failed) {
102103
$this->buffer .= Dumper::color('red', "-- FAILED: $title") . "\n$message";
103104
} elseif ($test->getResult() === Test::Skipped && $this->displaySkipped) {

src/Runner/Test.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,15 @@ public function getDuration(): ?float
9090
}
9191

9292

93+
/**
94+
* Full output (stdout + stderr)
95+
*/
96+
public function getOutput(): string
97+
{
98+
return $this->stdout . ($this->stderr ? "\nSTDERR:\n" . $this->stderr : '');
99+
}
100+
101+
93102
/**
94103
* @return static
95104
*/

src/Runner/TestHandler.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,9 @@ public function initiate(string $file): void
7474

7575
foreach ($tests as $test) {
7676
$this->runner->prepareTest($test);
77-
$this->runner->addJob(new Job($test, $php, $this->runner->getEnvironmentVariables()));
77+
$job = new Job($test, $php, $this->runner->getEnvironmentVariables());
78+
$job->setTempDirectory($this->tempDir);
79+
$this->runner->addJob($job);
7880
}
7981
}
8082

@@ -194,10 +196,11 @@ private function initiateTestCase(Test $test, $foo, PhpInterpreter $interpreter)
194196

195197
if ($methods === null) {
196198
$job = new Job($test->withArguments(['method' => TestCase::ListMethods]), $interpreter, $this->runner->getEnvironmentVariables());
199+
$job->setTempDirectory($this->tempDir);
197200
$job->run();
198201

199202
if (in_array($job->getExitCode(), [Job::CodeError, Job::CodeFail, Job::CodeSkip], true)) {
200-
return $test->withResult($job->getExitCode() === Job::CodeSkip ? Test::Skipped : Test::Failed, $job->getTest()->stdout);
203+
return $test->withResult($job->getExitCode() === Job::CodeSkip ? Test::Skipped : Test::Failed, $job->getTest()->getOutput());
201204
}
202205

203206
$stdout = $job->getTest()->stdout;
@@ -244,7 +247,7 @@ private function assessExitCode(Job $job, string|int $code): ?Test
244247
$message = $job->getExitCode() !== Job::CodeFail
245248
? "Exited with error code {$job->getExitCode()} (expected $code)"
246249
: '';
247-
return $job->getTest()->withResult(Test::Failed, trim($message . "\n" . $job->getTest()->stdout));
250+
return $job->getTest()->withResult(Test::Failed, trim($message . "\n" . $job->getTest()->getOutput()));
248251
}
249252

250253
return null;

tests/Runner/Job.phpt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ require __DIR__ . '/../bootstrap.php';
1313
test(function () {
1414
$test = (new Test('Job.test.phptx'))->withArguments(['one', 'two' => 1])->withArguments(['three', 'two' => 2]);
1515
$job = new Job($test, createInterpreter());
16-
$job->run($job::RunCollectErrors);
16+
$job->setTempDirectory(Tester\Helpers::prepareTempDir(sys_get_temp_dir()));
17+
$job->run();
1718

1819
Assert::false($job->isRunning());
1920
Assert::same($test, $job->getTest());

tests/Runner/Runner.multiple-fails.phpt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ $interpreter = createInterpreter()
4747
$runner = new Runner($interpreter);
4848
$runner->paths[] = __DIR__ . '/multiple-fails/*.phptx';
4949
$runner->outputHandlers[] = $logger = new Logger;
50+
$runner->setTempDirectory(Tester\Helpers::prepareTempDir(sys_get_temp_dir()));
5051
$runner->run();
5152

5253
Assert::match(
@@ -86,8 +87,8 @@ Assert::same(Test::Failed, $logger->results['testcase-pre-fail.phptx'][0]);
8687

8788
Assert::match(
8889
defined('PHPDBG_VERSION')
89-
? '%A%Parse error: %a% in %a%testcase-syntax-error.phptx on line %d%'
90-
: 'Parse error: %a% in %a%testcase-syntax-error.phptx on line %d%',
90+
? '%A%Parse error: %a% in %a%testcase-syntax-error.phptx on line %d%%A?%'
91+
: 'Parse error: %a% in %a%testcase-syntax-error.phptx on line %d%%A?%',
9192
trim($logger->results['testcase-syntax-error.phptx'][1])
9293
);
9394
Assert::same(Test::Failed, $logger->results['testcase-syntax-error.phptx'][0]);

tests/Runner/Test.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ test(function () {
2020
Assert::null($test->message);
2121
Assert::same('', $test->stdout);
2222
Assert::same('', $test->stderr);
23+
Assert::same('', $test->getOutput());
2324
Assert::same('some/Test.phpt', $test->getFile());
2425
Assert::same([], $test->getArguments());
2526
Assert::same('some/Test.phpt', $test->getSignature());

tests/RunnerOutput/OutputHandlers.expect.console.txt

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,24 @@ F.sF.sFsF.s
77
line
88
stdout.Failed:
99

10-
in %a%01-basic.fail.phptx(7) Tester\Assert::fail('');
10+
in %a%01-basic.fail.phptx(%d%) Tester\Assert::fail('');
11+
12+
STDERR:
13+
Multi
14+
line
15+
stderr.
1116

1217
-- FAILED: Title for output handlers | 02-title.fail.phptx
1318
Multi
1419
line
1520
stdout.Failed:
1621

17-
in %a%02-title.fail.phptx(11) Tester\Assert::fail('');
22+
in %a%02-title.fail.phptx(%d%) Tester\Assert::fail('');
23+
24+
STDERR:
25+
Multi
26+
line
27+
stderr.
1828

1929
-- FAILED: 03-message.fail.phptx
2030
Multi
@@ -23,14 +33,24 @@ F.sF.sFsF.s
2333
line
2434
message.
2535

26-
in %a%03-message.fail.phptx(7) Tester\Assert::fail("Multi\nline\nmessage.");
36+
in %a%03-message.fail.phptx(%d%) Tester\Assert::fail("Multi\nline\nmessage.");
37+
38+
STDERR:
39+
Multi
40+
line
41+
stderr.
2742

2843
-- FAILED: 04-args.fail.phptx dataprovider=thisIsAVeryVeryVeryLongArgumentNameToTestHowOutputHandlersDealWithItsLengthInTheirOutputFormatting|%a%provider.ini
2944
Multi
3045
line
3146
stdout.Failed:
3247

33-
in %a%04-args.fail.phptx(11) Tester\Assert::fail('');
48+
in %a%04-args.fail.phptx(%d%) Tester\Assert::fail('');
49+
50+
STDERR:
51+
Multi
52+
line
53+
stderr.
3454

3555

3656
FAILURES! (11 tests, 4 failures, 4 skipped, %a% seconds)

tests/RunnerOutput/OutputHandlers.expect.consoleWithSkip.txt

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,43 +7,63 @@ F.sF.sFsF.s
77
line
88
stdout.Failed:
99

10-
in %a%01-basic.fail.phptx(7) Tester\Assert::fail('');
10+
in %a%01-basic.fail.phptx(%d%) Tester\Assert::fail('');
1111

12-
-- Skipped: %a?%01-basic.skip.phptx
12+
STDERR:
13+
Multi
14+
line
15+
stderr.
16+
17+
-- Skipped: 01-basic.skip.phptx
1318

1419

15-
-- FAILED: Title for output handlers | 02-title.fail.phptx
20+
-- FAILED: Title for output handlers | %a?%02-title.fail.phptx
1621
Multi
1722
line
1823
stdout.Failed:
1924

20-
in %a%02-title.fail.phptx(11) Tester\Assert::fail('');
25+
in %a%02-title.fail.phptx(%d%) Tester\Assert::fail('');
26+
27+
STDERR:
28+
Multi
29+
line
30+
stderr.
2131

22-
-- Skipped: Title for output handlers | 02-title.skip.phptx
32+
-- Skipped: Title for output handlers | %a?%02-title.skip.phptx
2333

2434

25-
-- FAILED: 03-message.fail.phptx
35+
-- FAILED: %a?%03-message.fail.phptx
2636
Multi
2737
line
2838
stdout.Failed: Multi
2939
line
3040
message.
3141

32-
in %a%03-message.fail.phptx(7) Tester\Assert::fail("Multi\nline\nmessage.");
42+
in %a%03-message.fail.phptx(%d%) Tester\Assert::fail("Multi\nline\nmessage.");
3343

34-
-- Skipped: 03-message.skip.phptx
44+
STDERR:
45+
Multi
46+
line
47+
stderr.
48+
49+
-- Skipped: %a?%03-message.skip.phptx
3550
Multi
3651
line
3752
message.
3853

39-
-- FAILED: 04-args.fail.phptx dataprovider=thisIsAVeryVeryVeryLongArgumentNameToTestHowOutputHandlersDealWithItsLengthInTheirOutputFormatting|%a%provider.ini
54+
-- FAILED: %a?%04-args.fail.phptx dataprovider=thisIsAVeryVeryVeryLongArgumentNameToTestHowOutputHandlersDealWithItsLengthInTheirOutputFormatting|%a%provider.ini
4055
Multi
4156
line
4257
stdout.Failed:
4358

44-
in %a%04-args.fail.phptx(11) Tester\Assert::fail('');
59+
in %a%04-args.fail.phptx(%d%) Tester\Assert::fail('');
60+
61+
STDERR:
62+
Multi
63+
line
64+
stderr.
4565

46-
-- Skipped: 04-args.skip.phptx dataprovider=thisIsAVeryVeryVeryLongArgumentNameToTestHowOutputHandlersDealWithItsLengthInTheirOutputFormatting|%a%provider.ini
66+
-- Skipped: %a?%04-args.skip.phptx dataprovider=thisIsAVeryVeryVeryLongArgumentNameToTestHowOutputHandlersDealWithItsLengthInTheirOutputFormatting|%a%provider.ini
4767
Multi
4868
line
4969
message.

0 commit comments

Comments
 (0)