From 2cbecd10e6d2b06249d94a9333d1d5ae1dfa975f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20=C5=BBmuda?= Date: Mon, 23 Oct 2023 11:23:53 +0200 Subject: [PATCH 01/16] Fix typo in toHaveProperties() PHPDoc block --- src/Mixins/Expectation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mixins/Expectation.php b/src/Mixins/Expectation.php index a6910b649..358892c74 100644 --- a/src/Mixins/Expectation.php +++ b/src/Mixins/Expectation.php @@ -314,7 +314,7 @@ public function toHaveProperty(string $name, mixed $value = new Any(), string $m /** * Asserts that the value contains the provided properties $names. * - * @param iterable $names + * @param iterable $names * @return self */ public function toHaveProperties(iterable $names, string $message = ''): self From 8be46b57a0b126a1f2127a44399d106e72880639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Borys=20=C5=BBmuda?= Date: Fri, 24 Nov 2023 09:16:13 +0100 Subject: [PATCH 02/16] Update `toHaveProperties()` $names param --- src/Mixins/Expectation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mixins/Expectation.php b/src/Mixins/Expectation.php index 358892c74..b17a796b1 100644 --- a/src/Mixins/Expectation.php +++ b/src/Mixins/Expectation.php @@ -314,7 +314,7 @@ public function toHaveProperty(string $name, mixed $value = new Any(), string $m /** * Asserts that the value contains the provided properties $names. * - * @param iterable $names + * @param iterable|iterable $names * @return self */ public function toHaveProperties(iterable $names, string $message = ''): self From e5dc6f0ae2dfe468960c3a1913b7ac17f990a4bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20N=C3=BCrnberger?= Date: Sun, 30 Jul 2023 08:35:00 +0200 Subject: [PATCH 03/16] junit support --- src/Bootstrappers/BootSubscribers.php | 1 + src/Logging/JUnit/Converter.php | 228 ++++++++++ src/Logging/JUnit/JUnitLogger.php | 398 ++++++++++++++++++ src/Logging/JUnit/Subscriber/Subscriber.php | 28 ++ .../Subscriber/TestErroredSubscriber.php | 19 + .../JUnit/Subscriber/TestFailedSubscriber.php | 19 + .../Subscriber/TestFinishedSubscriber.php | 19 + .../TestMarkedIncompleteSubscriber.php | 19 + .../Subscriber/TestPreparedSubscriber.php | 19 + .../TestRunnerExecutionFinishedSubscriber.php | 19 + .../Subscriber/TestSkippedSubscriber.php | 19 + .../TestSuiteFinishedSubscriber.php | 19 + .../Subscriber/TestSuiteStartedSubscriber.php | 19 + src/Subscribers/EnsureJunitEnabled.php | 48 +++ 14 files changed, 874 insertions(+) create mode 100644 src/Logging/JUnit/Converter.php create mode 100644 src/Logging/JUnit/JUnitLogger.php create mode 100644 src/Logging/JUnit/Subscriber/Subscriber.php create mode 100644 src/Logging/JUnit/Subscriber/TestErroredSubscriber.php create mode 100644 src/Logging/JUnit/Subscriber/TestFailedSubscriber.php create mode 100644 src/Logging/JUnit/Subscriber/TestFinishedSubscriber.php create mode 100644 src/Logging/JUnit/Subscriber/TestMarkedIncompleteSubscriber.php create mode 100644 src/Logging/JUnit/Subscriber/TestPreparedSubscriber.php create mode 100644 src/Logging/JUnit/Subscriber/TestRunnerExecutionFinishedSubscriber.php create mode 100644 src/Logging/JUnit/Subscriber/TestSkippedSubscriber.php create mode 100644 src/Logging/JUnit/Subscriber/TestSuiteFinishedSubscriber.php create mode 100644 src/Logging/JUnit/Subscriber/TestSuiteStartedSubscriber.php create mode 100644 src/Subscribers/EnsureJunitEnabled.php diff --git a/src/Bootstrappers/BootSubscribers.php b/src/Bootstrappers/BootSubscribers.php index 248b9ddee..ad4ab7da8 100644 --- a/src/Bootstrappers/BootSubscribers.php +++ b/src/Bootstrappers/BootSubscribers.php @@ -25,6 +25,7 @@ final class BootSubscribers implements Bootstrapper Subscribers\EnsureIgnorableTestCasesAreIgnored::class, Subscribers\EnsureKernelDumpIsFlushed::class, Subscribers\EnsureTeamCityEnabled::class, + Subscribers\EnsureJunitEnabled::class, ]; /** diff --git a/src/Logging/JUnit/Converter.php b/src/Logging/JUnit/Converter.php new file mode 100644 index 000000000..f417a59e2 --- /dev/null +++ b/src/Logging/JUnit/Converter.php @@ -0,0 +1,228 @@ +stateGenerator = new StateGenerator(); + } + + /** + * Gets the test case method name. + */ + public function getTestCaseMethodName(Test $test): string + { + if (! $test instanceof TestMethod) { + throw ShouldNotHappen::fromMessage('Not an instance of TestMethod'); + } + + return $test->testDox()->prettifiedMethodName(); + } + + /** + * Gets the test case location. + */ + public function getTestCaseLocation(Test $test, bool $withDescription = false): string + { + if (! $test instanceof TestMethod) { + throw ShouldNotHappen::fromMessage('Not an instance of TestMethod'); + } + + $path = $test->testDox()->prettifiedClassName(); + $relativePath = $this->toRelativePath($path); + + // TODO: Get the description without the dataset. + $description = $test->testDox()->prettifiedMethodName(); + + if (! $withDescription) { + return $relativePath; + } + + return "$relativePath::$description"; + } + + /** + * Gets the exception message. + */ + public function getExceptionMessage(Throwable $throwable): string + { + if (is_a($throwable->className(), FrameworkException::class, true)) { + return $throwable->message(); + } + + $buffer = $throwable->className(); + $throwableMessage = $throwable->message(); + + if ($throwableMessage !== '') { + $buffer .= ": $throwableMessage"; + } + + return $buffer; + } + + /** + * Gets the exception details. + */ + public function getExceptionDetails(Throwable $throwable): string + { + $buffer = $this->getStackTrace($throwable); + + while ($throwable->hasPrevious()) { + $throwable = $throwable->previous(); + + $buffer .= sprintf( + "\nCaused by\n%s\n%s", + $throwable->description(), + $this->getStackTrace($throwable) + ); + } + + return $buffer; + } + + /** + * Gets the stack trace. + */ + public function getStackTrace(Throwable $throwable): string + { + $stackTrace = $throwable->stackTrace(); + + // Split stacktrace per frame. + $frames = explode("\n", $stackTrace); + + // Remove empty lines + $frames = array_filter($frames); + + // clean the paths of each frame. + $frames = array_map( + fn (string $frame): string => $this->toRelativePath($frame), + $frames + ); + + // Format stacktrace as `at ` + $frames = array_map( + fn (string $frame) => "at $frame", + $frames + ); + + return implode("\n", $frames); + } + + /** + * Gets the test suite name. + */ + public function getTestSuiteName(TestSuite $testSuite): string + { + $name = $testSuite->name(); + + if (str_starts_with($name, self::PREFIX)) { + return Str::after($name, self::PREFIX); + } + + return Str::after($name, $this->rootPath); + } + + /** + * Gets the test suite location. + */ + public function getTestSuiteLocation(TestSuite $testSuite): ?string + { + $tests = $testSuite->tests()->asArray(); + + // TODO: figure out how to get the file path without a test being there. + if ($tests === []) { + return null; + } + + $firstTest = $tests[0]; + if (! $firstTest instanceof TestMethod) { + throw ShouldNotHappen::fromMessage('Not an instance of TestMethod'); + } + + $path = $firstTest->testDox()->prettifiedClassName(); + + return $this->toRelativePath($path); + } + + /** + * Gets the test suite size. + */ + public function getTestSuiteSize(TestSuite $testSuite): int + { + return $testSuite->count(); + } + + /** + * Transforms the given path in relative path. + */ + private function toRelativePath(string $path): string + { + // Remove cwd from the path. + return str_replace("$this->rootPath".DIRECTORY_SEPARATOR, '', $path); + } + + /** + * Get the test result. + */ + public function getStateFromResult(PhpUnitTestResult $result): State + { + $events = [ + ...$result->testErroredEvents(), + ...$result->testFailedEvents(), + ...$result->testSkippedEvents(), + ...array_merge(...array_values($result->testConsideredRiskyEvents())), + ...$result->testMarkedIncompleteEvents(), + ]; + + $numberOfNotPassedTests = count( + array_unique( + array_map( + function (BeforeFirstTestMethodErrored|Errored|Failed|Skipped|ConsideredRisky|MarkedIncomplete $event): string { + if ($event instanceof BeforeFirstTestMethodErrored) { + return $event->testClassName(); + } + + return $this->getTestCaseLocation($event->test()); + }, + $events + ) + ) + ); + + $numberOfPassedTests = $result->numberOfTestsRun() - $numberOfNotPassedTests; + + return $this->stateGenerator->fromPhpUnitTestResult($numberOfPassedTests, $result); + } +} diff --git a/src/Logging/JUnit/JUnitLogger.php b/src/Logging/JUnit/JUnitLogger.php new file mode 100644 index 000000000..538d28cbd --- /dev/null +++ b/src/Logging/JUnit/JUnitLogger.php @@ -0,0 +1,398 @@ + + */ + private array $testSuiteTests = [0]; + + /** + * @psalm-var array + */ + private array $testSuiteAssertions = [0]; + + /** + * @psalm-var array + */ + private array $testSuiteErrors = [0]; + + /** + * @psalm-var array + */ + private array $testSuiteFailures = [0]; + + /** + * @psalm-var array + */ + private array $testSuiteSkipped = [0]; + + /** + * @psalm-var array + */ + private array $testSuiteTimes = [0]; + + private int $testSuiteLevel = 0; + + private ?DOMElement $currentTestCase = null; + + private ?HRTime $time = null; + + private bool $prepared = false; + + /**✅ + * @throws EventFacadeIsSealedException + * @throws UnknownSubscriberTypeException + */ + public function __construct( + private readonly Printer $printer, + private readonly OutputInterface $output, + private readonly Converter $converter, + ) { + $this->registerSubscribers(); + $this->createDocument(); + } + + public function flush(): void + { + $this->printer->print($this->document->saveXML()); + + $this->printer->flush(); + + $this->output->writeln('Junit finished'); + } + + public function testSuiteStarted(Started $event): void + { + $testSuite = $this->document->createElement('testsuite'); + $testSuite->setAttribute('name', $this->converter->getTestSuiteName($event->testSuite())); + + if ($event->testSuite()->isForTestClass()) { + $testSuite->setAttribute('file', $this->converter->getTestSuiteLocation($event->testSuite()) ?? ''); + } + + if ($this->testSuiteLevel > 0) { + $this->testSuites[$this->testSuiteLevel]->appendChild($testSuite); + } else { + $this->root->appendChild($testSuite); + } + + $this->testSuiteLevel++; + $this->testSuites[$this->testSuiteLevel] = $testSuite; + $this->testSuiteTests[$this->testSuiteLevel] = 0; + $this->testSuiteAssertions[$this->testSuiteLevel] = 0; + $this->testSuiteErrors[$this->testSuiteLevel] = 0; + $this->testSuiteFailures[$this->testSuiteLevel] = 0; + $this->testSuiteSkipped[$this->testSuiteLevel] = 0; + $this->testSuiteTimes[$this->testSuiteLevel] = 0; + } + + public function testSuiteFinished(): void + { + $this->testSuites[$this->testSuiteLevel]->setAttribute( + 'tests', + (string) $this->testSuiteTests[$this->testSuiteLevel], + ); + + $this->testSuites[$this->testSuiteLevel]->setAttribute( + 'assertions', + (string) $this->testSuiteAssertions[$this->testSuiteLevel], + ); + + $this->testSuites[$this->testSuiteLevel]->setAttribute( + 'errors', + (string) $this->testSuiteErrors[$this->testSuiteLevel], + ); + + $this->testSuites[$this->testSuiteLevel]->setAttribute( + 'failures', + (string) $this->testSuiteFailures[$this->testSuiteLevel], + ); + + $this->testSuites[$this->testSuiteLevel]->setAttribute( + 'skipped', + (string) $this->testSuiteSkipped[$this->testSuiteLevel], + ); + + $this->testSuites[$this->testSuiteLevel]->setAttribute( + 'time', + sprintf('%F', $this->testSuiteTimes[$this->testSuiteLevel]), + ); + + if ($this->testSuiteLevel > 1) { + $this->testSuiteTests[$this->testSuiteLevel - 1] += $this->testSuiteTests[$this->testSuiteLevel]; + $this->testSuiteAssertions[$this->testSuiteLevel - 1] += $this->testSuiteAssertions[$this->testSuiteLevel]; + $this->testSuiteErrors[$this->testSuiteLevel - 1] += $this->testSuiteErrors[$this->testSuiteLevel]; + $this->testSuiteFailures[$this->testSuiteLevel - 1] += $this->testSuiteFailures[$this->testSuiteLevel]; + $this->testSuiteSkipped[$this->testSuiteLevel - 1] += $this->testSuiteSkipped[$this->testSuiteLevel]; + $this->testSuiteTimes[$this->testSuiteLevel - 1] += $this->testSuiteTimes[$this->testSuiteLevel]; + } + + $this->testSuiteLevel--; + } + + /** + * @throws InvalidArgumentException + * @throws NoDataSetFromDataProviderException + */ + public function testPrepared(Prepared $event): void + { + $this->createTestCase($event); + $this->prepared = true; + } + + /** + * @throws InvalidArgumentException + */ + public function testFinished(Finished $event): void + { + $this->handleFinish($event->telemetryInfo(), $event->numberOfAssertionsPerformed()); + } + + /** + * @throws InvalidArgumentException + * @throws NoDataSetFromDataProviderException + */ + public function testMarkedIncomplete(MarkedIncomplete $event): void + { + $this->handleIncompleteOrSkipped($event); + } + + /** + * @throws InvalidArgumentException + * @throws NoDataSetFromDataProviderException + */ + public function testSkipped(Skipped $event): void + { + $this->handleIncompleteOrSkipped($event); + } + + /** + * @throws InvalidArgumentException + * @throws NoDataSetFromDataProviderException + */ + public function testErrored(Errored $event): void + { + $this->handleFault($event, 'error'); + + $this->testSuiteErrors[$this->testSuiteLevel]++; + } + + /** + * @throws InvalidArgumentException + * @throws NoDataSetFromDataProviderException + */ + public function testFailed(Failed $event): void + { + $this->handleFault($event, 'failure'); + + $this->testSuiteFailures[$this->testSuiteLevel]++; + } + + /** + * @throws InvalidArgumentException + */ + private function handleFinish(Info $telemetryInfo, int $numberOfAssertionsPerformed): void + { + assert($this->currentTestCase !== null); + assert($this->time !== null); + + $time = $telemetryInfo->time()->duration($this->time)->asFloat(); + + $this->testSuiteAssertions[$this->testSuiteLevel] += $numberOfAssertionsPerformed; + + $this->currentTestCase->setAttribute( + 'assertions', + (string) $numberOfAssertionsPerformed, + ); + + $this->currentTestCase->setAttribute( + 'time', + sprintf('%F', $time), + ); + + $this->testSuites[$this->testSuiteLevel]->appendChild( + $this->currentTestCase, + ); + + $this->testSuiteTests[$this->testSuiteLevel]++; + $this->testSuiteTimes[$this->testSuiteLevel] += $time; + + $this->currentTestCase = null; + $this->time = null; + $this->prepared = false; + } + + /**✅ + * @throws EventFacadeIsSealedException + * @throws UnknownSubscriberTypeException + */ + private function registerSubscribers(): void + { + $subscribers = [ + new TestSuiteStartedSubscriber($this), + new TestSuiteFinishedSubscriber($this), + new TestPreparedSubscriber($this), + new TestFinishedSubscriber($this), + new TestErroredSubscriber($this), + new TestFailedSubscriber($this), + new TestMarkedIncompleteSubscriber($this), + new TestSkippedSubscriber($this), + new TestRunnerExecutionFinishedSubscriber($this), + ]; + + Facade::instance()->registerSubscribers(...$subscribers); + } + + private function createDocument(): void + { + $this->output->writeln('Start Junit'); + + $this->document = new DOMDocument('1.0', 'UTF-8'); + $this->document->formatOutput = true; + + $this->root = $this->document->createElement('testsuites'); + $this->document->appendChild($this->root); + } + + /** + * @throws InvalidArgumentException + * @throws NoDataSetFromDataProviderException + */ + private function handleFault(Errored|Failed $event, string $type): void + { + if (! $this->prepared) { + $this->createTestCase($event); + } + + assert($this->currentTestCase !== null); + + $testName = $this->converter->getTestCaseMethodName($event->test()); + // $message = $this->converter->getExceptionMessage($event->throwable()); + // $details = $this->converter->getExceptionDetails($event->throwable()); + + $buffer = $testName; + + $throwable = $event->throwable(); + $buffer .= trim( + $throwable->description().PHP_EOL. + $throwable->stackTrace(), + ); + + $fault = $this->document->createElement( + $type, + Xml::prepareString($buffer), + ); + + $fault->setAttribute('type', $throwable->className()); + + $this->currentTestCase->appendChild($fault); + + if (! $this->prepared) { + $this->handleFinish($event->telemetryInfo(), 0); + } + } + + /** + * @throws InvalidArgumentException + * @throws NoDataSetFromDataProviderException + */ + private function handleIncompleteOrSkipped(MarkedIncomplete|Skipped $event): void + { + if (! $this->prepared) { + $this->createTestCase($event); + } + + assert($this->currentTestCase !== null); + + $skipped = $this->document->createElement('skipped'); + + $this->currentTestCase->appendChild($skipped); + + $this->testSuiteSkipped[$this->testSuiteLevel]++; + + if (! $this->prepared) { + $this->handleFinish($event->telemetryInfo(), 0); + } + } + + /** + * @throws InvalidArgumentException + * @throws NoDataSetFromDataProviderException + * + * @psalm-assert !null $this->currentTestCase + */ + private function createTestCase(Errored|Failed|MarkedIncomplete|Prepared|Skipped $event): void + { + $testCase = $this->document->createElement('testcase'); + + $file = $this->converter->getTestCaseLocation($event->test()); + + $testCase->setAttribute('name', $this->converter->getTestCaseMethodName($event->test())); +// $testCase->setAttribute('name', $event->test()->name()); + $testCase->setAttribute('file', $file); +// $testCase->setAttribute('file', $event->test()->file()); + + if ($event->test()->isTestMethod()) { + assert($event->test() instanceof TestMethod); + + //dd(TestSuite::getInstance()->tests->get($file)); + // add classname, and line to this + + $testCase->setAttribute('line', (string) $event->test()->line()); //@todo figure out how to get line number in original pest file + $testCase->setAttribute('class', $event->test()->name()); + $testCase->setAttribute('classname', str_replace('\\', '.', $event->test()->name())); + } + + $this->currentTestCase = $testCase; + $this->time = $event->telemetryInfo()->time(); + } +} diff --git a/src/Logging/JUnit/Subscriber/Subscriber.php b/src/Logging/JUnit/Subscriber/Subscriber.php new file mode 100644 index 000000000..7ffa1f343 --- /dev/null +++ b/src/Logging/JUnit/Subscriber/Subscriber.php @@ -0,0 +1,28 @@ +logger; + } +} diff --git a/src/Logging/JUnit/Subscriber/TestErroredSubscriber.php b/src/Logging/JUnit/Subscriber/TestErroredSubscriber.php new file mode 100644 index 000000000..871727747 --- /dev/null +++ b/src/Logging/JUnit/Subscriber/TestErroredSubscriber.php @@ -0,0 +1,19 @@ +logger()->testErrored($event); + } +} diff --git a/src/Logging/JUnit/Subscriber/TestFailedSubscriber.php b/src/Logging/JUnit/Subscriber/TestFailedSubscriber.php new file mode 100644 index 000000000..d0f6c1ea9 --- /dev/null +++ b/src/Logging/JUnit/Subscriber/TestFailedSubscriber.php @@ -0,0 +1,19 @@ +logger()->testFailed($event); + } +} diff --git a/src/Logging/JUnit/Subscriber/TestFinishedSubscriber.php b/src/Logging/JUnit/Subscriber/TestFinishedSubscriber.php new file mode 100644 index 000000000..4c1e937d3 --- /dev/null +++ b/src/Logging/JUnit/Subscriber/TestFinishedSubscriber.php @@ -0,0 +1,19 @@ +logger()->testFinished($event); + } +} diff --git a/src/Logging/JUnit/Subscriber/TestMarkedIncompleteSubscriber.php b/src/Logging/JUnit/Subscriber/TestMarkedIncompleteSubscriber.php new file mode 100644 index 000000000..4ed5d693b --- /dev/null +++ b/src/Logging/JUnit/Subscriber/TestMarkedIncompleteSubscriber.php @@ -0,0 +1,19 @@ +logger()->testMarkedIncomplete($event); + } +} diff --git a/src/Logging/JUnit/Subscriber/TestPreparedSubscriber.php b/src/Logging/JUnit/Subscriber/TestPreparedSubscriber.php new file mode 100644 index 000000000..f98413617 --- /dev/null +++ b/src/Logging/JUnit/Subscriber/TestPreparedSubscriber.php @@ -0,0 +1,19 @@ +logger()->testPrepared($event); + } +} diff --git a/src/Logging/JUnit/Subscriber/TestRunnerExecutionFinishedSubscriber.php b/src/Logging/JUnit/Subscriber/TestRunnerExecutionFinishedSubscriber.php new file mode 100644 index 000000000..8dcf762f5 --- /dev/null +++ b/src/Logging/JUnit/Subscriber/TestRunnerExecutionFinishedSubscriber.php @@ -0,0 +1,19 @@ +logger()->flush(); + } +} diff --git a/src/Logging/JUnit/Subscriber/TestSkippedSubscriber.php b/src/Logging/JUnit/Subscriber/TestSkippedSubscriber.php new file mode 100644 index 000000000..afa764ad6 --- /dev/null +++ b/src/Logging/JUnit/Subscriber/TestSkippedSubscriber.php @@ -0,0 +1,19 @@ +logger()->testSkipped($event); + } +} diff --git a/src/Logging/JUnit/Subscriber/TestSuiteFinishedSubscriber.php b/src/Logging/JUnit/Subscriber/TestSuiteFinishedSubscriber.php new file mode 100644 index 000000000..9ed15c15b --- /dev/null +++ b/src/Logging/JUnit/Subscriber/TestSuiteFinishedSubscriber.php @@ -0,0 +1,19 @@ +logger()->testSuiteFinished(); + } +} diff --git a/src/Logging/JUnit/Subscriber/TestSuiteStartedSubscriber.php b/src/Logging/JUnit/Subscriber/TestSuiteStartedSubscriber.php new file mode 100644 index 000000000..26f80239d --- /dev/null +++ b/src/Logging/JUnit/Subscriber/TestSuiteStartedSubscriber.php @@ -0,0 +1,19 @@ +logger()->testSuiteStarted($event); + } +} diff --git a/src/Subscribers/EnsureJunitEnabled.php b/src/Subscribers/EnsureJunitEnabled.php new file mode 100644 index 000000000..79c78eee9 --- /dev/null +++ b/src/Subscribers/EnsureJunitEnabled.php @@ -0,0 +1,48 @@ +input->hasParameterOption('--log-junit')) { + return; + } + + new JUnitLogger( + DefaultPrinter::from(Container::getInstance()->get(Configuration::class)->logfileJunit()), + $this->output, + new Converter($this->testSuite->rootPath), + ); + } +} From 117694f210f5500b0c42a1cc831801a85c1abde3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20N=C3=BCrnberger?= Date: Sun, 20 Aug 2023 17:55:17 +0200 Subject: [PATCH 04/16] cleanup --- src/Logging/JUnit/Converter.php | 61 ++++--------------------------- src/Logging/JUnit/JUnitLogger.php | 37 ++++++++----------- 2 files changed, 24 insertions(+), 74 deletions(-) diff --git a/src/Logging/JUnit/Converter.php b/src/Logging/JUnit/Converter.php index f417a59e2..e31fb1ff8 100644 --- a/src/Logging/JUnit/Converter.php +++ b/src/Logging/JUnit/Converter.php @@ -4,22 +4,13 @@ namespace Pest\Logging\JUnit; -use NunoMaduro\Collision\Adapters\Phpunit\State; use Pest\Exceptions\ShouldNotHappen; -use Pest\Support\StateGenerator; use Pest\Support\Str; use PHPUnit\Event\Code\Test; use PHPUnit\Event\Code\TestMethod; use PHPUnit\Event\Code\Throwable; -use PHPUnit\Event\Test\BeforeFirstTestMethodErrored; -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\Framework\Exception as FrameworkException; -use PHPUnit\TestRunner\TestResult\TestResult as PhpUnitTestResult; /** * @internal @@ -28,15 +19,12 @@ final class Converter { private const PREFIX = 'P\\'; - private readonly StateGenerator $stateGenerator; - /** * Creates a new instance of the Converter. */ public function __construct( private readonly string $rootPath, ) { - $this->stateGenerator = new StateGenerator(); } /** @@ -73,6 +61,14 @@ public function getTestCaseLocation(Test $test, bool $withDescription = false): return "$relativePath::$description"; } + /** + * Gets the trimmed test class name. + */ + public function getTrimmedTestClassName(TestMethod $test): string + { + return Str::after($test->className(), self::PREFIX); + } + /** * Gets the exception message. */ @@ -176,14 +172,6 @@ public function getTestSuiteLocation(TestSuite $testSuite): ?string return $this->toRelativePath($path); } - /** - * Gets the test suite size. - */ - public function getTestSuiteSize(TestSuite $testSuite): int - { - return $testSuite->count(); - } - /** * Transforms the given path in relative path. */ @@ -192,37 +180,4 @@ private function toRelativePath(string $path): string // Remove cwd from the path. return str_replace("$this->rootPath".DIRECTORY_SEPARATOR, '', $path); } - - /** - * Get the test result. - */ - public function getStateFromResult(PhpUnitTestResult $result): State - { - $events = [ - ...$result->testErroredEvents(), - ...$result->testFailedEvents(), - ...$result->testSkippedEvents(), - ...array_merge(...array_values($result->testConsideredRiskyEvents())), - ...$result->testMarkedIncompleteEvents(), - ]; - - $numberOfNotPassedTests = count( - array_unique( - array_map( - function (BeforeFirstTestMethodErrored|Errored|Failed|Skipped|ConsideredRisky|MarkedIncomplete $event): string { - if ($event instanceof BeforeFirstTestMethodErrored) { - return $event->testClassName(); - } - - return $this->getTestCaseLocation($event->test()); - }, - $events - ) - ) - ); - - $numberOfPassedTests = $result->numberOfTestsRun() - $numberOfNotPassedTests; - - return $this->stateGenerator->fromPhpUnitTestResult($numberOfPassedTests, $result); - } } diff --git a/src/Logging/JUnit/JUnitLogger.php b/src/Logging/JUnit/JUnitLogger.php index 538d28cbd..235899cc0 100644 --- a/src/Logging/JUnit/JUnitLogger.php +++ b/src/Logging/JUnit/JUnitLogger.php @@ -15,7 +15,6 @@ use Pest\Logging\JUnit\Subscriber\TestSkippedSubscriber; use Pest\Logging\JUnit\Subscriber\TestSuiteFinishedSubscriber; use Pest\Logging\JUnit\Subscriber\TestSuiteStartedSubscriber; -use Pest\TestSuite; use PHPUnit\Event\Code\TestMethod; use PHPUnit\Event\EventFacadeIsSealedException; use PHPUnit\Event\Facade; @@ -88,7 +87,7 @@ final class JUnitLogger private bool $prepared = false; - /**✅ + /** * @throws EventFacadeIsSealedException * @throws UnknownSubscriberTypeException */ @@ -271,7 +270,7 @@ private function handleFinish(Info $telemetryInfo, int $numberOfAssertionsPerfor $this->prepared = false; } - /**✅ + /** * @throws EventFacadeIsSealedException * @throws UnknownSubscriberTypeException */ @@ -315,16 +314,16 @@ private function handleFault(Errored|Failed $event, string $type): void assert($this->currentTestCase !== null); + $throwable = $event->throwable(); + $testName = $this->converter->getTestCaseMethodName($event->test()); - // $message = $this->converter->getExceptionMessage($event->throwable()); - // $details = $this->converter->getExceptionDetails($event->throwable()); + $message = $this->converter->getExceptionMessage($throwable); + $details = $this->converter->getExceptionDetails($throwable); $buffer = $testName; - - $throwable = $event->throwable(); $buffer .= trim( - $throwable->description().PHP_EOL. - $throwable->stackTrace(), + $message.PHP_EOL. + $details, ); $fault = $this->document->createElement( @@ -374,22 +373,18 @@ private function createTestCase(Errored|Failed|MarkedIncomplete|Prepared|Skipped { $testCase = $this->document->createElement('testcase'); - $file = $this->converter->getTestCaseLocation($event->test()); + $test = $event->test(); + $file = $this->converter->getTestCaseLocation($test); - $testCase->setAttribute('name', $this->converter->getTestCaseMethodName($event->test())); -// $testCase->setAttribute('name', $event->test()->name()); + $testCase->setAttribute('name', $this->converter->getTestCaseMethodName($test)); $testCase->setAttribute('file', $file); -// $testCase->setAttribute('file', $event->test()->file()); - - if ($event->test()->isTestMethod()) { - assert($event->test() instanceof TestMethod); - //dd(TestSuite::getInstance()->tests->get($file)); - // add classname, and line to this + if ($test->isTestMethod()) { + assert($test instanceof TestMethod); - $testCase->setAttribute('line', (string) $event->test()->line()); //@todo figure out how to get line number in original pest file - $testCase->setAttribute('class', $event->test()->name()); - $testCase->setAttribute('classname', str_replace('\\', '.', $event->test()->name())); + $className = $this->converter->getTrimmedTestClassName($test); + $testCase->setAttribute('class', $className); + $testCase->setAttribute('classname', str_replace('\\', '.', $className)); } $this->currentTestCase = $testCase; From 8efd25ef65c363d5c284501a723d0a5ba0308ec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20N=C3=BCrnberger?= Date: Sun, 20 Aug 2023 18:03:32 +0200 Subject: [PATCH 05/16] remove debug output --- src/Logging/JUnit/JUnitLogger.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Logging/JUnit/JUnitLogger.php b/src/Logging/JUnit/JUnitLogger.php index 235899cc0..68ce17561 100644 --- a/src/Logging/JUnit/JUnitLogger.php +++ b/src/Logging/JUnit/JUnitLogger.php @@ -105,8 +105,6 @@ public function flush(): void $this->printer->print($this->document->saveXML()); $this->printer->flush(); - - $this->output->writeln('Junit finished'); } public function testSuiteStarted(Started $event): void From 4550a344d36f7f0c674d1e936ebc71cc555543c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20N=C3=BCrnberger?= Date: Sun, 20 Aug 2023 18:03:59 +0200 Subject: [PATCH 06/16] overwrite phpunit junit logging with noop --- overrides/Logging/JUnit/JunitXmlLogger.php | 16 ++++++++++++++++ src/Bootstrappers/BootOverrides.php | 1 + 2 files changed, 17 insertions(+) create mode 100644 overrides/Logging/JUnit/JunitXmlLogger.php diff --git a/overrides/Logging/JUnit/JunitXmlLogger.php b/overrides/Logging/JUnit/JunitXmlLogger.php new file mode 100644 index 000000000..bc0e09533 --- /dev/null +++ b/overrides/Logging/JUnit/JunitXmlLogger.php @@ -0,0 +1,16 @@ + Date: Sun, 20 Aug 2023 18:11:54 +0200 Subject: [PATCH 07/16] unify converter --- src/Logging/{TeamCity => }/Converter.php | 10 +- src/Logging/JUnit/Converter.php | 183 ---------------------- src/Logging/JUnit/JUnitLogger.php | 1 + src/Logging/TeamCity/TeamCityLogger.php | 1 + src/Subscribers/EnsureJunitEnabled.php | 2 +- src/Subscribers/EnsureTeamCityEnabled.php | 2 +- 6 files changed, 13 insertions(+), 186 deletions(-) rename src/Logging/{TeamCity => }/Converter.php (96%) delete mode 100644 src/Logging/JUnit/Converter.php diff --git a/src/Logging/TeamCity/Converter.php b/src/Logging/Converter.php similarity index 96% rename from src/Logging/TeamCity/Converter.php rename to src/Logging/Converter.php index 2a2cce4f6..7cbf8f129 100644 --- a/src/Logging/TeamCity/Converter.php +++ b/src/Logging/Converter.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Pest\Logging\TeamCity; +namespace Pest\Logging; use NunoMaduro\Collision\Adapters\Phpunit\State; use Pest\Exceptions\ShouldNotHappen; @@ -150,6 +150,14 @@ public function getTestSuiteName(TestSuite $testSuite): string return Str::after($name, self::PREFIX); } + /** + * Gets the trimmed test class name. + */ + public function getTrimmedTestClassName(TestMethod $test): string + { + return Str::after($test->className(), self::PREFIX); + } + /** * Gets the test suite location. */ diff --git a/src/Logging/JUnit/Converter.php b/src/Logging/JUnit/Converter.php deleted file mode 100644 index e31fb1ff8..000000000 --- a/src/Logging/JUnit/Converter.php +++ /dev/null @@ -1,183 +0,0 @@ -testDox()->prettifiedMethodName(); - } - - /** - * Gets the test case location. - */ - public function getTestCaseLocation(Test $test, bool $withDescription = false): string - { - if (! $test instanceof TestMethod) { - throw ShouldNotHappen::fromMessage('Not an instance of TestMethod'); - } - - $path = $test->testDox()->prettifiedClassName(); - $relativePath = $this->toRelativePath($path); - - // TODO: Get the description without the dataset. - $description = $test->testDox()->prettifiedMethodName(); - - if (! $withDescription) { - return $relativePath; - } - - return "$relativePath::$description"; - } - - /** - * Gets the trimmed test class name. - */ - public function getTrimmedTestClassName(TestMethod $test): string - { - return Str::after($test->className(), self::PREFIX); - } - - /** - * Gets the exception message. - */ - public function getExceptionMessage(Throwable $throwable): string - { - if (is_a($throwable->className(), FrameworkException::class, true)) { - return $throwable->message(); - } - - $buffer = $throwable->className(); - $throwableMessage = $throwable->message(); - - if ($throwableMessage !== '') { - $buffer .= ": $throwableMessage"; - } - - return $buffer; - } - - /** - * Gets the exception details. - */ - public function getExceptionDetails(Throwable $throwable): string - { - $buffer = $this->getStackTrace($throwable); - - while ($throwable->hasPrevious()) { - $throwable = $throwable->previous(); - - $buffer .= sprintf( - "\nCaused by\n%s\n%s", - $throwable->description(), - $this->getStackTrace($throwable) - ); - } - - return $buffer; - } - - /** - * Gets the stack trace. - */ - public function getStackTrace(Throwable $throwable): string - { - $stackTrace = $throwable->stackTrace(); - - // Split stacktrace per frame. - $frames = explode("\n", $stackTrace); - - // Remove empty lines - $frames = array_filter($frames); - - // clean the paths of each frame. - $frames = array_map( - fn (string $frame): string => $this->toRelativePath($frame), - $frames - ); - - // Format stacktrace as `at ` - $frames = array_map( - fn (string $frame) => "at $frame", - $frames - ); - - return implode("\n", $frames); - } - - /** - * Gets the test suite name. - */ - public function getTestSuiteName(TestSuite $testSuite): string - { - $name = $testSuite->name(); - - if (str_starts_with($name, self::PREFIX)) { - return Str::after($name, self::PREFIX); - } - - return Str::after($name, $this->rootPath); - } - - /** - * Gets the test suite location. - */ - public function getTestSuiteLocation(TestSuite $testSuite): ?string - { - $tests = $testSuite->tests()->asArray(); - - // TODO: figure out how to get the file path without a test being there. - if ($tests === []) { - return null; - } - - $firstTest = $tests[0]; - if (! $firstTest instanceof TestMethod) { - throw ShouldNotHappen::fromMessage('Not an instance of TestMethod'); - } - - $path = $firstTest->testDox()->prettifiedClassName(); - - return $this->toRelativePath($path); - } - - /** - * Transforms the given path in relative path. - */ - private function toRelativePath(string $path): string - { - // Remove cwd from the path. - return str_replace("$this->rootPath".DIRECTORY_SEPARATOR, '', $path); - } -} diff --git a/src/Logging/JUnit/JUnitLogger.php b/src/Logging/JUnit/JUnitLogger.php index 68ce17561..497fe8ef4 100644 --- a/src/Logging/JUnit/JUnitLogger.php +++ b/src/Logging/JUnit/JUnitLogger.php @@ -6,6 +6,7 @@ use DOMDocument; use DOMElement; +use Pest\Logging\Converter; use Pest\Logging\JUnit\Subscriber\TestErroredSubscriber; use Pest\Logging\JUnit\Subscriber\TestFailedSubscriber; use Pest\Logging\JUnit\Subscriber\TestFinishedSubscriber; diff --git a/src/Logging/TeamCity/TeamCityLogger.php b/src/Logging/TeamCity/TeamCityLogger.php index 92d35daf9..072bd608e 100644 --- a/src/Logging/TeamCity/TeamCityLogger.php +++ b/src/Logging/TeamCity/TeamCityLogger.php @@ -6,6 +6,7 @@ use NunoMaduro\Collision\Adapters\Phpunit\Style; use Pest\Exceptions\ShouldNotHappen; +use Pest\Logging\Converter; use Pest\Logging\TeamCity\Subscriber\TestConsideredRiskySubscriber; use Pest\Logging\TeamCity\Subscriber\TestErroredSubscriber; use Pest\Logging\TeamCity\Subscriber\TestExecutionFinishedSubscriber; diff --git a/src/Subscribers/EnsureJunitEnabled.php b/src/Subscribers/EnsureJunitEnabled.php index 79c78eee9..b6f6a3396 100644 --- a/src/Subscribers/EnsureJunitEnabled.php +++ b/src/Subscribers/EnsureJunitEnabled.php @@ -4,8 +4,8 @@ namespace Pest\Subscribers; +use Pest\Logging\Converter; use Pest\Logging\JUnit\JUnitLogger; -use Pest\Logging\JUnit\Converter; use Pest\Support\Container; use Pest\TestSuite; use PHPUnit\Event\TestRunner\Configured; diff --git a/src/Subscribers/EnsureTeamCityEnabled.php b/src/Subscribers/EnsureTeamCityEnabled.php index bd6b1bf2e..264800c09 100644 --- a/src/Subscribers/EnsureTeamCityEnabled.php +++ b/src/Subscribers/EnsureTeamCityEnabled.php @@ -4,7 +4,7 @@ namespace Pest\Subscribers; -use Pest\Logging\TeamCity\Converter; +use Pest\Logging\Converter; use Pest\Logging\TeamCity\TeamCityLogger; use Pest\TestSuite; use PHPUnit\Event\TestRunner\Configured; From 53328587826de39aec69a9ff153c4a83091ba2b0 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Thu, 11 Jan 2024 15:46:50 +0000 Subject: [PATCH 08/16] chore: fixes snapshots --- tests/.snapshots/success.txt | 3 +-- tests/Features/SkipOnPhp.php | 4 ---- tests/Visual/Parallel.php | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index 41a30e464..a544db30c 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -1095,7 +1095,6 @@ WARN Tests\Features\SkipOnPhp ✓ it can run on php version - - it can skip on specific php version → This test is skipped on PHP [==8.3.1]. ✓ it can run on specific php version - it can skip on php versions depending on constraint → This test is skipped on PHP [>=7.4.0]. @@ -1378,4 +1377,4 @@ WARN Tests\Visual\Version - visual snapshot of help command output - Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 13 todos, 21 skipped, 978 passed (2304 assertions) \ No newline at end of file + Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 13 todos, 20 skipped, 978 passed (2304 assertions) \ No newline at end of file diff --git a/tests/Features/SkipOnPhp.php b/tests/Features/SkipOnPhp.php index ad528954e..15687d9ee 100644 --- a/tests/Features/SkipOnPhp.php +++ b/tests/Features/SkipOnPhp.php @@ -4,10 +4,6 @@ ->skipOnPhp('<=7.4.0') ->assertTrue(true); -it('can skip on specific php version') - ->skipOnPhp(PHP_VERSION) - ->assertTrue(false); - it('can run on specific php version') ->skipOnPhp('7.4.0') ->assertTrue(true); diff --git a/tests/Visual/Parallel.php b/tests/Visual/Parallel.php index 2b6b463c2..dd0d6a1ba 100644 --- a/tests/Visual/Parallel.php +++ b/tests/Visual/Parallel.php @@ -16,7 +16,7 @@ test('parallel', function () use ($run) { expect($run('--exclude-group=integration')) - ->toContain('Tests: 1 deprecated, 4 warnings, 5 incomplete, 2 notices, 13 todos, 17 skipped, 965 passed (2285 assertions)') + ->toContain('Tests: 1 deprecated, 4 warnings, 5 incomplete, 2 notices, 13 todos, 16 skipped, 965 passed (2285 assertions)') ->toContain('Parallel: 3 processes'); })->skipOnWindows(); From 6d749657275778fae31c43385ea4788039642dda Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sat, 20 Jan 2024 11:43:45 +0000 Subject: [PATCH 09/16] chore: bump dependencies --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 14bdaf6d4..f3b2f879a 100644 --- a/composer.json +++ b/composer.json @@ -19,14 +19,14 @@ "require": { "php": "^8.1.0", "brianium/paratest": "^7.3.1", - "nunomaduro/collision": "^7.10.0|^8.0.1", + "nunomaduro/collision": "^7.10.0|^8.1.0", "nunomaduro/termwind": "^1.15.1|^2.0.0", "pestphp/pest-plugin": "^2.1.1", "pestphp/pest-plugin-arch": "^2.6.1", - "phpunit/phpunit": "^10.5.5" + "phpunit/phpunit": "^10.5.7" }, "conflict": { - "phpunit/phpunit": ">10.5.5", + "phpunit/phpunit": ">10.5.7", "sebastian/exporter": "<5.1.0", "webmozart/assert": "<1.11.0" }, From e135e2671fd51314104f4ece98d4c0865150aea6 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sat, 20 Jan 2024 11:43:52 +0000 Subject: [PATCH 10/16] style --- src/Logging/TeamCity/ServiceMessage.php | 2 +- src/Repositories/TestRepository.php | 4 ++-- src/Support/HigherOrderMessageCollection.php | 2 +- src/Support/Str.php | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Logging/TeamCity/ServiceMessage.php b/src/Logging/TeamCity/ServiceMessage.php index ace7f40b9..ca16dd80b 100644 --- a/src/Logging/TeamCity/ServiceMessage.php +++ b/src/Logging/TeamCity/ServiceMessage.php @@ -63,7 +63,7 @@ public static function testStarted(string $name, string $location): self } /** - * @param int $duration in milliseconds + * @param int $duration in milliseconds */ public static function testFinished(string $name, int $duration): self { diff --git a/src/Repositories/TestRepository.php b/src/Repositories/TestRepository.php index 93fd23382..f8b789c27 100644 --- a/src/Repositories/TestRepository.php +++ b/src/Repositories/TestRepository.php @@ -30,12 +30,12 @@ final class TestRepository private array $uses = []; /** - * @var array + * @var array */ private array $testCaseFilters = []; /** - * @var array + * @var array */ private array $testCaseMethodFilters = []; diff --git a/src/Support/HigherOrderMessageCollection.php b/src/Support/HigherOrderMessageCollection.php index 8f5be734b..da13a16c0 100644 --- a/src/Support/HigherOrderMessageCollection.php +++ b/src/Support/HigherOrderMessageCollection.php @@ -58,7 +58,7 @@ public function proxy(object $target): void /** * Count the number of messages with the given name. * - * @param string $name A higher order message name (usually a method name) + * @param string $name A higher order message name (usually a method name) */ public function count(string $name): int { diff --git a/src/Support/Str.php b/src/Support/Str.php index 16fa58fc1..754749e7e 100644 --- a/src/Support/Str.php +++ b/src/Support/Str.php @@ -24,7 +24,7 @@ final class Str * Create a (unsecure & non-cryptographically safe) random alpha-numeric * string value. * - * @param int $length the length of the resulting randomized string + * @param int $length the length of the resulting randomized string * * @see https://github.com/laravel/framework/blob/4.2/src/Illuminate/Support/Str.php#L240-L242 */ From fef02594dbe6a4aa6a54aed0025d871cb1a0f207 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sat, 20 Jan 2024 11:44:00 +0000 Subject: [PATCH 11/16] release: 2.32.0 --- src/Pest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Pest.php b/src/Pest.php index e59c88de0..3ad6deecd 100644 --- a/src/Pest.php +++ b/src/Pest.php @@ -6,7 +6,7 @@ function version(): string { - return '2.31.0'; + return '2.32.0'; } function testDirectory(string $file = ''): string From b9d2be87a28b218fd13d52df1c0d6c72d58cf191 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sat, 20 Jan 2024 13:21:57 +0000 Subject: [PATCH 12/16] fix: missing things on `junit` --- composer.json | 3 +- src/Logging/JUnit/JUnitLogger.php | 20 +++------ src/Mixins/Expectation.php | 2 +- src/Plugins/Cache.php | 7 ++- src/Plugins/JUnit.php | 44 +++++++++++++++++++ src/Subscribers/EnsureJunitEnabled.php | 8 ++-- ...isual_snapshot_of_help_command_output.snap | 3 +- ...isual_snapshot_of_help_command_output.snap | 2 +- 8 files changed, 63 insertions(+), 26 deletions(-) create mode 100644 src/Plugins/JUnit.php diff --git a/composer.json b/composer.json index f3b2f879a..77b14c3ec 100644 --- a/composer.json +++ b/composer.json @@ -107,7 +107,8 @@ "Pest\\Plugins\\Snapshot", "Pest\\Plugins\\Verbose", "Pest\\Plugins\\Version", - "Pest\\Plugins\\Parallel" + "Pest\\Plugins\\Parallel", + "Pest\\Plugins\\JUnit" ] }, "phpstan": { diff --git a/src/Logging/JUnit/JUnitLogger.php b/src/Logging/JUnit/JUnitLogger.php index 497fe8ef4..ba71f88f6 100644 --- a/src/Logging/JUnit/JUnitLogger.php +++ b/src/Logging/JUnit/JUnitLogger.php @@ -16,7 +16,6 @@ use Pest\Logging\JUnit\Subscriber\TestSkippedSubscriber; use Pest\Logging\JUnit\Subscriber\TestSuiteFinishedSubscriber; use Pest\Logging\JUnit\Subscriber\TestSuiteStartedSubscriber; -use PHPUnit\Event\Code\TestMethod; use PHPUnit\Event\EventFacadeIsSealedException; use PHPUnit\Event\Facade; use PHPUnit\Event\InvalidArgumentException; @@ -33,14 +32,12 @@ use PHPUnit\Event\UnknownSubscriberTypeException; use PHPUnit\TextUI\Output\Printer; use PHPUnit\Util\Xml; -use Symfony\Component\Console\Output\OutputInterface; /** * @internal */ final class JUnitLogger { - private DOMDocument $document; private DOMElement $root; @@ -76,7 +73,7 @@ final class JUnitLogger private array $testSuiteSkipped = [0]; /** - * @psalm-var array + * @psalm-var array */ private array $testSuiteTimes = [0]; @@ -94,7 +91,6 @@ final class JUnitLogger */ public function __construct( private readonly Printer $printer, - private readonly OutputInterface $output, private readonly Converter $converter, ) { $this->registerSubscribers(); @@ -103,7 +99,7 @@ public function __construct( public function flush(): void { - $this->printer->print($this->document->saveXML()); + $this->printer->print((string) $this->document->saveXML()); $this->printer->flush(); } @@ -240,8 +236,8 @@ public function testFailed(Failed $event): void */ private function handleFinish(Info $telemetryInfo, int $numberOfAssertionsPerformed): void { - assert($this->currentTestCase !== null); - assert($this->time !== null); + assert($this->currentTestCase instanceof \DOMElement); + assert($this->time instanceof \PHPUnit\Event\Telemetry\HRTime); $time = $telemetryInfo->time()->duration($this->time)->asFloat(); @@ -292,8 +288,6 @@ private function registerSubscribers(): void private function createDocument(): void { - $this->output->writeln('Start Junit'); - $this->document = new DOMDocument('1.0', 'UTF-8'); $this->document->formatOutput = true; @@ -311,7 +305,7 @@ private function handleFault(Errored|Failed $event, string $type): void $this->createTestCase($event); } - assert($this->currentTestCase !== null); + assert($this->currentTestCase instanceof \DOMElement); $throwable = $event->throwable(); @@ -349,7 +343,7 @@ private function handleIncompleteOrSkipped(MarkedIncomplete|Skipped $event): voi $this->createTestCase($event); } - assert($this->currentTestCase !== null); + assert($this->currentTestCase instanceof \DOMElement); $skipped = $this->document->createElement('skipped'); @@ -379,8 +373,6 @@ private function createTestCase(Errored|Failed|MarkedIncomplete|Prepared|Skipped $testCase->setAttribute('file', $file); if ($test->isTestMethod()) { - assert($test instanceof TestMethod); - $className = $this->converter->getTrimmedTestClassName($test); $testCase->setAttribute('class', $className); $testCase->setAttribute('classname', str_replace('\\', '.', $className)); diff --git a/src/Mixins/Expectation.php b/src/Mixins/Expectation.php index 6a906c81b..0d0b4d92c 100644 --- a/src/Mixins/Expectation.php +++ b/src/Mixins/Expectation.php @@ -320,7 +320,7 @@ public function toHaveProperty(string $name, mixed $value = new Any(), string $m public function toHaveProperties(iterable $names, string $message = ''): self { foreach ($names as $name => $value) { - is_int($name) ? $this->toHaveProperty($value, message: $message) : $this->toHaveProperty($name, $value, $message); + is_int($name) ? $this->toHaveProperty($value, message: $message) : $this->toHaveProperty($name, $value, $message); // @phpstan-ignore-line } return $this; diff --git a/src/Plugins/Cache.php b/src/Plugins/Cache.php index 682b938c9..7312691b0 100644 --- a/src/Plugins/Cache.php +++ b/src/Plugins/Cache.php @@ -31,10 +31,9 @@ final class Cache implements HandlesArguments public function handleArguments(array $arguments): array { if (! $this->hasArgument('--cache-directory', $arguments)) { - $arguments = $this->pushArgument( - sprintf('--cache-directory=%s', realpath(self::TEMPORARY_FOLDER)), - $arguments - ); + $arguments = $this->pushArgument('--cache-directory', $arguments); + + $arguments = $this->pushArgument((string) realpath(self::TEMPORARY_FOLDER), $arguments); } if (! $this->hasArgument('--parallel', $arguments)) { diff --git a/src/Plugins/JUnit.php b/src/Plugins/JUnit.php new file mode 100644 index 000000000..3ee9bdf7b --- /dev/null +++ b/src/Plugins/JUnit.php @@ -0,0 +1,44 @@ +hasArgument('--log-junit', $arguments)) { + return $arguments; + } + + $logUnitArgument = null; + + $arguments = array_filter($arguments, function (string $argument) use (&$logUnitArgument): bool { + if (str_starts_with($argument, '--log-junit')) { + $logUnitArgument = $argument; + + return false; + } + + return true; + }); + + assert(is_string($logUnitArgument)); + + $arguments[] = $logUnitArgument; + + return array_values($arguments); + } +} diff --git a/src/Subscribers/EnsureJunitEnabled.php b/src/Subscribers/EnsureJunitEnabled.php index b6f6a3396..b57c32e6b 100644 --- a/src/Subscribers/EnsureJunitEnabled.php +++ b/src/Subscribers/EnsureJunitEnabled.php @@ -13,7 +13,6 @@ use PHPUnit\TextUI\Configuration\Configuration; use PHPUnit\TextUI\Output\DefaultPrinter; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; /** * @internal @@ -25,7 +24,6 @@ final class EnsureJunitEnabled implements ConfiguredSubscriber */ public function __construct( private readonly InputInterface $input, - private readonly OutputInterface $output, private readonly TestSuite $testSuite, ) { } @@ -39,9 +37,11 @@ public function notify(Configured $event): void return; } + $configuration = Container::getInstance()->get(Configuration::class); + assert($configuration instanceof Configuration); + new JUnitLogger( - DefaultPrinter::from(Container::getInstance()->get(Configuration::class)->logfileJunit()), - $this->output, + DefaultPrinter::from($configuration->logfileJunit()), new Converter($this->testSuite->rootPath), ); } diff --git a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap index 87b0ec079..7d08bd34a 100644 --- a/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap +++ b/tests/.pest/snapshots/Visual/Help/visual_snapshot_of_help_command_output.snap @@ -1,5 +1,5 @@ - Pest Testing Framework 2.31.0. + Pest Testing Framework 2.32.0. USAGE: pest [options] @@ -84,6 +84,7 @@ --reverse-list .............................. Print defects in reverse order --teamcity . Replace default progress and result output with TeamCity format --testdox ................ Replace default result output with TestDox format + --debug Replace default progress and result output with debugging information --compact ................ Replace default result output with Compact format LOGGING OPTIONS: diff --git a/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap b/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap index 969fdf6ea..b698a1f81 100644 --- a/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap +++ b/tests/.pest/snapshots/Visual/Version/visual_snapshot_of_help_command_output.snap @@ -1,3 +1,3 @@ - Pest Testing Framework 2.31.0. + Pest Testing Framework 2.32.0. From 0ab636e4361cd71aeaafc4ebc480f7c593693a4b Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sat, 20 Jan 2024 13:28:43 +0000 Subject: [PATCH 13/16] chore: fixes workflow --- .github/workflows/tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4b79676d5..7fd6abbe5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,14 +15,14 @@ jobs: fail-fast: true matrix: os: [ubuntu-latest, macos-latest, windows-latest] - symfony: ['6.4.0', '7.0.1'] + symfony: ['6.4', '7.1'] php: ['8.1', '8.2', '8.3'] dependency_version: [prefer-lowest, prefer-stable] exclude: - php: '8.1' - symfony: '7.0.1' + symfony: '7.0' - name: PHP ${{ matrix.php }} - Symfony ^${{ matrix.symfony }} - ${{ matrix.os }} - ${{ matrix.dependency_version }} + name: PHP ${{ matrix.php }} - Symfony ~${{ matrix.symfony }} - ${{ matrix.os }} - ${{ matrix.dependency_version }} steps: - name: Checkout From 9a01504b76485344cc2f3dee63ad437fe3df0d56 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sat, 20 Jan 2024 13:32:21 +0000 Subject: [PATCH 14/16] chore: fixes workflow --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7fd6abbe5..5f7b7efdd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,7 +15,7 @@ jobs: fail-fast: true matrix: os: [ubuntu-latest, macos-latest, windows-latest] - symfony: ['6.4', '7.1'] + symfony: ['6.4', '7.0'] php: ['8.1', '8.2', '8.3'] dependency_version: [prefer-lowest, prefer-stable] exclude: From 5aa3b91d56d495171907a01654ef797ef7877eb9 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sat, 20 Jan 2024 13:36:31 +0000 Subject: [PATCH 15/16] chore: fixes windows builds --- .github/workflows/tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5f7b7efdd..fa9d77710 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -21,6 +21,8 @@ jobs: exclude: - php: '8.1' symfony: '7.0' + - symfony: '6.4' + os: 'windows-latest' name: PHP ${{ matrix.php }} - Symfony ~${{ matrix.symfony }} - ${{ matrix.os }} - ${{ matrix.dependency_version }} From ac5d6c1f6754b4a6b4d16d825e5ebd4725a4f779 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Sat, 20 Jan 2024 13:48:00 +0000 Subject: [PATCH 16/16] chore: fixes constrains no workflow --- .github/workflows/tests.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fa9d77710..1385be9d6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -21,10 +21,8 @@ jobs: exclude: - php: '8.1' symfony: '7.0' - - symfony: '6.4' - os: 'windows-latest' - name: PHP ${{ matrix.php }} - Symfony ~${{ matrix.symfony }} - ${{ matrix.os }} - ${{ matrix.dependency_version }} + name: PHP ${{ matrix.php }} - Symfony ^${{ matrix.symfony }} - ${{ matrix.os }} - ${{ matrix.dependency_version }} steps: - name: Checkout @@ -43,7 +41,7 @@ jobs: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - name: Install PHP dependencies - run: composer update --${{ matrix.dependency_version }} --no-interaction --no-progress --ansi --with="symfony/console:^${{ matrix.symfony }}" + run: composer update --${{ matrix.dependency_version }} --no-interaction --no-progress --ansi --with="symfony/console:~${{ matrix.symfony }}" - name: Unit Tests run: composer test:unit