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): 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(); 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): string => "at $frame", $frames ); return implode("\n", $frames); } /** * Gets the test suite name. */ public function getTestSuiteName(TestSuite $testSuite): string { if ($testSuite instanceof TestSuiteForTestMethodWithDataProvider) { $firstTest = $this->getFirstTest($testSuite); if ($firstTest instanceof \PHPUnit\Event\Code\TestMethod) { return $this->getTestMethodNameWithoutDatasetSuffix($firstTest); } } $name = $testSuite->name(); if (! str_starts_with($name, self::PREFIX)) { return $name; } 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. */ public function getTestSuiteLocation(TestSuite $testSuite): ?string { $firstTest = $this->getFirstTest($testSuite); if (! $firstTest instanceof \PHPUnit\Event\Code\TestMethod) { return null; } $path = $firstTest->testDox()->prettifiedClassName(); $classRelativePath = $this->toRelativePath($path); if ($testSuite instanceof TestSuiteForTestMethodWithDataProvider) { $methodName = $this->getTestMethodNameWithoutDatasetSuffix($firstTest); return "$classRelativePath::$methodName"; } return $classRelativePath; } /** * Gets the prettified test method name without dataset-related suffix. */ private function getTestMethodNameWithoutDatasetSuffix(TestMethod $testMethod): string { return Str::beforeLast($testMethod->testDox()->prettifiedMethodName(), ' with data set '); } /** * Gets the first test from the test suite. */ private function getFirstTest(TestSuite $testSuite): ?TestMethod { $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'); } return $firstTest; } /** * 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); } }