This commit is contained in:
nuno maduro
2026-04-20 20:58:38 -07:00
parent 1476b529a1
commit a5915b16ab
7 changed files with 152 additions and 22 deletions

View File

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Pest\Plugins\Tia;
use Pest\Contracts\Bootstrapper as BootstrapperContract;
use Pest\Plugins\Tia\Contracts\State;
use Pest\Support\Container;
/**
* Plugin-level container registrations for TIA. Runs as part of Kernel's
* bootstrapper chain so Tia's own service graph is set up without Kernel
* having to know about any of its internals.
*
* Most Tia services (`Recorder`, `CoverageCollector`, `WatchPatterns`,
* `ResultCollector`, `BaselineSync`) are auto-buildable — Pest's container
* resolves them lazily via constructor reflection. The only service that
* requires an explicit binding is the `State` contract, because the
* filesystem implementation needs a root-directory string that reflection
* can't infer.
*
* @internal
*/
final readonly class Bootstrapper implements BootstrapperContract
{
public function __construct(private Container $container) {}
public function boot(): void
{
$this->container->add(State::class, new FileState($this->tempDir()));
}
/**
* Resolve Pest's `.temp/` directory relative to this file so TIA's
* caches share the same location as the rest of Pest's transient
* state (PHPUnit result cache, coverage PHP dumps, etc.).
*/
private function tempDir(): string
{
return __DIR__
.DIRECTORY_SEPARATOR.'..'
.DIRECTORY_SEPARATOR.'..'
.DIRECTORY_SEPARATOR.'..'
.DIRECTORY_SEPARATOR.'.temp';
}
}

View File

@ -60,7 +60,7 @@ final class Graph
* @var array<string, array{
* sha: ?string,
* tree: array<string, string>,
* results: array<string, array{status: int, message: string, time: float}>
* results: array<string, array{status: int, message: string, time: float, assertions?: int}>
* }>
*/
private array $baselines = [];
@ -257,14 +257,36 @@ final class Graph
$this->baselines[$branch]['sha'] = $sha;
}
public function setResult(string $branch, string $testId, int $status, string $message, float $time): void
public function setResult(string $branch, string $testId, int $status, string $message, float $time, int $assertions = 0): void
{
$this->ensureBaseline($branch);
$this->baselines[$branch]['results'][$testId] = [
'status' => $status, 'message' => $message, 'time' => $time,
'status' => $status,
'message' => $message,
'time' => $time,
'assertions' => $assertions,
];
}
/**
* Returns the cached assertion count for a test, or `null` if unknown.
* Callers use this to feed `addToAssertionCount()` at replay time so
* the "Tests: N passed (M assertions)" banner matches the recorded run
* instead of defaulting to 1 assertion per test.
*/
public function getAssertions(string $branch, string $testId, string $fallbackBranch = 'main'): ?int
{
$baseline = $this->baselineFor($branch, $fallbackBranch);
if (! isset($baseline['results'][$testId]['assertions'])) {
return null;
}
$value = $baseline['results'][$testId]['assertions'];
return is_int($value) ? $value : null;
}
public function getResult(string $branch, string $testId, string $fallbackBranch = 'main'): ?TestStatus
{
$baseline = $this->baselineFor($branch, $fallbackBranch);
@ -310,7 +332,7 @@ final class Graph
}
/**
* @return array{sha: ?string, tree: array<string, string>, results: array<string, array{status: int, message: string, time: float}>}
* @return array{sha: ?string, tree: array<string, string>, results: array<string, array{status: int, message: string, time: float, assertions?: int}>}
*/
private function baselineFor(string $branch, string $fallbackBranch): array
{

View File

@ -118,6 +118,17 @@ final class ResultCollector
$this->startTime = null;
}
/**
* Called by the Finished subscriber after a test's outcome + assertion
* events have all fired. Clears the "currently recording" pointer so
* the next test's events don't get mis-attributed.
*/
public function finishTest(): void
{
$this->currentTestId = null;
$this->startTime = null;
}
private function record(int $status, string $message): void
{
if ($this->currentTestId === null) {
@ -128,14 +139,17 @@ final class ResultCollector
? round(microtime(true) - $this->startTime, 3)
: 0.0;
// PHPUnit can fire more than one outcome event per test — the
// canonical case is a risky pass (`Passed` then `ConsideredRisky`).
// Last-wins semantics preserve the most specific status; the
// existing assertion count (if any) survives the overwrite.
$existing = $this->results[$this->currentTestId] ?? null;
$this->results[$this->currentTestId] = [
'status' => $status,
'message' => $message,
'time' => $time,
'assertions' => 0,
'assertions' => $existing['assertions'] ?? 0,
];
$this->currentTestId = null;
$this->startTime = null;
}
}