diff --git a/readme.md b/readme.md index 441047e4..62ea436b 100644 --- a/readme.md +++ b/readme.md @@ -223,7 +223,8 @@ Options: -s Show information about skipped tests. --stop-on-fail Stop execution upon the first failure. -j Run jobs in parallel (default: 8). - -o Specify output format. + -o + Specify output format. -w | --watch Watch directory. -i | --info Show tests environment info and exit. --setup Script for runner setup. diff --git a/src/Runner/CliTester.php b/src/Runner/CliTester.php index d1cfb5cd..68f5559c 100644 --- a/src/Runner/CliTester.php +++ b/src/Runner/CliTester.php @@ -112,7 +112,7 @@ private function loadOptions(): CommandLine -s Show information about skipped tests. --stop-on-fail Stop execution upon the first failure. -j Run jobs in parallel (default: 8). - -o (e.g. -o junit:output.xml) + -o (e.g. -o junit:output.xml) Specify one or more output formats with optional file name. -w | --watch Watch directory. -i | --info Show tests environment info and exit. @@ -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), diff --git a/src/Runner/Output/ConsolePrinter.php b/src/Runner/Output/ConsolePrinter.php index 772697e4..ae5f5b91 100644 --- a/src/Runner/Output/ConsolePrinter.php +++ b/src/Runner/Output/ConsolePrinter.php @@ -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')], + }; } @@ -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"; @@ -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, + ); + } } diff --git a/tests/RunnerOutput/OutputHandlers.expect.consoleLines.txt b/tests/RunnerOutput/OutputHandlers.expect.consoleLines.txt new file mode 100644 index 00000000..f30ab5d3 --- /dev/null +++ b/tests/RunnerOutput/OutputHandlers.expect.consoleLines.txt @@ -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) diff --git a/tests/RunnerOutput/OutputHandlers.phpt b/tests/RunnerOutput/OutputHandlers.phpt index c32a4cbb..a467aaa0 100644 --- a/tests/RunnerOutput/OutputHandlers.phpt +++ b/tests/RunnerOutput/OutputHandlers.phpt @@ -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));