Skip to content

Commit

Permalink
Implemented console-lines mode which prints each test on separate l…
Browse files Browse the repository at this point in the history
…ine. (#443)

This is handy for environments with non-standard (buffered) handling of
standard output, for example Github Actions (where a progress of tests
cannot be seen until until end-of-line appears, which in standard `console`
mode happens only when all tests finish) or Docker Compose logging output,
where in standard `console` mode each finished test's dot is printed
alone on separate line.

Or the console-lines mode can be handy just to see a more detailed
progress of tests in all environments, because it outputs something
like this:

```
· 1/85 Framework/Assert.contains.phpt OK in 0.14 s
· 2/85 CodeCoverage/PhpParser.parse.edge.phpt OK in 0.17 s
· 3/85 CodeCoverage/PhpParser.parse.lines-of-code.phpt SKIPPED in 0.18 s
· 4/85 CodeCoverage/PhpParser.parse.lines.phpt FAILED in 0.19 s
...
```

Also, "cider mode" now shows a lemon emoji for skipped tests.

Co-authored-by: David Grudl <david@grudl.com>
  • Loading branch information
smuuf and dg committed Jan 8, 2024
1 parent 1271f4c commit 5b2ff31
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 10 deletions.
3 changes: 2 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,8 @@ Options:
-s Show information about skipped tests.
--stop-on-fail Stop execution upon the first failure.
-j <num> Run <num> jobs in parallel (default: 8).
-o <console|tap|junit|none> Specify output format.
-o <console|console-lines|tap|junit|none>
Specify output format.
-w | --watch <path> Watch directory.
-i | --info Show tests environment info and exit.
--setup <path> Script for runner setup.
Expand Down
10 changes: 8 additions & 2 deletions src/Runner/CliTester.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ private function loadOptions(): CommandLine
-s Show information about skipped tests.
--stop-on-fail Stop execution upon the first failure.
-j <num> Run <num> jobs in parallel (default: 8).
-o <console|tap|junit|log|none> (e.g. -o junit:output.xml)
-o <console|console-lines|tap|junit|log|none> (e.g. -o junit:output.xml)
Specify one or more output formats with optional file name.
-w | --watch <path> Watch directory.
-i | --info Show tests environment info and exit.
Expand Down Expand Up @@ -230,7 +230,13 @@ private function createRunner(): Runner
foreach ($this->options['-o'] as $output) {
[$format, $file] = $output;
match ($format) {
'console' => $runner->outputHandlers[] = new Output\ConsolePrinter($runner, (bool) $this->options['-s'], $file, (bool) $this->options['--cider']),
'console', 'console-lines' => $runner->outputHandlers[] = new Output\ConsolePrinter(
$runner,
(bool) $this->options['-s'],
$file,
(bool) $this->options['--cider'],
$format === 'console-lines',
),
'tap' => $runner->outputHandlers[] = new Output\TapPrinter($file),
'junit' => $runner->outputHandlers[] = new Output\JUnitPrinter($file),
'log' => $runner->outputHandlers[] = new Output\Logger($runner, $file),
Expand Down
57 changes: 51 additions & 6 deletions src/Runner/Output/ConsolePrinter.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,16 @@ public function __construct(
bool $displaySkipped = false,
?string $file = null,
bool $ciderMode = false,
private bool $lineMode = false,
) {
$this->runner = $runner;
$this->displaySkipped = $displaySkipped;
$this->file = fopen($file ?: 'php://output', 'w');
$this->symbols = [
Test::Passed => $ciderMode ? Dumper::color('green', '🍎') : '.',
Test::Skipped => 's',
Test::Failed => $ciderMode ? Dumper::color('red', '🍎') : Dumper::color('white/red', 'F'),
];
$this->symbols = match (true) {
$ciderMode => [Test::Passed => '🍏', Test::Skipped => 's', Test::Failed => '🍎'],
$lineMode => [Test::Passed => Dumper::color('lime', 'OK'), Test::Skipped => Dumper::color('yellow', 'SKIP'), Test::Failed => Dumper::color('white/red', 'FAIL')],
default => [Test::Passed => '.', Test::Skipped => 's', Test::Failed => Dumper::color('white/red', 'F')],
};
}


Expand Down Expand Up @@ -94,7 +95,12 @@ public function prepare(Test $test): void
public function finish(Test $test): void
{
$this->results[$test->getResult()]++;
fwrite($this->file, $this->symbols[$test->getResult()]);
fwrite(
$this->file,
$this->lineMode
? $this->generateFinishLine($test)
: $this->symbols[$test->getResult()],
);

$title = ($test->title ? "$test->title | " : '') . substr($test->getSignature(), strlen($this->baseDir));
$message = ' ' . str_replace("\n", "\n ", trim((string) $test->message)) . "\n\n";
Expand All @@ -121,4 +127,43 @@ public function end(): void

$this->buffer = '';
}


private function generateFinishLine(Test $test): string
{
$result = $test->getResult();

$shortFilePath = str_replace($this->baseDir, '', $test->getFile());
$shortDirPath = dirname($shortFilePath) . DIRECTORY_SEPARATOR;
$basename = basename($shortFilePath);
$fileText = $result === Test::Failed
? Dumper::color('red', $shortDirPath) . Dumper::color('white/red', $basename)
: Dumper::color('gray', $shortDirPath) . Dumper::color('silver', $basename);

$header = '· ';
$titleText = $test->title
? Dumper::color('fuchsia', " [$test->title]")
: '';

// failed tests messages will be printed after all tests are finished
$message = '';
if ($result !== Test::Failed && $test->message) {
$indent = str_repeat(' ', mb_strlen($header));
$message = preg_match('#\n#', $test->message)
? "\n$indent" . preg_replace('#\r?\n#', '\0' . $indent, $test->message)
: Dumper::color('olive', "[$test->message]");
}

return sprintf(
"%s%s/%s %s%s %s %s %s\n",
$header,
array_sum($this->results),
$this->count,
$fileText,
$titleText,
$this->symbols[$result],
Dumper::color('gray', sprintf('in %.2f s', $test->getDuration())),
$message,
);
}
}
73 changes: 73 additions & 0 deletions tests/RunnerOutput/OutputHandlers.expect.consoleLines.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
%a% | %a% | 1 thread

· 1/11 .%ds%01-basic.fail.phptx FAIL in %f% s
· 2/11 .%ds%01-basic.pass.phptx OK in %f% s
· 3/11 .%ds%01-basic.skip.phptx SKIP in %f% s
· 4/11 .%ds%02-title.fail.phptx [Title for output handlers] FAIL in %f% s
· 5/11 .%ds%02-title.pass.phptx [Title for output handlers] OK in %f% s
· 6/11 .%ds%02-title.skip.phptx [Title for output handlers] SKIP in %f% s
· 7/11 .%ds%03-message.fail.phptx FAIL in %f% s
· 8/11 .%ds%03-message.skip.phptx SKIP in %f% s
Multi
line
message.
· 9/11 .%ds%04-args.fail.phptx FAIL in %f% s
· 10/11 .%ds%04-args.pass.phptx OK in %f% s
· 11/11 .%ds%04-args.skip.phptx SKIP in %f% s
Multi
line
message.


-- FAILED: 01-basic.fail.phptx
Multi
line
stdout.Failed:

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

STDERR:
Multi
line
stderr.

-- FAILED: Title for output handlers | 02-title.fail.phptx
Multi
line
stdout.Failed:

in %a%02-title.fail.phptx(%d%) Tester\Assert::fail('');

STDERR:
Multi
line
stderr.

-- FAILED: 03-message.fail.phptx
Multi
line
stdout.Failed: Multi
line
message.

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

STDERR:
Multi
line
stderr.

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

in %a%04-args.fail.phptx(%d%) Tester\Assert::fail('');

STDERR:
Multi
line
stderr.


FAILURES! (11 tests, 4 failures, 4 skipped, %a% seconds)
3 changes: 2 additions & 1 deletion tests/RunnerOutput/OutputHandlers.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@ $runner->setEnvironmentVariable(Tester\Environment::VariableColors, '0');
$runner->paths[] = __DIR__ . '/cases/*.phptx';
$runner->outputHandlers[] = new Output\ConsolePrinter($runner, false, $console = FileMock::create(''));
$runner->outputHandlers[] = new Output\ConsolePrinter($runner, true, $consoleWithSkipped = FileMock::create(''));
$runner->outputHandlers[] = new Output\ConsolePrinter($runner, false, $consoleLines = FileMock::create(''), false, true);
$runner->outputHandlers[] = new Output\JUnitPrinter($jUnit = FileMock::create(''));
$runner->outputHandlers[] = new Output\Logger($runner, $logger = FileMock::create(''));
$runner->outputHandlers[] = new Output\TapPrinter($tap = FileMock::create(''));
$runner->run();


Assert::matchFile(__DIR__ . '/OutputHandlers.expect.console.txt', Dumper::removeColors(file_get_contents($console)));
Assert::matchFile(__DIR__ . '/OutputHandlers.expect.consoleWithSkip.txt', Dumper::removeColors(file_get_contents($consoleWithSkipped)));
Assert::matchFile(__DIR__ . '/OutputHandlers.expect.consoleLines.txt', Dumper::removeColors(file_get_contents($consoleLines)));
Assert::matchFile(__DIR__ . '/OutputHandlers.expect.jUnit.xml', file_get_contents($jUnit));
Assert::matchFile(__DIR__ . '/OutputHandlers.expect.logger.txt', file_get_contents($logger));
Assert::matchFile(__DIR__ . '/OutputHandlers.expect.tap.txt', file_get_contents($tap));

0 comments on commit 5b2ff31

Please sign in to comment.