From 047753c8363b12e337b2d9321647be4b9648599c Mon Sep 17 00:00:00 2001 From: Jeffrey van Hees Date: Mon, 3 Aug 2026 03:43:15 +0200 Subject: [PATCH] fix: key Tia results per dataset row (#1799) The result cache was keyed `Class::method` on both sides, so every row of a `->with()` test shared one entry and the last writer won. On replay that single status was handed to every row. `TestMethod::id()` already appends `#dataSetName`, and `TestCase` reaches the same value object through `valueObjectForEvents()`, so both sides can use it. --- src/Concerns/Testable.php | 4 +- ...sureTiaAssertionsAreRecordedOnFinished.php | 2 +- .../EnsureTiaResultsAreCollected.php | 2 +- tests/Unit/Plugins/Tia/ResultKey.php | 114 ++++++++++++++++++ 4 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 tests/Unit/Plugins/Tia/ResultKey.php diff --git a/src/Concerns/Testable.php b/src/Concerns/Testable.php index c1c25637..6397be70 100644 --- a/src/Concerns/Testable.php +++ b/src/Concerns/Testable.php @@ -280,7 +280,7 @@ trait Testable /** @var Tia $tia */ $tia = Container::getInstance()->get(Tia::class); - $status = $tia->getStatus(self::$__filename, $this::class.'::'.$this->name()); + $status = $tia->getStatus(self::$__filename, $this->valueObjectForEvents()->id()); $replay = ReplayType::fromStatus($status); if ($replay !== ReplayType::None) { @@ -319,7 +319,7 @@ trait Testable private function __beginReplay(ReplayType $replay, Tia $tia): void { $this->__replay = $replay; - $this->__replayAssertions = $tia->getAssertionCount($this::class.'::'.$this->name()); + $this->__replayAssertions = $tia->getAssertionCount($this->valueObjectForEvents()->id()); $this->__ran = true; } diff --git a/src/Subscribers/EnsureTiaAssertionsAreRecordedOnFinished.php b/src/Subscribers/EnsureTiaAssertionsAreRecordedOnFinished.php index 46f92be8..96fd5538 100644 --- a/src/Subscribers/EnsureTiaAssertionsAreRecordedOnFinished.php +++ b/src/Subscribers/EnsureTiaAssertionsAreRecordedOnFinished.php @@ -22,7 +22,7 @@ final readonly class EnsureTiaAssertionsAreRecordedOnFinished implements Finishe if ($test instanceof TestMethod) { $this->collector->recordAssertions( - $test->className().'::'.$test->methodName(), + $test->id(), $event->numberOfAssertionsPerformed(), ); } diff --git a/src/Subscribers/EnsureTiaResultsAreCollected.php b/src/Subscribers/EnsureTiaResultsAreCollected.php index 90bc6582..41f60d68 100644 --- a/src/Subscribers/EnsureTiaResultsAreCollected.php +++ b/src/Subscribers/EnsureTiaResultsAreCollected.php @@ -21,7 +21,7 @@ final readonly class EnsureTiaResultsAreCollected implements PreparationStartedS $test = $event->test(); if ($test instanceof TestMethod) { - $this->collector->testPrepared($test->className().'::'.$test->methodName(), $test->file()); + $this->collector->testPrepared($test->id(), $test->file()); } } } diff --git a/tests/Unit/Plugins/Tia/ResultKey.php b/tests/Unit/Plugins/Tia/ResultKey.php new file mode 100644 index 00000000..4bd37174 --- /dev/null +++ b/tests/Unit/Plugins/Tia/ResultKey.php @@ -0,0 +1,114 @@ +snapshot()->userCpuTime(); + + return new Info( + $system->snapshot(), + $zeroDuration, + $zeroMemory, + $zeroDuration, + $zeroMemory, + $zeroCpuTime, + $zeroCpuTime, + $zeroCpuTime, + $zeroCpuTime, + $zeroCpuTime, + $zeroCpuTime, + ); +} + +it('keys a result per dataset row rather than per method', function (): void { + $collector = new ResultCollector; + $subscriber = new EnsureTiaResultsAreCollected($collector); + + $subscriber->notify(new PreparationStarted(tiaResultKeyTelemetryInfo(), tiaResultKeyTestMethod('opp'))); + $collector->testPassed(); + $collector->finishTest(); + + $subscriber->notify(new PreparationStarted(tiaResultKeyTelemetryInfo(), tiaResultKeyTestMethod('fake'))); + $collector->testSkipped('the fake driver does not report balances'); + $collector->finishTest(); + + // Without the dataset in the key both rows write to `Class::method`, so the + // second one overwrites the first and a replay hands every row the same + // status — a passing row reported as skipped, or a failing one as passed. + expect(array_keys($collector->all()))->toBe([ + 'Tests\Feature\OrderTest::it prices an order#opp', + 'Tests\Feature\OrderTest::it prices an order#fake', + ]); +}); + +it('records assertions against the same per-dataset key', function (): void { + $collector = new ResultCollector; + + (new EnsureTiaResultsAreCollected($collector))->notify( + new PreparationStarted(tiaResultKeyTelemetryInfo(), tiaResultKeyTestMethod('opp')), + ); + $collector->testPassed(); + + (new EnsureTiaAssertionsAreRecordedOnFinished($collector))->notify( + new Finished(tiaResultKeyTelemetryInfo(), tiaResultKeyTestMethod('opp'), 7), + ); + + expect($collector->all()['Tests\Feature\OrderTest::it prices an order#opp']['assertions'])->toBe(7); +}); + +it('leaves a test without a dataset keyed by class and method', function (): void { + $collector = new ResultCollector; + + (new EnsureTiaResultsAreCollected($collector))->notify( + new PreparationStarted(tiaResultKeyTelemetryInfo(), tiaResultKeyTestMethod(null)), + ); + $collector->testPassed(); + + expect(array_keys($collector->all()))->toBe(['Tests\Feature\OrderTest::it prices an order']); +});