From 41f11c0ef3907ae6be34b74267bef9884aa86e5e Mon Sep 17 00:00:00 2001 From: nuno maduro Date: Thu, 16 Apr 2026 10:59:06 -0700 Subject: [PATCH] feat(tia): continues to work on poc --- src/Bootstrappers/BootSubscribers.php | 1 + src/Concerns/Testable.php | 46 +++++ src/Contracts/Plugins/BeforeEachable.php | 25 +++ src/Kernel.php | 3 +- src/Plugins/Tia.php | 179 ++++++++++++++++-- src/Plugins/Tia/CachedTestResult.php | 33 ++++ src/Plugins/Tia/ChangedFiles.php | 96 ++++++++++ src/Plugins/Tia/Graph.php | 99 +++++++++- src/Plugins/Tia/ResultCollector.php | 119 ++++++++++++ .../EnsureTiaResultsAreCollected.php | 86 +++++++++ tests/Arch.php | 1 + 11 files changed, 668 insertions(+), 20 deletions(-) create mode 100644 src/Contracts/Plugins/BeforeEachable.php create mode 100644 src/Plugins/Tia/CachedTestResult.php create mode 100644 src/Plugins/Tia/ResultCollector.php create mode 100644 src/Subscribers/EnsureTiaResultsAreCollected.php diff --git a/src/Bootstrappers/BootSubscribers.php b/src/Bootstrappers/BootSubscribers.php index 749a6b5d..c01af8f5 100644 --- a/src/Bootstrappers/BootSubscribers.php +++ b/src/Bootstrappers/BootSubscribers.php @@ -27,6 +27,7 @@ final readonly class BootSubscribers implements Bootstrapper Subscribers\EnsureTeamCityEnabled::class, Subscribers\EnsureTiaCoverageIsRecorded::class, Subscribers\EnsureTiaCoverageIsFlushed::class, + Subscribers\EnsureTiaResultsAreCollected::class, ]; /** diff --git a/src/Concerns/Testable.php b/src/Concerns/Testable.php index 3f7e3b77..38488934 100644 --- a/src/Concerns/Testable.php +++ b/src/Concerns/Testable.php @@ -5,8 +5,11 @@ declare(strict_types=1); namespace Pest\Concerns; use Closure; +use Pest\Contracts\Plugins\BeforeEachable; use Pest\Exceptions\DatasetArgumentsMismatch; use Pest\Panic; +use Pest\Plugin\Loader; +use Pest\Plugins\Tia\CachedTestResult; use Pest\Preset; use Pest\Support\ChainableClosure; use Pest\Support\ExceptionTrace; @@ -75,6 +78,12 @@ trait Testable */ public bool $__ran = false; + /** + * Set when a `BeforeEachable` plugin returns a cached success result. + * Checked in `__runTest` and `tearDown` to skip body + cleanup. + */ + private bool $__cachedPass = false; + /** * The test's test closure. */ @@ -227,6 +236,31 @@ trait Testable { TestSuite::getInstance()->test = $this; + $this->__cachedPass = false; + + /** @var BeforeEachable $plugin */ + foreach (Loader::getPlugins(BeforeEachable::class) as $plugin) { + $cached = $plugin->beforeEach(self::$__filename, $this::class.'::'.$this->name()); + + if ($cached instanceof CachedTestResult) { + if ($cached->isSuccess()) { + $this->__cachedPass = true; + + return; + } + + // Non-success: throw appropriate exception. PHPUnit catches + // it in runBare() and marks the test with the correct status. + // This makes skips, failures, incompletes, todos appear in + // output exactly as if the test ran. + match ($cached->status) { + 1 => $this->markTestSkipped($cached->message), // skip / todo + 2 => $this->markTestIncomplete($cached->message), // incomplete + default => throw new \PHPUnit\Framework\AssertionFailedError($cached->message ?: 'Cached failure'), + }; + } + } + $method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name()); $description = $method->description; @@ -302,6 +336,12 @@ trait Testable */ protected function tearDown(...$arguments): void { + if ($this->__cachedPass) { + TestSuite::getInstance()->test = null; + + return; + } + $afterEach = TestSuite::getInstance()->afterEach->get(self::$__filename); if ($this->__afterEach instanceof Closure) { @@ -327,6 +367,12 @@ trait Testable */ private function __runTest(Closure $closure, ...$args): mixed { + if ($this->__cachedPass) { + $this->addToAssertionCount(1); + + return null; + } + $arguments = $this->__resolveTestArguments($args); $this->__ensureDatasetArgumentNameAndNumberMatches($arguments); diff --git a/src/Contracts/Plugins/BeforeEachable.php b/src/Contracts/Plugins/BeforeEachable.php new file mode 100644 index 00000000..436cfcec --- /dev/null +++ b/src/Contracts/Plugins/BeforeEachable.php @@ -0,0 +1,25 @@ +add(OutputInterface::class, $output) ->add(Container::class, $container) ->add(Tia\Recorder::class, new Tia\Recorder) - ->add(Tia\WatchPatterns::class, new Tia\WatchPatterns); + ->add(Tia\WatchPatterns::class, new Tia\WatchPatterns) + ->add(Tia\ResultCollector::class, new Tia\ResultCollector); $kernel = new self( new Application, diff --git a/src/Plugins/Tia.php b/src/Plugins/Tia.php index 6ee4f568..578711e9 100644 --- a/src/Plugins/Tia.php +++ b/src/Plugins/Tia.php @@ -5,12 +5,15 @@ declare(strict_types=1); namespace Pest\Plugins; use Pest\Contracts\Plugins\AddsOutput; +use Pest\Contracts\Plugins\BeforeEachable; use Pest\Contracts\Plugins\HandlesArguments; use Pest\Contracts\Plugins\Terminable; +use Pest\Plugins\Tia\CachedTestResult; use Pest\Plugins\Tia\ChangedFiles; use Pest\Plugins\Tia\Fingerprint; use Pest\Plugins\Tia\Graph; use Pest\Plugins\Tia\Recorder; +use Pest\Plugins\Tia\ResultCollector; use Pest\TestCaseFilters\TiaTestCaseFilter; use Pest\Plugins\Tia\WatchPatterns; use Pest\Support\Container; @@ -61,7 +64,7 @@ use Throwable; * * @internal */ -final class Tia implements AddsOutput, HandlesArguments, Terminable +final class Tia implements AddsOutput, BeforeEachable, HandlesArguments, Terminable { use Concerns\HandleArguments; @@ -98,6 +101,28 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable private bool $graphWritten = false; + private bool $replayRan = false; + + /** + * Holds the graph during replay so `beforeEach` can look up cached + * results without re-loading from disk on every test. + */ + private ?Graph $replayGraph = null; + + /** + * Current git branch (or `HEAD` SHA when detached). Resolved once per + * run so all graph accesses use the same branch key. + */ + private string $branch = 'main'; + + /** + * Test files that are affected (should re-execute). Keyed by + * project-relative path. Set during `enterReplayMode`. + * + * @var array + */ + private array $affectedFiles = []; + private static function tempDir(): string { $dir = (string) realpath(self::TEMP_DIR); @@ -137,6 +162,34 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable private readonly WatchPatterns $watchPatterns, ) {} + public function beforeEach(string $filename, string $testId): ?CachedTestResult + { + if ($this->replayGraph === null) { + return null; + } + + // Resolve file to project-relative path. + $projectRoot = TestSuite::getInstance()->rootPath; + $real = @realpath($filename); + $rel = $real !== false + ? str_replace(DIRECTORY_SEPARATOR, '/', substr($real, strlen(rtrim($projectRoot, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR))) + : null; + + // Affected files must re-execute. + if ($rel !== null && isset($this->affectedFiles[$rel])) { + return null; + } + + // Unknown files (not in graph) must execute — they're new. + if ($rel === null || ! $this->replayGraph->knowsTest($rel)) { + return null; + } + + // Known + unaffected: return cached result if we have one for this + // branch (falls back to main if branch is fresh). + return $this->replayGraph->getResult($this->branch, $testId); + } + /** * {@inheritDoc} */ @@ -211,7 +264,7 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable $graph = Graph::load($projectRoot, $cachePath) ?? new Graph($projectRoot); $graph->setFingerprint(Fingerprint::compute($projectRoot)); - $graph->setRecordedAtSha((new ChangedFiles($projectRoot))->currentSha()); + $graph->setRecordedAtSha($this->branch, (new ChangedFiles($projectRoot))->currentSha()); $graph->replaceEdges($perTest); $graph->pruneMissingTests(); @@ -242,6 +295,20 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable return $exitCode; } + // After a successful replay run, advance the recorded SHA to HEAD + // so the next run only diffs against what changed since NOW, not + // since the original recording. Without this, re-running `--tia` + // twice in a row would re-execute the same affected tests both + // times even though nothing new changed. + if ($this->replayRan) { + $this->bumpRecordedSha(); + } + + // Snapshot per-test results (status + message) from PHPUnit's result + // cache into our graph so future replay runs can faithfully reproduce + // pass/fail/skip/todo/incomplete for unaffected tests. + $this->snapshotTestResults(); + if ((string) Parallel::getGlobal(self::RECORDING_GLOBAL) !== '1') { return $exitCode; } @@ -257,7 +324,7 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable $graph = Graph::load($projectRoot, $cachePath) ?? new Graph($projectRoot); $graph->setFingerprint(Fingerprint::compute($projectRoot)); - $graph->setRecordedAtSha((new ChangedFiles($projectRoot))->currentSha()); + $graph->setRecordedAtSha($this->branch, (new ChangedFiles($projectRoot))->currentSha()); $merged = []; @@ -317,6 +384,11 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable // this point). $this->watchPatterns->useDefaults($projectRoot); + // Resolve current branch once per run so every baseline lookup uses + // the same key. Detached HEAD (or no git) falls back to `main` as + // the implicit branch identity. + $this->branch = (new ChangedFiles($projectRoot))->currentBranch() ?? 'main'; + $cachePath = self::cachePath(); $fingerprint = Fingerprint::compute($projectRoot); @@ -331,10 +403,11 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable if ($graph instanceof Graph) { $changedFiles = new ChangedFiles($projectRoot); + $branchSha = $graph->recordedAtSha($this->branch); if ($changedFiles->gitAvailable() - && $graph->recordedAtSha() !== null - && $changedFiles->since($graph->recordedAtSha()) === null) { + && $branchSha !== null + && $changedFiles->since($branchSha) === null) { $this->output->writeln( ' TIA recorded commit is no longer reachable — graph will be rebuilt.', ); @@ -355,6 +428,8 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable */ private function handleWorker(array $arguments, string $projectRoot, bool $recordingGlobal, bool $replayingGlobal): array { + $this->branch = (new ChangedFiles($projectRoot))->currentBranch() ?? 'main'; + if ($replayingGlobal) { // Replay in a worker: load the graph and the affected set that // the parent persisted, then install the per-file filter so @@ -435,7 +510,15 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable return $arguments; } - $changed = $changedFiles->since($graph->recordedAtSha()) ?? []; + $changed = $changedFiles->since($graph->recordedAtSha($this->branch)) ?? []; + + // Drop files whose content hash matches the last-run snapshot. This + // is the "dirty but identical" filter: if a file is uncommitted but + // its content hasn't moved since the last `--tia` invocation, its + // dependents already re-ran last time and don't need re-running + // again. + $changed = $changedFiles->filterUnchangedSinceLastRun($changed, $graph->lastRunTree($this->branch)); + $affected = $changed === [] ? [] : $graph->affected($changed); $totalKnown = count($graph->allTestFiles()); @@ -445,13 +528,13 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable $testSuite = TestSuite::getInstance(); $affectedSet = array_fill_keys($affected, true); - if (! Parallel::isEnabled()) { - $testSuite->tests->addTestCaseFilter( - new TiaTestCaseFilter($projectRoot, $graph, $affectedSet), - ); + $this->replayRan = true; + $this->replayGraph = $graph; + $this->affectedFiles = $affectedSet; + if (! Parallel::isEnabled()) { $this->output->writeln(sprintf( - ' TIA %d changed file(s) → %d affected, %d cached.', + ' TIA %d changed file(s) → %d affected, %d replayed.', count($changed), $affectedCount, $cachedCount, @@ -659,6 +742,80 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable return $out; } + /** + * After a successful replay, bump the graph's `recorded_at_sha` to the + * current HEAD. This way the next `--tia` run diffs only against what + * changed since THIS run, not since the original recording. + * + * The graph edges themselves are untouched — only the SHA marker moves. + */ + /** + * After a successful replay, advance the baseline: bump `recorded_at_sha` + * to the current HEAD (handles committed changes) and snapshot the + * working tree's content hashes (handles uncommitted changes). Next run + * compares against this baseline so identical files are skipped even if + * git still reports them as modified. + */ + private function bumpRecordedSha(): void + { + $projectRoot = TestSuite::getInstance()->rootPath; + $cachePath = self::cachePath(); + + $graph = Graph::load($projectRoot, $cachePath); + + if (! $graph instanceof Graph) { + return; + } + + $changedFiles = new ChangedFiles($projectRoot); + $currentSha = $changedFiles->currentSha(); + + if ($currentSha !== null) { + $graph->setRecordedAtSha($this->branch, $currentSha); + } + + // Snapshot the working tree: hash every currently-modified file. + // On next run, files still appearing as modified but whose hash + // matches this snapshot are treated as unchanged. + $workingTreeFiles = $changedFiles->since($currentSha) ?? []; + $graph->setLastRunTree($this->branch, $changedFiles->snapshotTree($workingTreeFiles)); + + $graph->save($cachePath); + } + + /** + * Merges per-test status + message from the `ResultCollector` into the + * TIA graph. Runs after every `--tia` invocation so the graph always has + * fresh results for faithful replay (pass, fail, skip, todo, etc.). + */ + private function snapshotTestResults(): void + { + /** @var ResultCollector $collector */ + $collector = Container::getInstance()->get(ResultCollector::class); + + $results = $collector->all(); + + if ($results === []) { + return; + } + + $cachePath = self::cachePath(); + $projectRoot = TestSuite::getInstance()->rootPath; + + $graph = Graph::load($projectRoot, $cachePath); + + if (! $graph instanceof Graph) { + return; + } + + foreach ($results as $testId => $result) { + $graph->setResult($this->branch, $testId, $result['status'], $result['message'], $result['time']); + } + + $graph->save($cachePath); + $collector->reset(); + } + private function coverageReportActive(): bool { try { diff --git a/src/Plugins/Tia/CachedTestResult.php b/src/Plugins/Tia/CachedTestResult.php new file mode 100644 index 00000000..d63bec73 --- /dev/null +++ b/src/Plugins/Tia/CachedTestResult.php @@ -0,0 +1,33 @@ +status === 0; + } +} diff --git a/src/Plugins/Tia/ChangedFiles.php b/src/Plugins/Tia/ChangedFiles.php index 18571ada..226513fe 100644 --- a/src/Plugins/Tia/ChangedFiles.php +++ b/src/Plugins/Tia/ChangedFiles.php @@ -32,6 +32,84 @@ final readonly class ChangedFiles * from HEAD (rebase / force-push) — in * that case the graph should be rebuilt. */ + /** + * Removes files whose current content hash matches the snapshot from the + * last `--tia` run. Used to ignore "dirty but unchanged" files — a file + * that git still reports as modified but whose content is bit-identical + * to the previous TIA invocation. + * + * @param array $files project-relative paths. + * @param array $lastRunTree path → content hash from last run. + * @return array + */ + public function filterUnchangedSinceLastRun(array $files, array $lastRunTree): array + { + if ($lastRunTree === []) { + return $files; + } + + $remaining = []; + + foreach ($files as $file) { + if (! isset($lastRunTree[$file])) { + $remaining[] = $file; + + continue; + } + + $absolute = $this->projectRoot.DIRECTORY_SEPARATOR.$file; + + if (! is_file($absolute)) { + // File deleted since last run — definitely changed. + $remaining[] = $file; + + continue; + } + + $hash = @hash_file('xxh128', $absolute); + + if ($hash === false || $hash !== $lastRunTree[$file]) { + $remaining[] = $file; + } + } + + return $remaining; + } + + /** + * Computes content hashes for the given project-relative files. Used to + * snapshot the working tree after a successful run so the next run can + * detect which files are actually different. + * + * @param array $files + * @return array path → xxh128 content hash + */ + public function snapshotTree(array $files): array + { + $out = []; + + foreach ($files as $file) { + $absolute = $this->projectRoot.DIRECTORY_SEPARATOR.$file; + + if (! is_file($absolute)) { + continue; + } + + $hash = @hash_file('xxh128', $absolute); + + if ($hash !== false) { + $out[$file] = $hash; + } + } + + return $out; + } + + /** + * @return array|null `null` when git is unavailable, or when + * the recorded SHA is no longer reachable + * from HEAD (rebase / force-push). + */ public function since(?string $sha): ?array { if (! $this->gitAvailable()) { @@ -87,6 +165,24 @@ final readonly class ChangedFiles return false; } + public function currentBranch(): ?string + { + if (! $this->gitAvailable()) { + return null; + } + + $process = new Process(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], $this->projectRoot); + $process->run(); + + if (! $process->isSuccessful()) { + return null; + } + + $branch = trim($process->getOutput()); + + return $branch === '' || $branch === 'HEAD' ? null : $branch; + } + public function gitAvailable(): bool { $process = new Process(['git', 'rev-parse', '--git-dir'], $this->projectRoot); diff --git a/src/Plugins/Tia/Graph.php b/src/Plugins/Tia/Graph.php index dcbf4502..02102df8 100644 --- a/src/Plugins/Tia/Graph.php +++ b/src/Plugins/Tia/Graph.php @@ -47,9 +47,22 @@ final class Graph private array $fingerprint = []; /** - * Commit SHA the graph was recorded against (if in a git repo). + * Per-branch baselines. Each branch independently tracks: + * - `sha` — last HEAD at which `--tia` ran on this branch + * - `tree` — content hashes of modified files at that point + * - `results` — per-test status + message + time + * + * Graph edges (test → source) stay shared across branches because + * structure doesn't change per branch. Only run-state is per-branch so + * a failing test on one branch doesn't poison another branch's replay. + * + * @var array, + * results: array + * }> */ - private ?string $recordedAtSha = null; + private array $baselines = []; /** * Canonicalised project root. Resolved through `realpath()` so paths @@ -224,14 +237,84 @@ final class Graph return $this->fingerprint; } - public function setRecordedAtSha(?string $sha): void + /** + * Returns the SHA the given branch last ran against, or falls back to + * `$fallbackBranch` (typically `main`) when this branch has no baseline + * yet. That way a freshly-created feature branch inherits main's + * baseline on its first run. + */ + public function recordedAtSha(string $branch, string $fallbackBranch = 'main'): ?string { - $this->recordedAtSha = $sha; + $baseline = $this->baselineFor($branch, $fallbackBranch); + + return $baseline['sha']; } - public function recordedAtSha(): ?string + public function setRecordedAtSha(string $branch, ?string $sha): void { - return $this->recordedAtSha; + $this->ensureBaseline($branch); + $this->baselines[$branch]['sha'] = $sha; + } + + public function setResult(string $branch, string $testId, int $status, string $message, float $time): void + { + $this->ensureBaseline($branch); + $this->baselines[$branch]['results'][$testId] = [ + 'status' => $status, 'message' => $message, 'time' => $time, + ]; + } + + public function getResult(string $branch, string $testId, string $fallbackBranch = 'main'): ?CachedTestResult + { + $baseline = $this->baselineFor($branch, $fallbackBranch); + + if (! isset($baseline['results'][$testId])) { + return null; + } + + $r = $baseline['results'][$testId]; + + return new CachedTestResult($r['status'], $r['message'], $r['time']); + } + + /** + * @param array $tree project-relative path → content hash + */ + public function setLastRunTree(string $branch, array $tree): void + { + $this->ensureBaseline($branch); + $this->baselines[$branch]['tree'] = $tree; + } + + /** + * @return array + */ + public function lastRunTree(string $branch, string $fallbackBranch = 'main'): array + { + return $this->baselineFor($branch, $fallbackBranch)['tree']; + } + + /** + * @return array{sha: ?string, tree: array, results: array} + */ + private function baselineFor(string $branch, string $fallbackBranch): array + { + if (isset($this->baselines[$branch])) { + return $this->baselines[$branch]; + } + + if ($branch !== $fallbackBranch && isset($this->baselines[$fallbackBranch])) { + return $this->baselines[$fallbackBranch]; + } + + return ['sha' => null, 'tree' => [], 'results' => []]; + } + + private function ensureBaseline(string $branch): void + { + if (! isset($this->baselines[$branch])) { + $this->baselines[$branch] = ['sha' => null, 'tree' => [], 'results' => []]; + } } /** @@ -296,10 +379,10 @@ final class Graph $graph = new self($projectRoot); $graph->fingerprint = is_array($data['fingerprint'] ?? null) ? $data['fingerprint'] : []; - $graph->recordedAtSha = is_string($data['recorded_at_sha'] ?? null) ? $data['recorded_at_sha'] : null; $graph->files = is_array($data['files'] ?? null) ? array_values($data['files']) : []; $graph->fileIds = array_flip($graph->files); $graph->edges = is_array($data['edges'] ?? null) ? $data['edges'] : []; + $graph->baselines = is_array($data['baselines'] ?? null) ? $data['baselines'] : []; return $graph; } @@ -315,9 +398,9 @@ final class Graph $payload = [ 'schema' => 1, 'fingerprint' => $this->fingerprint, - 'recorded_at_sha' => $this->recordedAtSha, 'files' => $this->files, 'edges' => $this->edges, + 'baselines' => $this->baselines, ]; $tmp = $path.'.'.bin2hex(random_bytes(4)).'.tmp'; diff --git a/src/Plugins/Tia/ResultCollector.php b/src/Plugins/Tia/ResultCollector.php new file mode 100644 index 00000000..acf8ebbe --- /dev/null +++ b/src/Plugins/Tia/ResultCollector.php @@ -0,0 +1,119 @@ + + */ + private array $results = []; + + private ?string $currentTestId = null; + + private ?float $startTime = null; + + public function testPrepared(string $testId): void + { + $this->currentTestId = $testId; + $this->startTime = microtime(true); + } + + public function testPassed(): void + { + if ($this->currentTestId === null) { + return; + } + + $this->record(0, ''); + } + + public function testFailed(string $message): void + { + if ($this->currentTestId === null) { + return; + } + + $this->record(7, $message); + } + + public function testErrored(string $message): void + { + if ($this->currentTestId === null) { + return; + } + + $this->record(8, $message); + } + + public function testSkipped(string $message): void + { + if ($this->currentTestId === null) { + return; + } + + $this->record(1, $message); + } + + public function testIncomplete(string $message): void + { + if ($this->currentTestId === null) { + return; + } + + $this->record(2, $message); + } + + public function testRisky(string $message): void + { + if ($this->currentTestId === null) { + return; + } + + $this->record(5, $message); + } + + /** + * @return array + */ + public function all(): array + { + return $this->results; + } + + public function reset(): void + { + $this->results = []; + $this->currentTestId = null; + $this->startTime = null; + } + + private function record(int $status, string $message): void + { + if ($this->currentTestId === null) { + return; + } + + $time = $this->startTime !== null + ? round(microtime(true) - $this->startTime, 3) + : 0.0; + + $this->results[$this->currentTestId] = [ + 'status' => $status, + 'message' => $message, + 'time' => $time, + ]; + + $this->currentTestId = null; + $this->startTime = null; + } +} diff --git a/src/Subscribers/EnsureTiaResultsAreCollected.php b/src/Subscribers/EnsureTiaResultsAreCollected.php new file mode 100644 index 00000000..4643c2bb --- /dev/null +++ b/src/Subscribers/EnsureTiaResultsAreCollected.php @@ -0,0 +1,86 @@ +test(); + + if ($test instanceof TestMethod) { + $this->collector->testPrepared($test->className().'::'.$test->methodName()); + } + + return; + } + + if ($event instanceof Passed) { + $this->collector->testPassed(); + + return; + } + + if ($event instanceof Failed) { + $this->collector->testFailed($event->throwable()->message()); + + return; + } + + if ($event instanceof Errored) { + $this->collector->testErrored($event->throwable()->message()); + + return; + } + + if ($event instanceof Skipped) { + $this->collector->testSkipped($event->message()); + + return; + } + + if ($event instanceof MarkedIncomplete) { + $this->collector->testIncomplete($event->throwable()->message()); + + return; + } + + // Last possible type: ConsideredRisky (all others returned above). + $this->collector->testRisky($event->message()); // @phpstan-ignore method.notFound + } +} diff --git a/tests/Arch.php b/tests/Arch.php index 3eca267a..56c45107 100644 --- a/tests/Arch.php +++ b/tests/Arch.php @@ -37,6 +37,7 @@ arch('contracts') ->toOnlyUse([ 'NunoMaduro\Collision\Contracts', 'Pest\Factories\TestCaseMethodFactory', + 'Pest\Plugins\Tia\CachedTestResult', 'Symfony\Component\Console', 'Pest\Arch\Contracts', 'Pest\PendingCalls',