From d9c18f9c02183f3e2da83e98a663115378f64096 Mon Sep 17 00:00:00 2001 From: nuno maduro Date: Wed, 22 Apr 2026 09:03:10 -0700 Subject: [PATCH] wip --- src/Concerns/Testable.php | 70 ++++++++++++++++++++----------------- src/Plugins/Tia.php | 33 ++++++++++++++--- src/Support/XdebugGuard.php | 4 +-- tests-tia/RebuildTest.php | 6 ++-- 4 files changed, 72 insertions(+), 41 deletions(-) diff --git a/src/Concerns/Testable.php b/src/Concerns/Testable.php index 6d304765..1e35cc2a 100644 --- a/src/Concerns/Testable.php +++ b/src/Concerns/Testable.php @@ -238,6 +238,44 @@ trait Testable $this->__cachedPass = false; + $method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name()); + + $description = $method->description; + if ($this->dataName()) { + $description = str_contains((string) $description, ':dataset') + ? str_replace(':dataset', str_replace('dataset ', '', $this->dataName()), (string) $description) + : $description.' with '.$this->dataName(); + } + + $description = htmlspecialchars(html_entity_decode((string) $description), ENT_NOQUOTES); + + if ($method->repetitions > 1) { + $matches = []; + preg_match('/\((.*?)\)/', $description, $matches); + + if (count($matches) > 1) { + if (str_contains($description, 'with '.$matches[0].' /')) { + $description = str_replace('with '.$matches[0].' /', '', $description); + } else { + $description = str_replace('with '.$matches[0], '', $description); + } + } + + $description .= ' @ repetition '.($matches[1].' of '.$method->repetitions); + } + + $this->__description = self::$__latestDescription = $description; + self::$__latestAssignees = $method->assignees; + self::$__latestNotes = $method->notes; + self::$__latestIssues = $method->issues; + self::$__latestPrs = $method->prs; + + // TIA replay short-circuit. Runs AFTER dataset/description/ + // assignee metadata is populated so output and filtering still + // see the correct test name + tags on a cache hit, but BEFORE + // `parent::setUp()` and `beforeEach` so we skip the user's + // fixture setup (which is the whole point of replay — avoid + // paying for work whose outcome we already know). /** @var Tia $tia */ $tia = Container::getInstance()->get(Tia::class); $cached = $tia->getCachedResult(self::$__filename, $this::class.'::'.$this->name()); @@ -275,38 +313,6 @@ trait Testable throw new AssertionFailedError($cached->message() ?: 'Cached failure'); } - $method = TestSuite::getInstance()->tests->get(self::$__filename)->getMethod($this->name()); - - $description = $method->description; - if ($this->dataName()) { - $description = str_contains((string) $description, ':dataset') - ? str_replace(':dataset', str_replace('dataset ', '', $this->dataName()), (string) $description) - : $description.' with '.$this->dataName(); - } - - $description = htmlspecialchars(html_entity_decode((string) $description), ENT_NOQUOTES); - - if ($method->repetitions > 1) { - $matches = []; - preg_match('/\((.*?)\)/', $description, $matches); - - if (count($matches) > 1) { - if (str_contains($description, 'with '.$matches[0].' /')) { - $description = str_replace('with '.$matches[0].' /', '', $description); - } else { - $description = str_replace('with '.$matches[0], '', $description); - } - } - - $description .= ' @ repetition '.($matches[1].' of '.$method->repetitions); - } - - $this->__description = self::$__latestDescription = $description; - self::$__latestAssignees = $method->assignees; - self::$__latestNotes = $method->notes; - self::$__latestIssues = $method->issues; - self::$__latestPrs = $method->prs; - parent::setUp(); $beforeEach = TestSuite::getInstance()->beforeEach->get(self::$__filename)[1]; diff --git a/src/Plugins/Tia.php b/src/Plugins/Tia.php index df3093b5..467a7116 100644 --- a/src/Plugins/Tia.php +++ b/src/Plugins/Tia.php @@ -72,7 +72,13 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable private const string OPTION = '--tia'; - private const string REBUILD_OPTION = '--tia-rebuild'; + /** + * Discards any existing graph and re-records from scratch. Meant to + * be combined with `--tia`; the flag is shared with the rest of Pest + * (no `tia-` prefix) so a single `--tia --fresh` reads naturally as + * "TIA, fresh start". + */ + private const string FRESH_OPTION = '--fresh'; /** * Bypasses `BaselineSync`'s post-failure cooldown. After a failed @@ -331,15 +337,22 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable $replayingGlobal = $isWorker && (string) Parallel::getGlobal(self::REPLAYING_GLOBAL) === '1'; $enabled = $this->hasArgument(self::OPTION, $arguments); - $forceRebuild = $this->hasArgument(self::REBUILD_OPTION, $arguments); + $freshRequested = $this->hasArgument(self::FRESH_OPTION, $arguments); $this->forceRefetch = $this->hasArgument(self::REFETCH_OPTION, $arguments); - if (! $enabled && ! $forceRebuild && ! $this->forceRefetch && ! $recordingGlobal && ! $replayingGlobal) { + // `--fresh` only takes effect alongside `--tia` (or from a + // worker that's already in TIA mode). Without `--tia`, Pest + // users could be passing `--fresh` to an unrelated plugin — + // silently ignore it here and let whatever else consumes it + // handle it. The flag isn't popped in that branch. + $forceRebuild = $freshRequested && ($enabled || $recordingGlobal || $replayingGlobal); + + if (! $enabled && ! $this->forceRefetch && ! $recordingGlobal && ! $replayingGlobal) { return $arguments; } $arguments = $this->popArgument(self::OPTION, $arguments); - $arguments = $this->popArgument(self::REBUILD_OPTION, $arguments); + $arguments = $this->popArgument(self::FRESH_OPTION, $arguments); $arguments = $this->popArgument(self::REFETCH_OPTION, $arguments); // When `--coverage` is active, piggyback on PHPUnit's CodeCoverage @@ -1141,6 +1154,18 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable private function registerRecap(): void { DefaultPrinter::addRecap(function (): string { + // Parallel mode: worker replays live in other processes and + // flushed their counters to disk on terminate. Collision's + // `writeRecap` fires inside `ExecutionFinished`, which is + // strictly before `addOutput` — so we must merge right here + // or the fragment below would read 0 and the suffix would + // silently disappear on `--tia --parallel`. The merge is + // idempotent: partial keys are deleted on read, so the + // later `addOutput` call becomes a no-op. + if (Parallel::isEnabled() && ! Parallel::isWorker()) { + $this->mergeWorkerReplayPartials(); + } + $fragments = []; if ($this->executedCount > 0) { diff --git a/src/Support/XdebugGuard.php b/src/Support/XdebugGuard.php index 58cf628a..de1c15da 100644 --- a/src/Support/XdebugGuard.php +++ b/src/Support/XdebugGuard.php @@ -21,7 +21,7 @@ use Pest\Plugins\Tia\Storage; * * The guard engages only when ALL of these hold: * 1. `--tia` is present in argv. - * 2. No `--tia-rebuild` flag (forced record always drives the coverage + * 2. No `--fresh` flag (forced record always drives the coverage * driver; dropping Xdebug would break the recording). * 3. No `--coverage*` flag (coverage runs need the driver regardless). * 4. A valid graph already exists on disk AND its structural fingerprint @@ -117,7 +117,7 @@ final class XdebugGuard return false; } - if ($value === '--tia-rebuild') { + if ($value === '--fresh') { return false; } diff --git a/tests-tia/RebuildTest.php b/tests-tia/RebuildTest.php index c9d4ecc8..17d7f709 100644 --- a/tests-tia/RebuildTest.php +++ b/tests-tia/RebuildTest.php @@ -5,18 +5,18 @@ declare(strict_types=1); use Pest\TestsTia\Support\Sandbox; /* - * `--tia-rebuild` short-circuits whatever graph is on disk and records + * `--tia --fresh` short-circuits whatever graph is on disk and records * from scratch. Used when the user knows the cache is wrong. */ -test('--tia-rebuild forces record mode even with a valid graph', function () { +test('--tia --fresh forces record mode even with a valid graph', function () { tiaScenario(function (Sandbox $sandbox) { $sandbox->pest(['--tia']); expect($sandbox->hasGraph())->toBeTrue(); $graphBefore = $sandbox->graph(); - $process = $sandbox->pest(['--tia', '--tia-rebuild']); + $process = $sandbox->pest(['--tia', '--fresh']); expect($process->isSuccessful())->toBeTrue(tiaOutput($process)); expect(tiaOutput($process))->toContain('recording dependency graph');