diff --git a/src/Pest.php b/src/Pest.php index 0c54a2c2..30f69d33 100644 --- a/src/Pest.php +++ b/src/Pest.php @@ -6,7 +6,7 @@ namespace Pest; function version(): string { - return '4.6.3'; + return '4.7.0'; } function testDirectory(string $file = ''): string diff --git a/src/Plugins/Tia.php b/src/Plugins/Tia.php index e4a64b33..6e6a83d8 100644 --- a/src/Plugins/Tia.php +++ b/src/Plugins/Tia.php @@ -1362,7 +1362,16 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable /** @var ResultCollector $collector */ $collector = Container::getInstance()->get(ResultCollector::class); - foreach ($collector->all() as $testId => $result) { + $results = $collector->all(); + $touchedFiles = []; + + foreach ($results as $testId => $result) { + $file = $result['file'] ?? null; + + if (is_string($file) && $file !== '') { + $touchedFiles[$file] = true; + } + $graph->setResult( $this->branch, $testId, @@ -1370,10 +1379,12 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable $result['message'], $result['time'], $result['assertions'], - $result['file'] ?? null, + $file, ); } + $graph->pruneStaleResults($this->branch, array_keys($touchedFiles), array_keys($results)); + $collector->reset(); } @@ -1396,6 +1407,8 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable return; } + $touchedFiles = []; + foreach ($results as $testId => $result) { $file = $result['file'] ?? null; @@ -1403,6 +1416,10 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable $file = $this->resolveFailedTestFile($testId); } + if (is_string($file) && $file !== '') { + $touchedFiles[$file] = true; + } + $graph->setResult( $this->branch, $testId, @@ -1414,6 +1431,8 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable ); } + $graph->pruneStaleResults($this->branch, array_keys($touchedFiles), array_keys($results)); + $this->saveGraph($graph); $collector->reset(); } diff --git a/src/Plugins/Tia/BaselineSync.php b/src/Plugins/Tia/BaselineSync.php index c291b6c2..5b147883 100644 --- a/src/Plugins/Tia/BaselineSync.php +++ b/src/Plugins/Tia/BaselineSync.php @@ -69,11 +69,6 @@ final readonly class BaselineSync $this->output->writeln(sprintf(' ─ %s', $text)); } - private function renderChildContinuation(string $text): void - { - $this->output->writeln(sprintf(' %s', $text)); - } - public function fetchIfAvailable(string $projectRoot, bool $force = false, bool $hasAnchor = false): bool { $repo = $this->detectGitHubRepo($projectRoot); diff --git a/src/Plugins/Tia/Fingerprint.php b/src/Plugins/Tia/Fingerprint.php index da860b93..161bba67 100644 --- a/src/Plugins/Tia/Fingerprint.php +++ b/src/Plugins/Tia/Fingerprint.php @@ -195,49 +195,6 @@ final readonly class Fingerprint return $parts === [] ? null : hash('xxh128', implode("\n", $parts)); } - private static function packageJsonHash(string $projectRoot): ?string - { - $path = $projectRoot.'/package.json'; - - if (! is_file($path)) { - return null; - } - - $raw = @file_get_contents($path); - - if ($raw === false) { - return null; - } - - $data = json_decode($raw, true); - - if (! is_array($data)) { - $hash = @hash_file('xxh128', $path); - - return $hash === false ? null : $hash; - } - - $relevant = [ - 'type' => $data['type'] ?? null, - 'packageManager' => $data['packageManager'] ?? null, - 'dependencies' => $data['dependencies'] ?? null, - 'devDependencies' => $data['devDependencies'] ?? null, - 'optionalDependencies' => $data['optionalDependencies'] ?? null, - 'peerDependencies' => $data['peerDependencies'] ?? null, - 'overrides' => $data['overrides'] ?? null, - 'resolutions' => $data['resolutions'] ?? null, - 'imports' => $data['imports'] ?? null, - 'exports' => $data['exports'] ?? null, - 'browser' => $data['browser'] ?? null, - ]; - - self::sortRecursively($relevant); - - $json = json_encode($relevant); - - return $json === false ? null : hash('xxh128', $json); - } - private static function composerLockHash(string $projectRoot): ?string { return self::trackedHash($projectRoot, 'composer.lock'); @@ -292,7 +249,7 @@ final readonly class Fingerprint return $cache[$key] = true; } - $finder = (new Finder()) + $finder = (new Finder) ->in($projectRoot) ->depth('== 0') ->name($relativePath) @@ -301,53 +258,6 @@ final readonly class Fingerprint return $cache[$key] = $finder->hasResults(); } - private static function composerJsonHash(string $projectRoot): ?string - { - $path = $projectRoot.'/composer.json'; - - if (! is_file($path)) { - return null; - } - - $raw = @file_get_contents($path); - - if ($raw === false) { - return null; - } - - $data = json_decode($raw, true); - - if (! is_array($data)) { - $hash = @hash_file('xxh128', $path); - - return $hash === false ? null : $hash; - } - - $config = is_array($data['config'] ?? null) ? $data['config'] : []; - $relevantConfig = array_intersect_key($config, [ - 'platform' => true, - 'allow-plugins' => true, - ]); - - $relevant = [ - 'autoload' => $data['autoload'] ?? null, - 'autoload-dev' => $data['autoload-dev'] ?? null, - 'require' => $data['require'] ?? null, - 'require-dev' => $data['require-dev'] ?? null, - 'extra' => $data['extra'] ?? null, - 'repositories' => $data['repositories'] ?? null, - 'minimum-stability' => $data['minimum-stability'] ?? null, - 'prefer-stable' => $data['prefer-stable'] ?? null, - 'config' => $relevantConfig === [] ? null : $relevantConfig, - ]; - - self::sortRecursively($relevant); - - $json = json_encode($relevant); - - return $json === false ? null : hash('xxh128', $json); - } - private static function sortRecursively(mixed &$value): void { if (! is_array($value)) { diff --git a/src/Plugins/Tia/Graph.php b/src/Plugins/Tia/Graph.php index 6da50f8b..a91dc919 100644 --- a/src/Plugins/Tia/Graph.php +++ b/src/Plugins/Tia/Graph.php @@ -1321,6 +1321,49 @@ final class Graph } } + /** + * Prune baseline result entries whose test files were just executed but whose + * test IDs are no longer present (e.g. the test method was removed or renamed). + * + * @param array $touchedFiles Absolute or project-relative paths. + * @param array $keepTestIds Test IDs that produced a result this run. + */ + public function pruneStaleResults(string $branch, array $touchedFiles, array $keepTestIds): void + { + if (! isset($this->baselines[$branch]['results'])) { + return; + } + + $touched = []; + foreach ($touchedFiles as $file) { + $rel = $this->relative($file); + + if ($rel !== null) { + $touched[$rel] = true; + } + } + + if ($touched === []) { + return; + } + + $keep = array_fill_keys($keepTestIds, true); + + foreach ($this->baselines[$branch]['results'] as $testId => $result) { + $file = $result['file'] ?? null; + + if (! is_string($file) || ! isset($touched[$file])) { + continue; + } + + if (isset($keep[$testId])) { + continue; + } + + unset($this->baselines[$branch]['results'][$testId]); + } + } + public static function decode(string $json, string $projectRoot): ?self { $data = json_decode($json, true);