From 951b54e7cd68a616e2f43013a5b338828dc3f7a6 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Mon, 6 Feb 2023 19:56:35 +0000 Subject: [PATCH] Uses `ResultPrinter` --- .../Parallel/Paratest/ResultPrinter.php | 282 ++++++++++++++++++ .../Parallel/Paratest/WrapperRunner.php | 2 +- 2 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 src/Plugins/Parallel/Paratest/ResultPrinter.php diff --git a/src/Plugins/Parallel/Paratest/ResultPrinter.php b/src/Plugins/Parallel/Paratest/ResultPrinter.php new file mode 100644 index 00000000..5fd0cdb2 --- /dev/null +++ b/src/Plugins/Parallel/Paratest/ResultPrinter.php @@ -0,0 +1,282 @@ + */ + private array $tailPositions; + + public function __construct( + private readonly OutputInterface $output, + private readonly Options $options + ) { + $this->printer = new class ($this->output) implements Printer { + public function __construct( + private readonly OutputInterface $output, + ) { + } + + public function print(string $buffer): void + { + $this->output->write(OutputFormatter::escape($buffer)); + } + + public function flush(): void + { + } + }; + + if (! $this->options->configuration->hasLogfileTeamcity()) { + return; + } + + $teamcityLogFileHandle = fopen($this->options->configuration->logfileTeamcity(), 'ab+'); + assert($teamcityLogFileHandle !== false); + $this->teamcityLogFileHandle = $teamcityLogFileHandle; + } + + public function setTestCount(int $testCount): void + { + $this->totalCases = $testCount; + } + + public function start(): void + { + $this->numTestsWidth = strlen((string) $this->totalCases); + $this->maxColumn = $this->numberOfColumns + + (DIRECTORY_SEPARATOR === '\\' ? -1 : 0) // fix windows blank lines + - strlen($this->getProgress()); + + // @see \PHPUnit\TextUI\TestRunner::writeMessage() + $output = $this->output; + $write = static function (string $type, string $message) use ($output): void { + $output->write(sprintf("%-15s%s\n", $type . ':', $message)); + }; + + // @see \PHPUnit\TextUI\Application::writeRuntimeInformation() + $write('Processes', (string) $this->options->processes); + } + + /** @param list $teamcityFiles */ + public function printFeedback(SplFileInfo $progressFile, array $teamcityFiles): void + { + if ($this->options->needsTeamcity) { + $teamcityProgress = $this->tailMultiple($teamcityFiles); + + if ($this->teamcityLogFileHandle !== null) { + fwrite($this->teamcityLogFileHandle, $teamcityProgress); + } + } + + if ($this->options->configuration->outputIsTeamCity()) { + assert(isset($teamcityProgress)); + $this->output->write($teamcityProgress); + + return; + } + + if ($this->options->configuration->noProgress()) { + return; + } + + $feedbackItems = $this->tail($progressFile); + if ($feedbackItems === '') { + return; + } + + $feedbackItems = preg_replace('/ +\\d+ \\/ \\d+ \\( ?\\d+%\\)\\s*/', '', $feedbackItems); + + $actualTestCount = strlen($feedbackItems); + for ($index = 0; $index < $actualTestCount; ++$index) { + $this->printFeedbackItem($feedbackItems[$index]); + } + } + + /** + * @param list $teamcityFiles + * @param list $testdoxFiles + */ + public function printResults(TestResult $testResult, array $teamcityFiles, array $testdoxFiles): void + { + if ($this->options->needsTeamcity) { + $teamcityProgress = $this->tailMultiple($teamcityFiles); + + if ($this->teamcityLogFileHandle !== null) { + fwrite($this->teamcityLogFileHandle, $teamcityProgress); + $resource = $this->teamcityLogFileHandle; + $this->teamcityLogFileHandle = null; + fclose($resource); + } + } + + if ($this->options->configuration->outputIsTeamCity()) { + assert(isset($teamcityProgress)); + $this->output->write($teamcityProgress); + + return; + } + + if ($this->options->configuration->outputIsTestDox()) { + $this->output->write($this->tailMultiple($testdoxFiles)); + + return; + } + + $resultPrinter = new DefaultResultPrinter( + $this->printer, + $this->options->configuration->displayDetailsOnIncompleteTests(), + $this->options->configuration->displayDetailsOnSkippedTests(), + $this->options->configuration->displayDetailsOnTestsThatTriggerDeprecations(), + $this->options->configuration->displayDetailsOnTestsThatTriggerErrors(), + $this->options->configuration->displayDetailsOnTestsThatTriggerNotices(), + $this->options->configuration->displayDetailsOnTestsThatTriggerWarnings(), + false, + ); + $summaryPrinter = new SummaryPrinter( + $this->printer, + $this->options->configuration->colors(), + ); + + $this->printer->print(PHP_EOL . (new ResourceUsageFormatter())->resourceUsageSinceStartOfRequest() . PHP_EOL . PHP_EOL); + + $resultPrinter->print($testResult); + $summaryPrinter->print($testResult); + } + + private function printFeedbackItem(string $item): void + { + $this->printFeedbackItemColor($item); + ++$this->column; + ++$this->casesProcessed; + if ($this->column !== $this->maxColumn && $this->casesProcessed < $this->totalCases) { + return; + } + + if ( + $this->casesProcessed > 0 + && $this->casesProcessed === $this->totalCases + && ($pad = $this->maxColumn - $this->column) > 0 + ) { + $this->output->write(str_repeat(' ', $pad)); + } + + $this->output->write($this->getProgress() . "\n"); + $this->column = 0; + } + + private function printFeedbackItemColor(string $item): void + { + $buffer = match ($item) { + 'E' => $this->colorizeTextBox('fg-red, bold', $item), + 'F' => $this->colorizeTextBox('bg-red, fg-white', $item), + 'I', 'N', 'D', 'R', 'W' => $this->colorizeTextBox('fg-yellow, bold', $item), + 'S' => $this->colorizeTextBox('fg-cyan, bold', $item), + default => $item, + }; + + $this->output->write($buffer); + } + + private function getProgress(): string + { + return sprintf( + ' %' . $this->numTestsWidth . 'd / %' . $this->numTestsWidth . 'd (%3s%%)', + $this->casesProcessed, + $this->totalCases, + floor(($this->totalCases > 0 ? $this->casesProcessed / $this->totalCases : 0) * 100), + ); + } + + private function colorizeTextBox(string $color, string $buffer): string + { + if (! $this->options->configuration->colors()) { + return $buffer; + } + + return Color::colorizeTextBox($color, $buffer); + } + + /** @param list $files */ + private function tailMultiple(array $files): string + { + $content = ''; + foreach ($files as $file) { + if (! $file->isFile()) { + continue; + } + + $content .= $this->tail($file); + } + + return $content; + } + + private function tail(SplFileInfo $file): string + { + $path = $file->getPathname(); + $handle = fopen($path, 'r'); + assert($handle !== false); + $fseek = fseek($handle, $this->tailPositions[$path] ?? 0); + assert($fseek === 0); + + $contents = ''; + while (! feof($handle)) { + $fread = fread($handle, 8192); + assert($fread !== false); + $contents .= $fread; + } + + $ftell = ftell($handle); + assert($ftell !== false); + $this->tailPositions[$path] = $ftell; + fclose($handle); + + return $contents; + } +} diff --git a/src/Plugins/Parallel/Paratest/WrapperRunner.php b/src/Plugins/Parallel/Paratest/WrapperRunner.php index 2ce4a13b..4ea37787 100644 --- a/src/Plugins/Parallel/Paratest/WrapperRunner.php +++ b/src/Plugins/Parallel/Paratest/WrapperRunner.php @@ -9,7 +9,6 @@ use ParaTest\JUnit\LogMerger; use ParaTest\JUnit\Writer; use ParaTest\Options; use ParaTest\RunnerInterface; -use ParaTest\WrapperRunner\ResultPrinter; use ParaTest\WrapperRunner\SuiteLoader; use ParaTest\WrapperRunner\WrapperWorker; use Pest\TestSuite; @@ -75,6 +74,7 @@ final class WrapperRunner implements RunnerInterface $wrapper = realpath( dirname(__DIR__, 4) . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'pest-wrapper.php', ); + assert($wrapper !== false); $phpFinder = new PhpExecutableFinder(); $phpBin = $phpFinder->find(false);