diff --git a/src/Bootstrappers/BootSubscribers.php b/src/Bootstrappers/BootSubscribers.php index f605d9be..5fa720ac 100644 --- a/src/Bootstrappers/BootSubscribers.php +++ b/src/Bootstrappers/BootSubscribers.php @@ -25,6 +25,7 @@ final readonly class BootSubscribers implements Bootstrapper Subscribers\EnsureIgnorableTestCasesAreIgnored::class, Subscribers\EnsureKernelDumpIsFlushed::class, Subscribers\EnsureTeamCityEnabled::class, + Subscribers\EnsureTiaIsRunningPestTestsOnly::class, Subscribers\EnsureTiaCoverageIsRecorded::class, Subscribers\EnsureTiaCoverageIsFlushed::class, Subscribers\EnsureTiaResultsAreCollected::class, diff --git a/src/Exceptions/TiaRequiresPestTests.php b/src/Exceptions/TiaRequiresPestTests.php new file mode 100644 index 00000000..603fcd05 --- /dev/null +++ b/src/Exceptions/TiaRequiresPestTests.php @@ -0,0 +1,46 @@ +writeln([ + '', + ' ERROR Tia mode requires Pest tests.', + '', + sprintf(' Encountered PHPUnit class %s', $this->className), + sprintf(' in %s.', $this->file), + '', + ' Convert it to a Pest test, or run without Tia.', + '', + ]); + } + + public function exitCode(): int + { + return 1; + } +} diff --git a/src/Plugins/Tia.php b/src/Plugins/Tia.php index 50c67593..4250c795 100644 --- a/src/Plugins/Tia.php +++ b/src/Plugins/Tia.php @@ -787,18 +787,18 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable } $affectedFromChanges = $changed === [] ? [] : $graph->affected($changed); - $failedFromCache = []; + $rerunFromCache = []; if ($this->filteredMode) { - $failedFromCache = $graph->failedOrErroredTestFiles($this->branch); + $rerunFromCache = $graph->testFilesToRerun($this->branch); } $affected = array_values(array_unique([ ...$affectedFromChanges, - ...$failedFromCache, + ...$rerunFromCache, ])); - $this->reportAffectedSummary($changed, $affectedFromChanges, $failedFromCache, $affected); + $this->reportAffectedSummary($changed, $affectedFromChanges, $rerunFromCache, $affected); $affectedSet = array_fill_keys($affected, true); $canRefreshReplayEdges = $affected !== [] && $coverageAvailable; @@ -852,10 +852,10 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable /** * @param array $changedFiles * @param array $affectedFromChanges - * @param array $failedFromCache + * @param array $rerunFromCache * @param array $affected */ - private function reportAffectedSummary(array $changedFiles, array $affectedFromChanges, array $failedFromCache, array $affected): void + private function reportAffectedSummary(array $changedFiles, array $affectedFromChanges, array $rerunFromCache, array $affected): void { $this->output->writeln(''); @@ -865,12 +865,12 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable return; } - $newFailures = $failedFromCache === [] + $newReruns = $rerunFromCache === [] ? 0 - : count(array_diff($failedFromCache, $affectedFromChanges)); + : count(array_diff($rerunFromCache, $affectedFromChanges)); $reasons = []; - $singleReason = (int) ($affectedFromChanges !== []) + (int) ($newFailures > 0) === 1; + $singleReason = (int) ($affectedFromChanges !== []) + (int) ($newReruns > 0) === 1; if ($affectedFromChanges !== []) { $reasons[] = $singleReason @@ -887,17 +887,17 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable ); } - if ($newFailures > 0) { + if ($newReruns > 0) { $reasons[] = $singleReason ? sprintf( - 'from %d previous failure%s', - $newFailures, - $newFailures === 1 ? '' : 's', + 'from %d previously unsuccessful test%s', + $newReruns, + $newReruns === 1 ? '' : 's', ) : sprintf( - '%d from previous failure%s', - $newFailures, - $newFailures === 1 ? '' : 's', + '%d from previously unsuccessful test%s', + $newReruns, + $newReruns === 1 ? '' : 's', ); } diff --git a/src/Plugins/Tia/Graph.php b/src/Plugins/Tia/Graph.php index 0010b8d9..c42b2fc6 100644 --- a/src/Plugins/Tia/Graph.php +++ b/src/Plugins/Tia/Graph.php @@ -516,13 +516,13 @@ final class Graph /** * @return array */ - public function failedOrErroredTestFiles(string $branch, string $fallbackBranch = 'main'): array + public function testFilesToRerun(string $branch, string $fallbackBranch = 'main'): array { $baseline = $this->baselineFor($branch, $fallbackBranch); $files = []; foreach ($baseline['results'] as $result) { - if ($result['status'] !== 7 && $result['status'] !== 8) { + if (! self::shouldRerun($result['status'])) { continue; } @@ -544,12 +544,12 @@ final class Graph return array_keys($files); } - public function hasUnlocatedFailuresOrErrors(string $branch, string $fallbackBranch = 'main'): bool + public function hasUnlocatedTestsToRerun(string $branch, string $fallbackBranch = 'main'): bool { $baseline = $this->baselineFor($branch, $fallbackBranch); foreach ($baseline['results'] as $result) { - if ($result['status'] !== 7 && $result['status'] !== 8) { + if (! self::shouldRerun($result['status'])) { continue; } @@ -563,6 +563,16 @@ final class Graph return false; } + private static function shouldRerun(int $status): bool + { + $testStatus = TestStatus::from($status); + + return $testStatus->isFailure() + || $testStatus->isError() + || $testStatus->isIncomplete() + || $testStatus->isRisky(); + } + /** * @param array $tree project-relative path → content hash */ diff --git a/src/Plugins/Tia/Recorder.php b/src/Plugins/Tia/Recorder.php index 57f66a6d..dfd319a7 100644 --- a/src/Plugins/Tia/Recorder.php +++ b/src/Plugins/Tia/Recorder.php @@ -119,8 +119,8 @@ final class Recorder $this->perTestUsesDatabase[$file] = true; } - $this->linkAncestorFiles($className); - $this->linkImportedFiles($file); + // $this->linkAncestorFiles($className); + // $this->linkImportedFiles($file); if ($this->driver === 'pcov') { \pcov\clear(); @@ -175,7 +175,7 @@ final class Recorder $this->perTestFiles[$this->currentTestFile][$sourceFile] = true; } - $this->linkSourceDependencies($coveredFiles); + // $this->linkSourceDependencies($coveredFiles); $this->currentTestFile = null; $this->includedFilesAtTestStart = []; diff --git a/src/Plugins/Tia/ResultCollector.php b/src/Plugins/Tia/ResultCollector.php index 09c6148a..1b84c8c8 100644 --- a/src/Plugins/Tia/ResultCollector.php +++ b/src/Plugins/Tia/ResultCollector.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Pest\Plugins\Tia; +use PHPUnit\Framework\TestStatus\TestStatus; + /** * @internal */ @@ -33,7 +35,7 @@ final class ResultCollector return; } - $this->record(0, ''); + $this->record(TestStatus::success()); } public function testFailed(string $message): void @@ -42,7 +44,7 @@ final class ResultCollector return; } - $this->record(7, $message); + $this->record(TestStatus::failure($message)); } public function testErrored(string $message): void @@ -51,7 +53,7 @@ final class ResultCollector return; } - $this->record(8, $message); + $this->record(TestStatus::error($message)); } public function testSkipped(string $message): void @@ -60,7 +62,7 @@ final class ResultCollector return; } - $this->record(1, $message); + $this->record(TestStatus::skipped($message)); } public function testIncomplete(string $message): void @@ -69,7 +71,7 @@ final class ResultCollector return; } - $this->record(2, $message); + $this->record(TestStatus::incomplete($message)); } public function testRisky(string $message): void @@ -78,7 +80,7 @@ final class ResultCollector return; } - $this->record(5, $message); + $this->record(TestStatus::risky($message)); } /** @@ -121,7 +123,7 @@ final class ResultCollector $this->startTime = null; } - private function record(int $status, string $message): void + private function record(TestStatus $status): void { if ($this->currentTestId === null) { return; @@ -134,8 +136,8 @@ final class ResultCollector $existing = $this->results[$this->currentTestId] ?? null; $this->results[$this->currentTestId] = [ - 'status' => $status, - 'message' => $message, + 'status' => $status->asInt(), + 'message' => $status->message(), 'time' => $time, 'assertions' => $existing['assertions'] ?? 0, ]; diff --git a/src/Subscribers/EnsureTiaIsRunningPestTestsOnly.php b/src/Subscribers/EnsureTiaIsRunningPestTestsOnly.php new file mode 100644 index 00000000..591881e6 --- /dev/null +++ b/src/Subscribers/EnsureTiaIsRunningPestTestsOnly.php @@ -0,0 +1,64 @@ +recorder->isActive()) { + return; + } + + $test = $event->test(); + + if (! $test instanceof TestMethod) { + return; + } + + $className = $test->className(); + + if (! class_exists($className, false)) { + return; + } + + if ($this->usesTestableTrait($className)) { + return; + } + + Panic::with(new TiaRequiresPestTests($className, $test->file())); + } + + private function usesTestableTrait(string $className): bool + { + $reflection = new ReflectionClass($className); + + do { + foreach ($reflection->getTraitNames() as $trait) { + if ($trait === Testable::class) { + return true; + } + } + + $reflection = $reflection->getParentClass(); + } while ($reflection !== false); + + return false; + } +}