From 9e4cf4b665c85a6e6b9e91e63358a18292bef8e9 Mon Sep 17 00:00:00 2001 From: nuno maduro Date: Sat, 2 May 2026 18:58:42 +0100 Subject: [PATCH] wip --- src/Bootstrappers/BootSubscribers.php | 4 +- src/Plugins/Tia.php | 4 +- src/Plugins/Tia/BaselineSync.php | 95 +------------------ src/Plugins/Tia/CoverageCollector.php | 1 - src/Plugins/Tia/Graph.php | 3 +- ...overageIsFlushed.php => EnsureTiaEnds.php} | 2 +- ...rageIsRecorded.php => EnsureTiaStarts.php} | 2 +- tests/Unit/Plugins/Tia/ContentHash.php | 66 ++++++------- 8 files changed, 43 insertions(+), 134 deletions(-) rename src/Subscribers/{EnsureTiaCoverageIsFlushed.php => EnsureTiaEnds.php} (82%) rename src/Subscribers/{EnsureTiaCoverageIsRecorded.php => EnsureTiaStarts.php} (88%) diff --git a/src/Bootstrappers/BootSubscribers.php b/src/Bootstrappers/BootSubscribers.php index 5fa720ac..065a5e0f 100644 --- a/src/Bootstrappers/BootSubscribers.php +++ b/src/Bootstrappers/BootSubscribers.php @@ -26,8 +26,8 @@ final readonly class BootSubscribers implements Bootstrapper Subscribers\EnsureKernelDumpIsFlushed::class, Subscribers\EnsureTeamCityEnabled::class, Subscribers\EnsureTiaIsRunningPestTestsOnly::class, - Subscribers\EnsureTiaCoverageIsRecorded::class, - Subscribers\EnsureTiaCoverageIsFlushed::class, + Subscribers\EnsureTiaStarts::class, + Subscribers\EnsureTiaEnds::class, Subscribers\EnsureTiaResultsAreCollected::class, Subscribers\EnsureTiaResultIsRecordedOnPassed::class, Subscribers\EnsureTiaResultIsRecordedOnFailed::class, diff --git a/src/Plugins/Tia.php b/src/Plugins/Tia.php index 497116b2..9f98d07e 100644 --- a/src/Plugins/Tia.php +++ b/src/Plugins/Tia.php @@ -126,7 +126,7 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable /** @var array{structural: array, environmental: array}|null */ private ?array $startFingerprint = null; -private bool $piggybackCoverage = false; + private bool $piggybackCoverage = false; private bool $recordingActive = false; @@ -1391,7 +1391,7 @@ private bool $piggybackCoverage = false; $coverage = Container::getInstance()->get(Coverage::class); assert($coverage instanceof Coverage); - return $coverage->coverage === true; + return $coverage->coverage; } /** diff --git a/src/Plugins/Tia/BaselineSync.php b/src/Plugins/Tia/BaselineSync.php index 3c2628ec..1efb36b9 100644 --- a/src/Plugins/Tia/BaselineSync.php +++ b/src/Plugins/Tia/BaselineSync.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace Pest\Plugins\Tia; -use Composer\InstalledVersions; use Pest\Exceptions\BaselineFetchFailed; use Pest\Panic; use Pest\Plugins\Tia; @@ -94,7 +93,7 @@ final readonly class BaselineSync if ($payload === null) { if ($failureKind === 'no-runs' || $failureKind === null) { $this->startCooldown(); - $this->emitPublishInstructions($repo); + $this->emitPublishInstructions(); } return false; @@ -162,7 +161,7 @@ final readonly class BaselineSync return $seconds.'s'; } - private function emitPublishInstructions(string $repo): void + private function emitPublishInstructions(): void { if ($this->isCi()) { $this->renderBadge('INFO', 'No baseline yet — this run will produce one.'); @@ -170,23 +169,8 @@ final readonly class BaselineSync return; } - $yaml = $this->isLaravel() - ? $this->laravelWorkflowYaml() - : $this->genericWorkflowYaml(); - $this->renderBadge('WARN', 'No baseline published yet — recording locally.'); - $this->renderChild('To share the baseline with your team, add this workflow to the repo:'); - $this->renderChild('.github/workflows/tia-baseline.yml'); - - $indentedYaml = array_map( - static fn (string $line): string => ' '.$line, - explode("\n", $yaml), - ); - - $this->output->writeln(['', ...$indentedYaml, '']); - - $this->renderChild(sprintf('Commit, push, then run once: gh workflow run tia-baseline.yml -R %s', $repo)); - $this->renderChild('Details: https://pestphp.com/docs/tia'); + $this->renderChild('See https://pestphp.com/docs/tia for how to publish one from CI.'); } private function isCi(): bool @@ -196,79 +180,6 @@ final readonly class BaselineSync || getenv('CIRCLECI') === 'true'; } - private function isLaravel(): bool - { - return class_exists(InstalledVersions::class) - && InstalledVersions::isInstalled('laravel/framework'); - } - - private function laravelWorkflowYaml(): string - { - return <<<'YAML' -name: TIA Baseline -on: - push: { branches: [main] } - schedule: [{ cron: '0 3 * * *' }] - workflow_dispatch: -jobs: - baseline: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: { fetch-depth: 0 } - - uses: shivammathur/setup-php@v2 - with: - php-version: '8.4' - coverage: xdebug - extensions: json, dom, curl, libxml, mbstring, zip, pdo, pdo_sqlite, sqlite3, bcmath, intl - - run: cp .env.example .env - - run: composer install --no-interaction --prefer-dist - - run: php artisan key:generate - - run: ./vendor/bin/pest --parallel --tia --coverage - - name: Stage baseline for upload - shell: bash - run: | - mkdir -p .pest-tia-baseline - cp -R "$HOME/.pest/tia"/*/. .pest-tia-baseline/ - - uses: actions/upload-artifact@v4 - with: - name: pest-tia-baseline - path: .pest-tia-baseline/ - retention-days: 30 -YAML; - } - - private function genericWorkflowYaml(): string - { - return <<<'YAML' -name: TIA Baseline -on: - push: { branches: [main] } - schedule: [{ cron: '0 3 * * *' }] - workflow_dispatch: -jobs: - baseline: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: { fetch-depth: 0 } - - uses: shivammathur/setup-php@v2 - with: { php-version: '8.4', coverage: xdebug } - - run: composer install --no-interaction --prefer-dist - - run: ./vendor/bin/pest --parallel --tia --coverage - - name: Stage baseline for upload - shell: bash - run: | - mkdir -p .pest-tia-baseline - cp -R "$HOME/.pest/tia"/*/. .pest-tia-baseline/ - - uses: actions/upload-artifact@v4 - with: - name: pest-tia-baseline - path: .pest-tia-baseline/ - retention-days: 30 -YAML; - } - private function detectGitHubRepo(string $projectRoot): ?string { $gitConfig = $projectRoot.DIRECTORY_SEPARATOR.'.git'.DIRECTORY_SEPARATOR.'config'; diff --git a/src/Plugins/Tia/CoverageCollector.php b/src/Plugins/Tia/CoverageCollector.php index f8d66099..1aaf34cb 100644 --- a/src/Plugins/Tia/CoverageCollector.php +++ b/src/Plugins/Tia/CoverageCollector.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace Pest\Plugins\Tia; use PHPUnit\Runner\CodeCoverage as PhpUnitCodeCoverage; -use ReflectionClass; use Throwable; /** diff --git a/src/Plugins/Tia/Graph.php b/src/Plugins/Tia/Graph.php index 8b0cd172..6da50f8b 100644 --- a/src/Plugins/Tia/Graph.php +++ b/src/Plugins/Tia/Graph.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace Pest\Plugins\Tia; -use Pest\Factories\Attribute; use Pest\Factories\TestCaseFactory; use Pest\Factories\TestCaseMethodFactory; use Pest\Support\Container; @@ -1012,7 +1011,7 @@ final class Graph } foreach ($method->attributes as $attribute) { - if (! $attribute instanceof Attribute || $attribute->name !== Group::class) { + if ($attribute->name !== Group::class) { continue; } diff --git a/src/Subscribers/EnsureTiaCoverageIsFlushed.php b/src/Subscribers/EnsureTiaEnds.php similarity index 82% rename from src/Subscribers/EnsureTiaCoverageIsFlushed.php rename to src/Subscribers/EnsureTiaEnds.php index 4298c434..5dba31f8 100644 --- a/src/Subscribers/EnsureTiaCoverageIsFlushed.php +++ b/src/Subscribers/EnsureTiaEnds.php @@ -11,7 +11,7 @@ use PHPUnit\Event\Test\FinishedSubscriber; /** * @internal */ -final readonly class EnsureTiaCoverageIsFlushed implements FinishedSubscriber +final readonly class EnsureTiaEnds implements FinishedSubscriber { public function __construct(private Recorder $recorder) {} diff --git a/src/Subscribers/EnsureTiaCoverageIsRecorded.php b/src/Subscribers/EnsureTiaStarts.php similarity index 88% rename from src/Subscribers/EnsureTiaCoverageIsRecorded.php rename to src/Subscribers/EnsureTiaStarts.php index 76ea8137..e6aca8c4 100644 --- a/src/Subscribers/EnsureTiaCoverageIsRecorded.php +++ b/src/Subscribers/EnsureTiaStarts.php @@ -12,7 +12,7 @@ use PHPUnit\Event\Test\PreparedSubscriber; /** * @internal */ -final readonly class EnsureTiaCoverageIsRecorded implements PreparedSubscriber +final readonly class EnsureTiaStarts implements PreparedSubscriber { public function __construct(private Recorder $recorder) {} diff --git a/tests/Unit/Plugins/Tia/ContentHash.php b/tests/Unit/Plugins/Tia/ContentHash.php index d1b2087b..6b235167 100644 --- a/tests/Unit/Plugins/Tia/ContentHash.php +++ b/tests/Unit/Plugins/Tia/ContentHash.php @@ -22,7 +22,7 @@ describe('of()', function () { describe('PHP files', function () { it('produces the same hash regardless of whitespace differences', function () { $a = ContentHash::ofContent('a.php', "toBe($b); }); @@ -56,8 +56,8 @@ describe('PHP files', function () { }); it('detects code changes', function () { - $a = ContentHash::ofContent('a.php', "not->toBe($b); }); @@ -70,8 +70,8 @@ describe('PHP files', function () { }); it('treats variable renames as a change', function () { - $a = ContentHash::ofContent('a.php', "not->toBe($b); }); @@ -92,43 +92,43 @@ describe('PHP files', function () { describe('Blade files', function () { it('strips blade comments', function () { - $a = ContentHash::ofContent('a.blade.php', "
{{-- a comment --}}Hello
"); - $b = ContentHash::ofContent('a.blade.php', "
Hello
"); + $a = ContentHash::ofContent('a.blade.php', '
{{-- a comment --}}Hello
'); + $b = ContentHash::ofContent('a.blade.php', '
Hello
'); expect($a)->toBe($b); }); it('strips multi-line blade comments', function () { $a = ContentHash::ofContent('a.blade.php', "
\n{{--\n multi\n line\n--}}\nHello\n
"); - $b = ContentHash::ofContent('a.blade.php', "
Hello
"); + $b = ContentHash::ofContent('a.blade.php', '
Hello
'); expect($a)->toBe($b); }); it('collapses whitespace', function () { $a = ContentHash::ofContent('a.blade.php', "
\n Hello\n World\n
"); - $b = ContentHash::ofContent('a.blade.php', "
Hello World
"); + $b = ContentHash::ofContent('a.blade.php', '
Hello World
'); expect($a)->toBe($b); }); it('detects content changes', function () { - $a = ContentHash::ofContent('a.blade.php', "
Hello
"); - $b = ContentHash::ofContent('a.blade.php', "
Goodbye
"); + $a = ContentHash::ofContent('a.blade.php', '
Hello
'); + $b = ContentHash::ofContent('a.blade.php', '
Goodbye
'); expect($a)->not->toBe($b); }); it('keeps blade directives intact', function () { - $a = ContentHash::ofContent('a.blade.php', "@if(\$user)Hi @endif"); - $b = ContentHash::ofContent('a.blade.php', "@if(\$user)Bye @endif"); + $a = ContentHash::ofContent('a.blade.php', '@if($user)Hi @endif'); + $b = ContentHash::ofContent('a.blade.php', '@if($user)Bye @endif'); expect($a)->not->toBe($b); }); it('does not use the PHP tokenizer for blade files', function () { - $a = ContentHash::ofContent('a.blade.php', " hello"); - $b = ContentHash::ofContent('a.blade.php', " hello"); + $a = ContentHash::ofContent('a.blade.php', ' hello'); + $b = ContentHash::ofContent('a.blade.php', ' hello'); expect($a)->not->toBe($b); }); @@ -137,70 +137,70 @@ describe('Blade files', function () { describe('JavaScript-like files', function () { it('strips line comments', function () { $a = ContentHash::ofContent('a.js', "// a comment\nconst foo = 1;"); - $b = ContentHash::ofContent('a.js', "const foo = 1;"); + $b = ContentHash::ofContent('a.js', 'const foo = 1;'); expect($a)->toBe($b); }); it('strips block comments on their own lines', function () { $a = ContentHash::ofContent('a.js', "/* block */\nconst foo = 1;"); - $b = ContentHash::ofContent('a.js', "const foo = 1;"); + $b = ContentHash::ofContent('a.js', 'const foo = 1;'); expect($a)->toBe($b); }); it('collapses whitespace', function () { $a = ContentHash::ofContent('a.js', "const foo = 1;\n\nconst bar = 2;"); - $b = ContentHash::ofContent('a.js', "const foo = 1; const bar = 2;"); + $b = ContentHash::ofContent('a.js', 'const foo = 1; const bar = 2;'); expect($a)->toBe($b); }); it('detects code changes', function () { - $a = ContentHash::ofContent('a.js', "const foo = 1;"); - $b = ContentHash::ofContent('a.js', "const foo = 2;"); + $a = ContentHash::ofContent('a.js', 'const foo = 1;'); + $b = ContentHash::ofContent('a.js', 'const foo = 2;'); expect($a)->not->toBe($b); }); it('does not strip inline trailing comments', function () { - $a = ContentHash::ofContent('a.js', "const foo = 1; // inline"); - $b = ContentHash::ofContent('a.js', "const foo = 1;"); + $a = ContentHash::ofContent('a.js', 'const foo = 1; // inline'); + $b = ContentHash::ofContent('a.js', 'const foo = 1;'); expect($a)->not->toBe($b); }); it('applies the same rules to .ts files', function () { $a = ContentHash::ofContent('a.ts', "// comment\nconst foo: number = 1;"); - $b = ContentHash::ofContent('a.ts', "const foo: number = 1;"); + $b = ContentHash::ofContent('a.ts', 'const foo: number = 1;'); expect($a)->toBe($b); }); it('applies the same rules to .tsx files', function () { $a = ContentHash::ofContent('a.tsx', "// comment\nconst Foo = () =>
;"); - $b = ContentHash::ofContent('a.tsx', "const Foo = () =>
;"); + $b = ContentHash::ofContent('a.tsx', 'const Foo = () =>
;'); expect($a)->toBe($b); }); it('applies the same rules to .jsx files', function () { $a = ContentHash::ofContent('a.jsx', "// comment\nconst Foo = () =>
;"); - $b = ContentHash::ofContent('a.jsx', "const Foo = () =>
;"); + $b = ContentHash::ofContent('a.jsx', 'const Foo = () =>
;'); expect($a)->toBe($b); }); it('applies the same rules to .vue files', function () { $a = ContentHash::ofContent('a.vue', ""); - $b = ContentHash::ofContent('a.vue', ""); + $b = ContentHash::ofContent('a.vue', ''); expect($a)->toBe($b); }); it('applies the same rules to .svelte files', function () { $a = ContentHash::ofContent('a.svelte', ""); - $b = ContentHash::ofContent('a.svelte', ""); + $b = ContentHash::ofContent('a.svelte', ''); expect($a)->toBe($b); }); @@ -208,7 +208,7 @@ describe('JavaScript-like files', function () { it('applies the same rules to .mjs, .cjs, and .mts files', function () { foreach (['mjs', 'cjs', 'mts'] as $ext) { $a = ContentHash::ofContent("a.$ext", "// comment\nexport const foo = 1;"); - $b = ContentHash::ofContent("a.$ext", "export const foo = 1;"); + $b = ContentHash::ofContent("a.$ext", 'export const foo = 1;'); expect($a)->toBe($b); } @@ -217,22 +217,22 @@ describe('JavaScript-like files', function () { describe('unknown extensions', function () { it('hashes the raw content for unknown extensions', function () { - $a = ContentHash::ofContent('a.txt', "hello world"); - $b = ContentHash::ofContent('a.txt', "hello world"); + $a = ContentHash::ofContent('a.txt', 'hello world'); + $b = ContentHash::ofContent('a.txt', 'hello world'); expect($a)->toBe($b); }); it('does not normalise whitespace for unknown extensions', function () { - $a = ContentHash::ofContent('a.txt', "hello world"); - $b = ContentHash::ofContent('a.txt', "hello world"); + $a = ContentHash::ofContent('a.txt', 'hello world'); + $b = ContentHash::ofContent('a.txt', 'hello world'); expect($a)->not->toBe($b); }); it('does not strip comments for unknown extensions', function () { $a = ContentHash::ofContent('a.txt', "// not a comment here\nhello"); - $b = ContentHash::ofContent('a.txt', "hello"); + $b = ContentHash::ofContent('a.txt', 'hello'); expect($a)->not->toBe($b); });