From 1f79660add8d775ecf85be3e0d953902d53479da Mon Sep 17 00:00:00 2001 From: nuno maduro Date: Fri, 7 Aug 2026 01:55:34 +0100 Subject: [PATCH] wip --- src/Plugins/Tia.php | 89 +------------------ src/Plugins/Tia/ChangedFiles.php | 17 +--- src/Plugins/Tia/Enums/ReplayType.php | 4 - src/Plugins/Tia/Graph.php | 64 +------------ src/Plugins/Tia/Recorder.php | 9 -- src/Plugins/Tia/ResultCollector.php | 16 ---- tests/Features/Tia/BranchShapes.php | 9 -- tests/Features/Tia/CoveragePiggyback.php | 17 ---- tests/Features/Tia/DefaultBranchWriteTier.php | 3 - tests/Features/Tia/HostileState.php | 5 -- tests/Features/Tia/IssueStatuses.php | 7 -- tests/Features/Tia/RemoteBaseline.php | 13 --- tests/Features/Tia/SelectionPaths.php | 15 ---- tests/Features/Tia/StateReclamation.php | 17 ---- tests/Fixtures/Tia/GitRepo.php | 4 - tests/Fixtures/Tia/Project.php | 27 ------ tests/Fixtures/Tia/stubs/gh | 3 - tests/Pest.php | 3 - 18 files changed, 6 insertions(+), 316 deletions(-) diff --git a/src/Plugins/Tia.php b/src/Plugins/Tia.php index 1cbdac95..73b6ad66 100644 --- a/src/Plugins/Tia.php +++ b/src/Plugins/Tia.php @@ -124,18 +124,14 @@ final class Tia implements AddsOutput, HandlesArguments, HandlesOriginalArgument '--compact', '--ci-build-id', '--min', ]; - /** - * @var list - */ + /** @var list */ private const array COVERAGE_REPORT_FLAGS = [ '--coverage-clover', '--coverage-cobertura', '--coverage-crap4j', '--coverage-html', '--coverage-openclover', '--coverage-php', '--coverage-text', '--coverage-xml', ]; - /** - * @var list - */ + /** @var list */ private const array PARTIAL_SELECTION_FLAGS = [ '--filter', '--exclude-filter', '--group', '--exclude-group', '--covers', '--uses', '--testsuite', '--exclude-testsuite', '--test-suffix', @@ -160,19 +156,10 @@ final class Tia implements AddsOutput, HandlesArguments, HandlesOriginalArgument /** @var array */ private array $cachedAssertionsByTestId = []; - /** - * The status a replayed test was replayed *as*, so the write-back records - * what was cached rather than what the replay looked like from the - * outside. A cached deprecation replays as a pass — recording that pass - * would erase the deprecation from the baseline on the very next run. - * - * @var array - */ + /** @var array */ private array $cachedStatusByTestId = []; - /** - * @var array - */ + /** @var array */ private array $cachedTimeByTestId = []; private ?Graph $replayGraph = null; @@ -272,11 +259,6 @@ final class Tia implements AddsOutput, HandlesArguments, HandlesOriginalArgument return $graph; } - /** - * Drop a graph that will not decode, so the next run that can record starts - * clean instead of tripping over the same file forever — rebuilding needs a - * coverage driver, and without one the file would stay corrupt for good. - */ private function discardUnreadableGraph(): void { if (Parallel::isWorker()) { @@ -297,16 +279,6 @@ final class Tia implements AddsOutput, HandlesArguments, HandlesOriginalArgument $this->renderBadge('WARN', 'The dependency graph could not be read — it will be rebuilt.'); } - /** - * Delete a state file, unless this checkout may not write. - * - * A detached HEAD names no branch, so {@see self::saveGraph()} refuses to - * write — which means anything deleted here could never be rebuilt from - * this checkout. Read-only has to mean deletes too, or a drifted - * `composer.lock` on a detached CI checkout wipes the whole team's baseline. - * - * @return bool Whether the delete happened. - */ private function deleteState(string $key): bool { if ($this->detachedHead) { @@ -318,9 +290,6 @@ final class Tia implements AddsOutput, HandlesArguments, HandlesOriginalArgument private function saveGraph(Graph $graph): bool { - // A detached HEAD names no branch of its own, so `$this->branch` is the - // fallback — writing here would land this checkout's results in the - // default branch's baseline. Leave the graph exactly as it was. if ($this->detachedHead) { return true; } @@ -580,9 +549,6 @@ final class Tia implements AddsOutput, HandlesArguments, HandlesOriginalArgument $this->flushWorkerReplay(); } - // `terminate()` also runs from the shutdown handler, which is how a run - // that `exit()`s inside a test gets here — with a test prepared and - // never finished, and so with no right to a complete write. if ($this->writesSuppressed || $this->resultsOnlyWrites || $this->hasUnfinishedTest()) { $this->recorder->reset(); $this->coverageCollector->reset(); @@ -695,16 +661,6 @@ final class Tia implements AddsOutput, HandlesArguments, HandlesOriginalArgument return $exitCode; } - // Re-anchor the baseline. Reaching here means the run was complete — - // nothing suppressed, narrowed or truncated it — so its results are the - // truth at HEAD and the recorded revision may say so. - // - // That matters most when the recorded commit had become unreachable (a - // rebase, a force-push) and no coverage driver was available to rebuild: - // without this the stale revision survives, and every later run warns - // and re-runs the whole suite, for good. Stale edges are no objection — - // a complete run just re-recorded every result, and later changes are - // compared against the revision written here. if ($this->replayRan || $this->graphUnreachable) { $this->bumpRecordedSha(); } @@ -852,9 +808,6 @@ final class Tia implements AddsOutput, HandlesArguments, HandlesOriginalArgument try { $this->resolveBranch($projectRoot); } catch (MissingDependency $missingGit) { - // Every git call TIA makes fails on `HEAD` in a repository that has - // no commits yet, which reads as "git is missing" when git is right - // there. Say what is actually wrong instead. $repository = new ChangedFiles($projectRoot); if ($repository->isRepository() && ! $repository->hasCommits()) { @@ -913,21 +866,12 @@ final class Tia implements AddsOutput, HandlesArguments, HandlesOriginalArgument $this->state->write(self::KEY_COVERAGE_MARKER, ''); } - // An active coverage report owns the driver, so edges have to be - // piggybacked off its session — and that session is scoped to - // phpunit.xml's , not to the whole project. Refreshing an - // existing graph that way is safe (`replaceEdges()` keeps what it - // already has), but *founding* one on it is not: every source file - // outside the coverage scope would be missing from the graph for good, - // and a change to one of them would select nothing and replay a pass. if (! $graph instanceof Graph && $this->piggybackCoverage) { $this->emitCoverageScopedRecordSkipped(); return $arguments; } - // Past the guard above, a coverage-owned run always has a graph to - // refresh — a run without one never gets here. if ($coverageCacheOwned && ! $this->state->exists(self::KEY_COVERAGE_CACHE)) { if ($this->driftLabel === null) { $this->freshGraphReason = 'recording a coverage baseline'; @@ -1392,15 +1336,6 @@ final class Tia implements AddsOutput, HandlesArguments, HandlesOriginalArgument $this->renderChild('Install / enable pcov or xdebug (mode: coverage) in the worker PHP and rerun.'); } - /** - * A parallel run keeps its results in the workers, so the parent's collector - * is empty and nothing would ever reach the graph. Ask the workers to flush - * what they ran, so a parallel run refreshes — and prunes — exactly like the - * sequential run of the same command. - * - * Gated on a graph already existing: a project that has never run TIA must - * not gain a baseline from a plain `--parallel` run. - */ private function requestWorkerResults(): void { if (Parallel::isWorker() || ! Parallel::isEnabled() || $this->writesSuppressed) { @@ -1772,16 +1707,8 @@ final class Tia implements AddsOutput, HandlesArguments, HandlesOriginalArgument $collector->reset(); } - /** - * Give back what the graph no longer needs. Only ever called from a - * complete write — the RESULTS-ONLY and HARD-SUPPRESSED tiers may not - * remove an entry, and a narrowed run has not seen enough to judge. - */ private function reclaim(Graph $graph): void { - // The fallback branch never layers under itself, so marking it would - // write the graph for no reader's benefit — and cost a clean green run - // its "wrote nothing at all". if ($this->branch !== $this->fallbackBranch) { $graph->markBaselineComplete($this->branch); } @@ -1795,10 +1722,6 @@ final class Tia implements AddsOutput, HandlesArguments, HandlesOriginalArgument return; } - // A shallow, single-branch CI checkout can see almost no refs, and - // "git has never heard of it" would then mean "this clone is narrow", - // not "that branch is gone". Only reclaim from a checkout that can at - // least see the branch everything else falls back to. if (! in_array($this->fallbackBranch, $branches, true)) { return; } @@ -1966,10 +1889,6 @@ final class Tia implements AddsOutput, HandlesArguments, HandlesOriginalArgument return TestResultFacade::shouldStop(); } - /** - * A test that was prepared and never finished means this process is being - * torn down mid-file, so it has not seen enough of that file to prune it. - */ private function hasUnfinishedTest(): bool { $collector = Container::getInstance()->get(ResultCollector::class); diff --git a/src/Plugins/Tia/ChangedFiles.php b/src/Plugins/Tia/ChangedFiles.php index 35c1809a..805400f4 100644 --- a/src/Plugins/Tia/ChangedFiles.php +++ b/src/Plugins/Tia/ChangedFiles.php @@ -244,11 +244,7 @@ final readonly class ChangedFiles } /** - * Every branch name this checkout knows, local and remote alike. Remotes - * count: a branch that only lives on the origin is still a branch someone - * will check out, and its baseline must survive. - * - * @return list|null `null` when git cannot answer. + * @return list|null */ public function branchNames(): ?array { @@ -307,13 +303,6 @@ final readonly class ChangedFiles return $process->getExitCode() === 0; } - /** - * Whether this repository has a revision to anchor a baseline to. - * - * A freshly initialised repository has none, and every other git call TIA - * makes — {@see self::currentBranch()}, {@see self::currentSha()} — fails on - * `HEAD` there and reports git as missing, which it is not. - */ public function hasCommits(): bool { $process = new Process(['git', 'rev-parse', '--verify', '--quiet', 'HEAD'], $this->projectRoot); @@ -357,10 +346,6 @@ final readonly class ChangedFiles */ private function diffSinceSha(string $sha): array { - // `--no-renames` matters: with rename detection on, git reports only the - // destination of a moved file, so the path the graph has edges for — the - // one that is gone — never reaches selection, and every test that - // depended on it replays its recorded pass. $process = new Process( ['git', 'diff', '--name-only', '--no-renames', $sha.'..HEAD'], $this->projectRoot, diff --git a/src/Plugins/Tia/Enums/ReplayType.php b/src/Plugins/Tia/Enums/ReplayType.php index bd93a299..12217059 100644 --- a/src/Plugins/Tia/Enums/ReplayType.php +++ b/src/Plugins/Tia/Enums/ReplayType.php @@ -29,10 +29,6 @@ enum ReplayType $status->isRisky() => self::Risky, $status->isSkipped() => self::Skipped, $status->isIncomplete() => self::Incomplete, - // A recorded notice, deprecation or warning only reaches replay when - // the configured failOn* / displayDetailsOn* policies say it is not - // worth re-running — which means the test passed. Folding it into - // Failure below would turn a green run red on cache alone. $status->isNotice(), $status->isDeprecation(), $status->isWarning() => self::Pass, $status->isFailure(), $status->isError() => self::Failure, default => self::None, diff --git a/src/Plugins/Tia/Graph.php b/src/Plugins/Tia/Graph.php index 414e5ef0..80811fef 100644 --- a/src/Plugins/Tia/Graph.php +++ b/src/Plugins/Tia/Graph.php @@ -120,14 +120,6 @@ final class Graph } /** - * Keep only the test files this checkout actually has. - * - * A stale edge key — a test file a fetched baseline knew, or one another - * branch deleted — cannot be run by anyone, and selecting it strands - * `--filtered` on a run that matches nothing and reports success on a - * change no test looked at. {@see self::testFilesToRerun()} has always - * dropped these; the change-driven half of selection must agree. - * * @param array $testFiles Project-relative paths. * @return list */ @@ -677,9 +669,6 @@ final class Graph $r = $baseline['results'][$testId]; - // A status this build does not know — a graph written by a newer Pest, - // or a corrupt one — is not a result. Returning null re-executes the - // test rather than replaying an outcome nobody can interpret. return match ($r['status']) { 0 => TestStatus::success(), 1 => TestStatus::skipped($r['message']), @@ -717,8 +706,6 @@ final class Graph $rel = $this->relative($file); - // A test file that is no longer on disk cannot be re-run by anyone, - // so selecting it would only widen the run for nothing. if ($rel !== null && is_file($this->projectRoot.'/'.$rel)) { $files[$rel] = true; } @@ -727,15 +714,6 @@ final class Graph return array_keys($files); } - /** - * Whether a cached result due a re-run names a test file this project - * cannot address — an empty path, or one that resolves outside the project - * root. Those are genuinely lost, so the caller widens to the full suite. - * - * A path that resolves fine but is simply absent is *deleted*, not lost: - * widening would not run it either, and treating it as unlocated used to - * strand `--filtered` on a full replay for good. - */ public function hasUnlocatedTestsToRerun(string $branch, ?string $fallbackBranch = null): bool { $baseline = $this->baselineFor($branch, $fallbackBranch); @@ -852,24 +830,6 @@ final class Graph } /** - * The baseline a read sees for this branch: its own entries layered over the - * default branch's, so a key minted by a narrowed run — which only holds the - * handful of tests that ran — does not shadow the fallback for everything else. - * - * Once this branch has had a complete run, the layering becomes per *file* - * rather than per test id: the branch's entries for a file it executed are - * the whole truth, so the fallback's entries for that same file are dropped - * rather than merged. Without that, a test the branch renamed or removed — - * and {@see self::pruneStaleResults()} therefore unset — is resurrected by - * the default branch on the very next read, and never stops coming back. - * - * A branch whose key was minted by a *narrowed* run holds only the handful - * of tests that ran, and has no business speaking for the rest of their - * file, so it keeps the per-test-id merge. - * - * Read-only: the layering never reaches `$this->baselines`, so writes stay on - * the branch that ran. - * * @return array{sha: ?string, tree: array, complete?: bool, results: array} */ private function baselineFor(string $branch, ?string $fallbackBranch): array @@ -1512,12 +1472,6 @@ final class Graph } } - /** - * Record that this branch has run the whole suite at least once, which is - * what lets {@see self::baselineFor()} treat its entries as authoritative - * for the files they cover. Never mints a key: a run that recorded nothing - * has nothing to be authoritative about. - */ public function markBaselineComplete(string $branch): void { if (isset($this->baselines[$branch])) { @@ -1525,12 +1479,6 @@ final class Graph } } - /** - * Drop this branch's result entries whose test file is no longer on disk. - * - * Without this nothing but `--fresh` ever reclaims them, and a *failing* - * one keeps `--filtered` widened to a full replay on every later run. - */ public function pruneResultsForMissingFiles(string $branch): void { if (! isset($this->baselines[$branch]['results'])) { @@ -1561,10 +1509,7 @@ final class Graph } /** - * Drop baselines for branches git no longer knows, so the graph does not - * carry one full copy of the suite per branch ever created. - * - * @param array $keep Branch names that must survive. + * @param array $keep */ public function pruneMissingBranches(array $keep): void { @@ -1722,11 +1667,6 @@ final class Graph } /** - * A graph is state on disk that any process may have written: a newer Pest, - * a half-finished write, a hand edit. Every branch, every entry and every - * field is checked here so that a malformed one is dropped rather than - * reaching a read path and taking the run down with it. - * * @return array, complete?: bool, results: array}> */ private static function decodeBaselines(mixed $section): array @@ -1738,8 +1678,6 @@ final class Graph $baselines = []; foreach ($section as $key => $baseline) { - // A branch named `12345` decodes as an integer key, and must not be - // mistaken for a malformed one. $branch = (string) $key; if ($branch === '') { diff --git a/src/Plugins/Tia/Recorder.php b/src/Plugins/Tia/Recorder.php index e1156900..98e8b79c 100644 --- a/src/Plugins/Tia/Recorder.php +++ b/src/Plugins/Tia/Recorder.php @@ -359,15 +359,6 @@ final class Recorder continue; } - // A file whose *only* executed line is its last one was included, - // not used — the trailing line of an include is all that ran. - // - // That reading only holds for a driver that reports unexecuted - // lines too: pcov returns every executable line (`-1` for the ones - // that did not run), so "the single covered line is the highest - // line reported" means something. Xdebug reports executed lines - // only, where it is true of *any* file that ran a single line — - // which is most of them, and dropping those loses the edge. $lineKeys = array_keys($lines); $reportsUnexecutedLines = count($covered) < count($lines); diff --git a/src/Plugins/Tia/ResultCollector.php b/src/Plugins/Tia/ResultCollector.php index 22ccb916..48f47d85 100644 --- a/src/Plugins/Tia/ResultCollector.php +++ b/src/Plugins/Tia/ResultCollector.php @@ -38,11 +38,6 @@ final class ResultCollector return; } - // PHPUnit reports a test that triggered a notice, deprecation or - // warning as passed, and emits Passed for it. Recording success here - // would erase the issue from the baseline, and a later replay under - // --fail-on-deprecation (and friends) would come back green where a - // fresh run fails. Keep the issue; only refresh what it cannot know. if (isset($this->triggered[$this->currentTestId])) { $this->refreshTime(); @@ -120,12 +115,6 @@ final class ResultCollector return $this->results; } - /** - * Whether a test was prepared but never finished — the process is being - * torn down in the middle of it (an `exit()` inside a test, a killed - * worker). What it collected is therefore a partial view of that test - * file, and must not license pruning the siblings it never reached. - */ public function hasUnfinishedTest(): bool { return $this->currentTestId !== null; @@ -164,11 +153,6 @@ final class ResultCollector $this->startTime = null; } - /** - * Record an issue raised while the test was running. The most important - * one wins, exactly as PHPUnit ranks them, so a deprecation does not - * shadow the warning that followed it — or the failure. - */ private function recordIssue(TestStatus $status): void { if ($this->currentTestId === null) { diff --git a/tests/Features/Tia/BranchShapes.php b/tests/Features/Tia/BranchShapes.php index a7a5f1e0..47471c8a 100644 --- a/tests/Features/Tia/BranchShapes.php +++ b/tests/Features/Tia/BranchShapes.php @@ -8,11 +8,6 @@ afterEach(function (): void { Project::destroyAll(); }); -/* - * Invariant 5 — writes land on the branch that ran and only there — and - * invariant 6 — nothing is unbounded — under every branch shape git allows. - */ - test('a branch name git allows is a branch key TIA can hold', function (string $branch): void { $project = Project::make('master'); $project->seed('master'); @@ -164,8 +159,6 @@ test('a project below the git repository root refuses to run and writes nothing' $project = Project::make('master'); $nested = $project->nested(); - // git addresses paths from the repository root while the graph is - // project-relative, so the two have to coincide. TIA says so and stops. $result = $project->pestIn($nested, '--tia', ...$arguments); expect($result->exitCode)->toBe(1, $result->describe()) @@ -180,8 +173,6 @@ test('a repository with no commits says so, and leaves plain runs alone', functi $project->git()->run(['checkout', '--quiet', '-b', 'master']); $project->git()->addOrigin(); - // Every git call TIA makes asks about HEAD, which does not exist yet. That - // used to surface as `requires "git"`, with git installed and working. $tia = $project->pest('--tia'); expect($tia->exitCode)->toBe(1, $tia->describe()) diff --git a/tests/Features/Tia/CoveragePiggyback.php b/tests/Features/Tia/CoveragePiggyback.php index 59711ac7..9b7319ea 100644 --- a/tests/Features/Tia/CoveragePiggyback.php +++ b/tests/Features/Tia/CoveragePiggyback.php @@ -8,19 +8,6 @@ afterEach(function (): void { Project::destroyAll(); }); -/* - * An active coverage report owns the coverage driver, so TIA cannot open a - * session of its own and has to piggyback on PHPUnit's — which is scoped to - * `phpunit.xml`'s , not to the whole project. Edges recorded that way - * are missing every source file outside that scope, and a change to one of them - * would select nothing and replay a pass. Invariant 3 at its most dangerous. - * - * The rows below assert what a coverage run may and may not leave behind. They - * are deliberately silent about exit codes and result counts: `--coverage` - * itself fails on an interpreter with no driver, so only the graph's fate is - * the same everywhere. - */ - test('a coverage report does not found a dependency graph', function (array $arguments): void { $project = Project::make('master'); @@ -41,16 +28,12 @@ test('a plain run after a coverage run records the whole project scope', functio $graph = $project->graph(); - // Nothing to assert without a driver: there is no graph either way, and the - // point of the row is that the *plain* run is the one that founds it. if ($graph === null) { expect($project->graphExists())->toBeFalse(); return; } - // Self-edges included — they are the first thing a coverage-scoped - // recording drops, since test files are not in . expect(array_keys($graph['edges']))->toEqualCanonicalizing(array_keys(Project::EDGES)) ->and($graph['files'])->toContain('tests/Unit/CalculatorTest.php') ->and($graph['files'])->toContain('app/Calculator.php'); diff --git a/tests/Features/Tia/DefaultBranchWriteTier.php b/tests/Features/Tia/DefaultBranchWriteTier.php index 9a239632..6bc6a2cc 100644 --- a/tests/Features/Tia/DefaultBranchWriteTier.php +++ b/tests/Features/Tia/DefaultBranchWriteTier.php @@ -75,9 +75,6 @@ test('filtered mode falls back to a full replay when a cached failure cannot be $project->seed('master', failing: ['adds two numbers']); - // A path this project cannot address at all — recorded on another machine. - // A path that merely no longer exists is a *deleted* test, not a lost one, - // and widening the run would not find it either; see StateReclamation. $project->mutateGraph(function (array $graph): array { $testId = Project::testId('tests/Unit/CalculatorTest.php', 'adds two numbers'); diff --git a/tests/Features/Tia/HostileState.php b/tests/Features/Tia/HostileState.php index 6e664d3e..f6b85a2f 100644 --- a/tests/Features/Tia/HostileState.php +++ b/tests/Features/Tia/HostileState.php @@ -8,11 +8,6 @@ afterEach(function (): void { Project::destroyAll(); }); -/* - * Invariant 7 — a hostile state dir cannot break a run. Whatever is in - * graph.json, the suite still runs and exits on the tests' merit. - */ - test('a graph mangled beyond use still lets the suite run', function (string $contents): void { $project = Project::make('master'); $project->seed('master'); diff --git a/tests/Features/Tia/IssueStatuses.php b/tests/Features/Tia/IssueStatuses.php index 343395f5..b6baf3d1 100644 --- a/tests/Features/Tia/IssueStatuses.php +++ b/tests/Features/Tia/IssueStatuses.php @@ -8,13 +8,6 @@ afterEach(function (): void { Project::destroyAll(); }); -/* - * A test that triggers a notice, deprecation or warning is reported by PHPUnit - * as passed, and emits Passed. Recording it as a plain success made the cache - * hide the issue: a later run under --fail-on-* came back green where a fresh - * run failed. Invariant 3 — replay is faithful — at its most dangerous. - */ - function tiaTriggering(string $call): string { return <<): array|null $mutator * @return array{0: Project, 1: array} */ @@ -52,8 +44,6 @@ test('a fetched baseline that will not decode is discarded rather than trusted', $result = $project->pestWithEnvironment($project->path(), $environment, '--tia', '--baselined'); - // Nothing may be replayed out of it. Whether the run then records a graph - // of its own depends on the coverage driver, so that is not asserted here. expect($result->exitCode)->toBe(0, $result->describe()) ->and($result->output)->toContain('The dependency graph could not be read') ->and($result->tally())->toContain(Project::TOTAL_TESTS.' passed') @@ -120,9 +110,6 @@ test('a network failure warns and lets the suite run', function (string $mode): test('no published baseline yet starts a cooldown, and a corrupt cooldown does not break the run', function (): void { [$project, $environment] = tiaPublishedBaseline('no-runs'); - // On a machine with a coverage driver each run below records a graph of its - // own, and a run that has a graph never reaches the fetch at all. Take it - // away between runs, so what is under test is the cooldown and nothing else. $discardGraph = function () use ($project): void { if ($project->graphExists()) { $project->detachGraph(); diff --git a/tests/Features/Tia/SelectionPaths.php b/tests/Features/Tia/SelectionPaths.php index 8106e448..6957b034 100644 --- a/tests/Features/Tia/SelectionPaths.php +++ b/tests/Features/Tia/SelectionPaths.php @@ -8,13 +8,6 @@ afterEach(function (): void { Project::destroyAll(); }); -/** - * The selection paths below are all driven from a *seeded* graph rather than a - * recorded one: Blade and Inertia edges are recorded through Laravel hooks the - * fixture project does not have, and a changed `.php` source file would trip - * the driverless full-suite fallback. Views and JS files are neither, so what - * `Graph::affected()` does with them is measurable on any interpreter. - */ function tiaSeedWithView(Project $project, string $view): void { $project->seed('master'); @@ -51,8 +44,6 @@ test('a committed rename selects the tests that depended on the old path', funct tiaSeedWithView($project, 'resources/views/greeting.blade.php'); - // git reports only the destination of a rename unless asked not to, so the - // path the graph holds an edge for is the one that must still show up. $project->git()->run(['mv', 'resources/views/greeting.blade.php', 'resources/views/hello.blade.php']); $project->git()->commit('move the view'); $project->snapshot(); @@ -70,10 +61,6 @@ test('an affected test file that is gone does not strand a filtered run', functi $project->git()->commit('add view'); $project->seed('master'); - // A graph written before this checkout existed — a fetched baseline, or a - // branch that deleted the file — can hold an edge for a test file nothing - // can run. Selecting it would filter the suite down to nothing and report - // success on a change no test looked at. $project->mutateGraph(function (array $graph): array { $id = count($graph['files']); $graph['files'][$id] = 'resources/views/page.blade.php'; @@ -174,8 +161,6 @@ test('a changed partial selects the test that rendered its ancestor', function ( 'resources/views/page.blade.php' => "hi\n", 'resources/views/components/card.blade.php' => "
one
\n", ], 'resources/views/components/card.blade.php'], - // Two partials that include each other: the ancestor walk has to notice it - // has seen them and stop, rather than chase the cycle forever. 'include cycle' => [[ 'resources/views/page.blade.php' => "@include('partials.a')\n", 'resources/views/partials/a.blade.php' => "@include('partials.b')\n", diff --git a/tests/Features/Tia/StateReclamation.php b/tests/Features/Tia/StateReclamation.php index 54a53fa6..c5c625bd 100644 --- a/tests/Features/Tia/StateReclamation.php +++ b/tests/Features/Tia/StateReclamation.php @@ -129,8 +129,6 @@ test('the fallback still reaches a branch that has never run a test file', funct $project->git()->switchTo('feature-x', new: true); - // A narrowed run mints the branch key holding only the Greeter entries, so - // the layering must still serve master's cached failure for the Calculator. $project->pest('--filter=greets a person'); $result = $project->pest('--tia', '--filtered'); @@ -212,12 +210,6 @@ test('a malformed baseline entry cannot break the run', function (array $argumen ->and($result->tally())->toContain(Project::TOTAL_TESTS.' passed'); })->with(Project::SEQUENTIAL_AND_PARALLEL)->skipOnWindows(); -/* - * Invariant 1 — sequential and parallel must agree — under a process that is - * torn down in the middle of a test file. A worker that flushed what it got to - * before dying has not seen enough of that file to license pruning the - * siblings it never reached. - */ test('a run torn down mid-file does not prune the tests it never reached', function (array $arguments): void { $project = Project::make('master'); $project->seed('master'); @@ -281,10 +273,6 @@ test('a fatal error mid-file is a test error, not a truncation', function (array ->and($delta->structureMoved())->toBeFalse($delta->summary()); })->with(Project::SEQUENTIAL_AND_PARALLEL)->skipOnWindows(); -/* - * Invariant 2 — every command lands in exactly one tier and stays inside it — - * for the combinations that were never exercised. - */ test('a green complete run leaves the graph exactly as it found it', function (array $arguments): void { $project = Project::make('master'); $project->seed('master'); @@ -351,16 +339,11 @@ test('a graph whose recorded commit is gone is re-anchored, not warned about for $recordedSha = $project->graph()['baselines']['master']['sha']; - // A rebase, a force-push, a reset: the commit the baseline was recorded at - // is no longer an ancestor of HEAD, so nothing can be diffed against it. $project->git()->run(['reset', '--quiet', '--hard', 'HEAD~1']); $project->snapshot(); $first = $project->pest('--tia', ...$arguments); - // The whole suite runs, and its results are the truth at HEAD — so the - // recorded revision has to move, whether or not a coverage driver was - // around to refresh the edges. Without that, the run below repeats forever. expect($first->exitCode)->toBe(0, $first->describe()) ->and($first->output)->toContain('no longer reachable') ->and($first->tally())->toContain(Project::TOTAL_TESTS.' passed') diff --git a/tests/Fixtures/Tia/GitRepo.php b/tests/Fixtures/Tia/GitRepo.php index e962acd5..3b114dd9 100644 --- a/tests/Fixtures/Tia/GitRepo.php +++ b/tests/Fixtures/Tia/GitRepo.php @@ -130,10 +130,6 @@ final readonly class GitRepo private function process(array $arguments, bool $mustSucceed): Process { $process = new Process(['git', ...$arguments], $this->path, self::ENV); - // Generous on purpose: these rows each spawn a real pest subprocess, so - // a loaded machine — a shared CI runner, or two of these suites at once - // — can starve a git call for tens of seconds. A timeout here fails the - // row for reasons that have nothing to do with what it asserts. $process->setTimeout(120.0); $process->run(); diff --git a/tests/Fixtures/Tia/Project.php b/tests/Fixtures/Tia/Project.php index 74506a99..4348454e 100644 --- a/tests/Fixtures/Tia/Project.php +++ b/tests/Fixtures/Tia/Project.php @@ -43,9 +43,6 @@ final class Project public const int TOTAL_TESTS = 6; /** - * A dataset for the rule that TIA must reach the same outcome sequentially - * and in parallel: the same command, run both ways, must leave the same graph. - * * @var array>> */ public const array SEQUENTIAL_AND_PARALLEL = [ @@ -124,12 +121,6 @@ final class Project } } - /** - * A second copy of the fixture app in a subdirectory of this project, so a - * run can be started from a root that sits *below* the git repository root. - * - * @return string The nested project's absolute path. - */ public function nested(string $directory = 'nested'): string { $path = $this->path($directory); @@ -179,11 +170,6 @@ final class Project 'COLLISION_IGNORE_DURATION' => 'true', 'PARATEST' => '0', 'PAO_DISABLE' => '1', - // Recording is what needs a driver, and recording happens here, - // in the subprocess — never in the process running these rows. - // Asking for coverage mode only here lets a CI job leave xdebug - // off for the suite it is running (whose collection under xdebug - // costs more than every scenario put together) and still record. 'XDEBUG_MODE' => 'coverage', 'HOME' => $this->home(), 'GITHUB_EVENT_PATH' => '', @@ -265,10 +251,6 @@ final class Project $sentinel ? $this->sentinel() : $this->snapshot(); } - /** - * Take the graph out of the state dir and hand back its JSON, so it can be - * served as the artifact a remote baseline fetch downloads. - */ public function detachGraph(): string { $json = $this->state()->read(Tia::KEY_GRAPH); @@ -287,10 +269,6 @@ final class Project } /** - * Install a stand-in for the GitHub CLI and return the environment that - * points a run at it. `$mode` names the failure it should serve (see - * `stubs/gh`); `$payload` is the graph.json its artifact carries. - * * @return array */ public function gh(string $mode = 'ok', string $payload = '{}'): array @@ -338,10 +316,6 @@ final class Project }); } - /** - * Adds a second, empty baseline key, so a lone recorded baseline can no - * longer stand in for the default branch. - */ public function addBaseline(string $branch): void { $this->mutateGraph(function (array $graph) use ($branch): array { @@ -390,7 +364,6 @@ final class Project { $baselines = $this->graph()['baselines'] ?? []; - // A branch named `12345` comes back from json_decode as an integer key. return is_array($baselines) ? array_map(strval(...), array_keys($baselines)) : []; } diff --git a/tests/Fixtures/Tia/stubs/gh b/tests/Fixtures/Tia/stubs/gh index 7f4ae9eb..7aa960ac 100755 --- a/tests/Fixtures/Tia/stubs/gh +++ b/tests/Fixtures/Tia/stubs/gh @@ -1,7 +1,4 @@ #!/bin/sh -# A stand-in for the GitHub CLI, so the remote-baseline path can be exercised -# without a network. `GH_STUB_MODE` picks the failure to serve; `GH_STUB_PAYLOAD` -# names the graph.json the fake artifact carries. if [ "$1" = "auth" ]; then [ "$GH_STUB_MODE" = "unauthenticated" ] && exit 1 diff --git a/tests/Pest.php b/tests/Pest.php index 41086a64..156431f3 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -19,9 +19,6 @@ pest()->in('PHPUnit/GlobPatternTests/SubFolder2/*AsPattern.php')->use(CustomTest pest()->in('Visual')->group('integration'); -// Also their own group, so a CI job with a coverage driver can run the TIA -// scenarios — the half of TIA that only exists when a driver is present — -// without dragging the visual snapshots along. pest()->in('Features/Tia')->group('integration', 'tia'); // NOTE: global test value container to be mutated and checked across files, as needed