From c6073511a2cafa8092b041f91a89c06c5c3360ab Mon Sep 17 00:00:00 2001 From: nuno maduro Date: Sun, 19 Jul 2026 13:59:31 +0100 Subject: [PATCH] chore: fixes test printer --- .../WrapperRunner/ProgressPrinterOutput.php | 71 +++++++++++++++++++ .../Subscriber/TestSkippedSubscriber.php | 16 +++-- src/Bootstrappers/BootOverrides.php | 1 + src/Logging/Converter.php | 43 ++++++----- .../Subscriber/TestSkippedSubscriber.php | 4 ++ .../Parallel/Paratest/ResultPrinter.php | 4 -- src/Support/StateGenerator.php | 40 +++++++++-- 7 files changed, 143 insertions(+), 36 deletions(-) create mode 100644 overrides/ParaTest/WrapperRunner/ProgressPrinterOutput.php diff --git a/overrides/ParaTest/WrapperRunner/ProgressPrinterOutput.php b/overrides/ParaTest/WrapperRunner/ProgressPrinterOutput.php new file mode 100644 index 00000000..a5c4183a --- /dev/null +++ b/overrides/ParaTest/WrapperRunner/ProgressPrinterOutput.php @@ -0,0 +1,71 @@ + $this->progressPrinter->print($buffer), + default => $this->outputPrinter->print($buffer), + }; + } + + public function flush(): void + { + $this->progressPrinter->flush(); + $this->outputPrinter->flush(); + } +} diff --git a/overrides/TextUI/Output/Default/ProgressPrinter/Subscriber/TestSkippedSubscriber.php b/overrides/TextUI/Output/Default/ProgressPrinter/Subscriber/TestSkippedSubscriber.php index 57c194a9..a2b3b754 100644 --- a/overrides/TextUI/Output/Default/ProgressPrinter/Subscriber/TestSkippedSubscriber.php +++ b/overrides/TextUI/Output/Default/ProgressPrinter/Subscriber/TestSkippedSubscriber.php @@ -43,7 +43,7 @@ declare(strict_types=1); * file that was distributed with this source code. */ -namespace Pest\Logging\TeamCity\Subscriber; +namespace PHPUnit\TextUI\Output\Default\ProgressPrinter; use PHPUnit\Event\Test\Skipped; use PHPUnit\Event\Test\SkippedSubscriber; @@ -51,16 +51,20 @@ use ReflectionClass; /** * @internal This class is not covered by the backward compatibility promise for PHPUnit + * + * This file is overridden so PHPUnit's progress output emits a "T" before the + * regular "S" for "todo" tests — Pest's parallel result printer consumes the + * "T" and swallows the "S" that follows it. */ -final class TestSkippedSubscriber extends Subscriber implements SkippedSubscriber +final readonly class TestSkippedSubscriber extends Subscriber implements SkippedSubscriber { public function notify(Skipped $event): void { - if (str_contains($event->message(), '__TODO__')) { + if ($event->message() === '__TODO__') { $this->printTodoItem(); } - $this->logger()->testSkipped($event); + $this->printer()->testSkipped(); } /** @@ -69,7 +73,7 @@ final class TestSkippedSubscriber extends Subscriber implements SkippedSubscribe private function printTodoItem(): void { $mirror = new ReflectionClass($this->printer()); - $printerMirror = $mirror->getMethod('printProgress'); - $printerMirror->invoke($this->printer(), 'T'); + $printProgress = $mirror->getMethod('printProgress'); + $printProgress->invoke($this->printer(), 'T'); } } diff --git a/src/Bootstrappers/BootOverrides.php b/src/Bootstrappers/BootOverrides.php index 4b67bf0a..0d7b5d7f 100644 --- a/src/Bootstrappers/BootOverrides.php +++ b/src/Bootstrappers/BootOverrides.php @@ -18,6 +18,7 @@ final class BootOverrides implements Bootstrapper * @var array */ public const array FILES = [ + 'ParaTest/WrapperRunner/ProgressPrinterOutput.php', 'Runner/Filter/NameFilterIterator.php', 'Runner/ResultCache/DefaultResultCache.php', 'Runner/TestSuiteLoader.php', diff --git a/src/Logging/Converter.php b/src/Logging/Converter.php index 1c8b38cc..88cdeb24 100644 --- a/src/Logging/Converter.php +++ b/src/Logging/Converter.php @@ -15,11 +15,6 @@ use PHPUnit\Event\Test\AfterLastTestMethodErrored; use PHPUnit\Event\Test\AfterLastTestMethodFailed; use PHPUnit\Event\Test\BeforeFirstTestMethodErrored; use PHPUnit\Event\Test\BeforeFirstTestMethodFailed; -use PHPUnit\Event\Test\ConsideredRisky; -use PHPUnit\Event\Test\Errored; -use PHPUnit\Event\Test\Failed; -use PHPUnit\Event\Test\MarkedIncomplete; -use PHPUnit\Event\Test\Skipped; use PHPUnit\Event\TestSuite\TestSuite; use PHPUnit\Event\TestSuite\TestSuiteForTestMethodWithDataProvider; use PHPUnit\Framework\Exception as FrameworkException; @@ -254,25 +249,29 @@ final readonly class Converter ...$result->testMarkedIncompleteEvents(), ]; - $numberOfNotPassedTests = count( - array_unique( - array_map( - function (AfterLastTestMethodErrored|AfterLastTestMethodFailed|BeforeFirstTestMethodErrored|BeforeFirstTestMethodFailed|Errored|Failed|Skipped|ConsideredRisky|MarkedIncomplete $event): string { - if ($event instanceof BeforeFirstTestMethodErrored - || $event instanceof AfterLastTestMethodErrored - || $event instanceof BeforeFirstTestMethodFailed - || $event instanceof AfterLastTestMethodFailed) { - return $event->testClassName(); - } + $notPassedTests = []; - return $this->getTestCaseLocation($event->test()); - }, - $events - ) - ) - ); + foreach ($events as $event) { + if ($event instanceof AfterLastTestMethodErrored) { + // PHPUnit's collector does not count these towards `numberOfTestsRun`... + continue; + } + if ($event instanceof AfterLastTestMethodFailed) { + // PHPUnit's collector does not count these towards `numberOfTestsRun`... + continue; + } + if ($event instanceof BeforeFirstTestMethodErrored || $event instanceof BeforeFirstTestMethodFailed) { + $notPassedTests[] = $event->testClassName(); - $numberOfPassedTests = $result->numberOfTestsRun() - $numberOfNotPassedTests; + continue; + } + + $notPassedTests[] = $this->getTestCaseLocation($event->test()); + } + + $numberOfPassedTests = $result->numberOfTestsRun() + - count(array_unique($notPassedTests)) + - $result->numberOfTestSkippedByTestSuiteSkippedEvents(); return $this->stateGenerator->fromPhpUnitTestResult($numberOfPassedTests, $result); } diff --git a/src/Logging/TeamCity/Subscriber/TestSkippedSubscriber.php b/src/Logging/TeamCity/Subscriber/TestSkippedSubscriber.php index 9e6b284b..1e70668f 100644 --- a/src/Logging/TeamCity/Subscriber/TestSkippedSubscriber.php +++ b/src/Logging/TeamCity/Subscriber/TestSkippedSubscriber.php @@ -14,6 +14,10 @@ final class TestSkippedSubscriber extends Subscriber implements SkippedSubscribe { public function notify(Skipped $event): void { + if ($event->message() === '__TODO__') { + return; // "todo" tests are reported in the summary, not as ignored tests... + } + $this->logger()->testSkipped($event); } } diff --git a/src/Plugins/Parallel/Paratest/ResultPrinter.php b/src/Plugins/Parallel/Paratest/ResultPrinter.php index 2041b44e..48fc5845 100644 --- a/src/Plugins/Parallel/Paratest/ResultPrinter.php +++ b/src/Plugins/Parallel/Paratest/ResultPrinter.php @@ -120,10 +120,6 @@ final class ResultPrinter $unexpectedOutput = $this->tail($outputFile); if ($unexpectedOutput !== '') { - if (preg_match('/^T+$/', $unexpectedOutput) > 0) { - return; - } - $this->output->write($unexpectedOutput); } diff --git a/src/Support/StateGenerator.php b/src/Support/StateGenerator.php index f9b32d60..6ed7ccbd 100644 --- a/src/Support/StateGenerator.php +++ b/src/Support/StateGenerator.php @@ -10,6 +10,10 @@ use NunoMaduro\Collision\Exceptions\TestOutcome; use PHPUnit\Event\Code\TestDoxBuilder; use PHPUnit\Event\Code\TestMethod; use PHPUnit\Event\Code\ThrowableBuilder; +use PHPUnit\Event\Test\AfterLastTestMethodErrored; +use PHPUnit\Event\Test\AfterLastTestMethodFailed; +use PHPUnit\Event\Test\BeforeFirstTestMethodErrored; +use PHPUnit\Event\Test\BeforeFirstTestMethodFailed; use PHPUnit\Event\Test\Errored; use PHPUnit\Event\Test\Failed; use PHPUnit\Event\Test\PhpunitDeprecationTriggered; @@ -35,8 +39,7 @@ final class StateGenerator $testResultEvent->throwable() )); } else { - // @phpstan-ignore-next-line - $state->add(TestResult::fromBeforeFirstTestMethodErrored($testResultEvent)); + $this->addClassLevelEvent($state, $testResultEvent); } } @@ -48,8 +51,7 @@ final class StateGenerator $testResultEvent->throwable() )); } else { - // @phpstan-ignore-next-line - $state->add(TestResult::fromBeforeFirstTestMethodErrored($testResultEvent)); + $this->addClassLevelEvent($state, $testResultEvent); } } @@ -184,6 +186,36 @@ final class StateGenerator return $state; } + /** + * Adds the given class-level "hook" failure to the state. Collision's + * `fromBeforeFirstTestMethodErrored` only accepts `BeforeFirstTestMethodErrored` + * events, so the remaining class-level events get a synthesized test method. + */ + private function addClassLevelEvent(State $state, AfterLastTestMethodErrored|AfterLastTestMethodFailed|BeforeFirstTestMethodErrored|BeforeFirstTestMethodFailed $event): void + { + if ($event instanceof BeforeFirstTestMethodErrored) { + $state->add(TestResult::fromBeforeFirstTestMethodErrored($event)); + + return; + } + + $methodName = $event instanceof BeforeFirstTestMethodFailed ? 'beforeAll' : 'afterAll'; + + $state->add(TestResult::fromPestParallelTestCase( + new TestMethod( + $event->testClassName(), // @phpstan-ignore-line + $methodName, + '', // @phpstan-ignore-line + 1, + TestDoxBuilder::fromClassNameAndMethodName($event->testClassName(), $methodName), // @phpstan-ignore-line + MetadataCollection::fromArray([]), + TestDataCollection::fromArray([]) + ), + TestResult::FAIL, + $event->throwable() + )); + } + /** * @param array> $testResultEvents */