mirror of
https://github.com/pestphp/pest.git
synced 2026-04-24 07:57:29 +02:00
wip
This commit is contained in:
@ -16,8 +16,9 @@ use Symfony\Component\Process\Process;
|
||||
*
|
||||
* Storage: **workflow artifacts**, not releases. A dedicated CI workflow
|
||||
* (conventionally `.github/workflows/tia-baseline.yml`) runs the full
|
||||
* suite under `--tia` and uploads the resulting `tia.json` +
|
||||
* `tia-coverage.bin` as a named artifact (`pest-tia-baseline`). On dev
|
||||
* suite under `--tia` and uploads the `.temp/tia/` directory as a named
|
||||
* artifact (`pest-tia-baseline`) containing `graph.json` +
|
||||
* `coverage.bin`. On dev
|
||||
* machines, this class finds the latest successful run of that workflow
|
||||
* and downloads the artifact via `gh`.
|
||||
*
|
||||
@ -48,7 +49,7 @@ final class BaselineSync
|
||||
|
||||
/**
|
||||
* Artifact name the workflow uploads under. The artifact is a zip
|
||||
* containing `tia.json` (always) + `tia-coverage.bin` (optional).
|
||||
* containing `graph.json` (always) + `coverage.bin` (optional).
|
||||
*/
|
||||
private const string ARTIFACT_NAME = 'pest-tia-baseline';
|
||||
|
||||
@ -87,9 +88,7 @@ final class BaselineSync
|
||||
$payload = $this->download($repo);
|
||||
|
||||
if ($payload === null) {
|
||||
$this->output->writeln(
|
||||
' <fg=yellow>TIA</> no baseline published yet — recording locally.',
|
||||
);
|
||||
$this->emitPublishInstructions($repo);
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -110,6 +109,48 @@ final class BaselineSync
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints actionable instructions for publishing a first baseline when
|
||||
* the consumer-side fetch finds nothing. Without this, the "no
|
||||
* baseline yet" state is a dead-end for users — they see the message
|
||||
* and have to guess what to do next.
|
||||
*/
|
||||
private function emitPublishInstructions(string $repo): void
|
||||
{
|
||||
$this->output->writeln([
|
||||
' <fg=yellow>TIA</> no baseline published yet — recording locally.',
|
||||
'',
|
||||
' To share the baseline with your team, add this workflow to the repo:',
|
||||
'',
|
||||
' <fg=cyan>.github/workflows/tia-baseline.yml</>',
|
||||
'',
|
||||
' 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',
|
||||
' - uses: actions/upload-artifact@v4',
|
||||
' with:',
|
||||
' name: pest-tia-baseline',
|
||||
' path: vendor/pestphp/pest/.temp/tia/',
|
||||
' retention-days: 30',
|
||||
'',
|
||||
sprintf(' Commit, push, then run once: <fg=cyan>gh workflow run tia-baseline.yml -R %s</>', $repo),
|
||||
' Details: <fg=gray>https://pestphp.com/docs/tia/ci</>',
|
||||
'',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses `.git/config` for the `origin` remote and extracts
|
||||
* `org/repo`. Supports the two URL flavours git emits out of the box.
|
||||
|
||||
@ -32,9 +32,11 @@ final readonly class Bootstrapper implements BootstrapperContract
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.).
|
||||
* TIA's own subdirectory under Pest's `.temp/`. Keeping every TIA blob
|
||||
* in a single folder (`.temp/tia/`) avoids the `tia-`-prefix salad
|
||||
* alongside PHPUnit's unrelated files (coverage.php, test-results,
|
||||
* code-coverage/) and makes the CI artifact-upload path a single
|
||||
* directory instead of a list of individual files.
|
||||
*/
|
||||
private function tempDir(): string
|
||||
{
|
||||
@ -42,6 +44,7 @@ final readonly class Bootstrapper implements BootstrapperContract
|
||||
.DIRECTORY_SEPARATOR.'..'
|
||||
.DIRECTORY_SEPARATOR.'..'
|
||||
.DIRECTORY_SEPARATOR.'..'
|
||||
.DIRECTORY_SEPARATOR.'.temp';
|
||||
.DIRECTORY_SEPARATOR.'.temp'
|
||||
.DIRECTORY_SEPARATOR.'tia';
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ interface State
|
||||
|
||||
/**
|
||||
* Returns every key whose name starts with `$prefix`. Used to collect
|
||||
* paratest worker partials (`tia-worker-<token>.json`, etc.) without
|
||||
* paratest worker partials (`worker-edges-<token>.json`, etc.) without
|
||||
* exposing backend-specific glob semantics.
|
||||
*
|
||||
* @return list<string>
|
||||
|
||||
@ -16,7 +16,7 @@ final readonly class Fingerprint
|
||||
{
|
||||
// Bump this whenever the set of inputs or the hash algorithm changes, so
|
||||
// older graphs are invalidated automatically.
|
||||
private const int SCHEMA_VERSION = 2;
|
||||
private const int SCHEMA_VERSION = 3;
|
||||
|
||||
/**
|
||||
* @return array<string, int|string|null>
|
||||
@ -26,6 +26,13 @@ final readonly class Fingerprint
|
||||
return [
|
||||
'schema' => self::SCHEMA_VERSION,
|
||||
'php' => PHP_VERSION,
|
||||
// Loaded extensions + their versions. Guards against the
|
||||
// "recorded without pcov/xdebug → subsequent run has the
|
||||
// driver but graph has no edges" trap where the fingerprint
|
||||
// matches but the graph is effectively empty. Sorted so two
|
||||
// processes with the same extensions in different load order
|
||||
// still produce the same hash.
|
||||
'extensions' => self::extensionsFingerprint(),
|
||||
'pest' => self::readPestVersion($projectRoot),
|
||||
'composer_lock' => self::hashIfExists($projectRoot.'/composer.lock'),
|
||||
'phpunit_xml' => self::hashIfExists($projectRoot.'/phpunit.xml'),
|
||||
@ -41,6 +48,27 @@ final readonly class Fingerprint
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Deterministic hash of the PHP extension set: `ext-name@version` pairs
|
||||
* sorted alphabetically and joined. Captures both presence (pcov
|
||||
* disappeared? graph must rebuild) and version changes (xdebug minor
|
||||
* bump with coverage-mode semantics).
|
||||
*/
|
||||
private static function extensionsFingerprint(): string
|
||||
{
|
||||
$extensions = get_loaded_extensions();
|
||||
sort($extensions);
|
||||
|
||||
$parts = [];
|
||||
|
||||
foreach ($extensions as $name) {
|
||||
$version = phpversion($name);
|
||||
$parts[] = $name.'@'.($version === false ? '?' : $version);
|
||||
}
|
||||
|
||||
return hash('xxh128', implode("\n", $parts));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $a
|
||||
* @param array<string, mixed> $b
|
||||
|
||||
@ -60,6 +60,10 @@ final readonly class Laravel implements WatchDefault
|
||||
|
||||
// Blade templates — compiled to cache, source file not executed.
|
||||
'resources/views/**/*.blade.php' => [$featurePath],
|
||||
// Email templates are nested under views/email or views/emails
|
||||
// by convention and power mailable tests that render markup.
|
||||
'resources/views/email/**/*.blade.php' => [$featurePath],
|
||||
'resources/views/emails/**/*.blade.php' => [$featurePath],
|
||||
|
||||
// Translations — JSON translations read via file_get_contents,
|
||||
// PHP translations loaded via include (but during boot).
|
||||
|
||||
@ -29,6 +29,10 @@ final readonly class Livewire implements WatchDefault
|
||||
// Livewire views live alongside Blade views or in a dedicated dir.
|
||||
'resources/views/livewire/**/*.blade.php' => [$testPath],
|
||||
'resources/views/components/**/*.blade.php' => [$testPath],
|
||||
// Volt's second default mount — single-file components used as
|
||||
// full-page routes. Missing this means editing a Volt page
|
||||
// doesn't re-run its tests.
|
||||
'resources/views/pages/**/*.blade.php' => [$testPath],
|
||||
|
||||
// Livewire JS interop / Alpine plugins.
|
||||
'resources/js/**/*.js' => [$testPath],
|
||||
|
||||
@ -25,9 +25,14 @@ final readonly class Php implements WatchDefault
|
||||
|
||||
return [
|
||||
// Environment files — can change DB drivers, feature flags,
|
||||
// queue connections, etc. Not PHP, not fingerprinted.
|
||||
// queue connections, etc. Not PHP, not fingerprinted. Covers
|
||||
// the local-override variants (`.env.local`, `.env.testing.local`)
|
||||
// that both Laravel and Symfony recommend for machine-specific
|
||||
// config.
|
||||
'.env' => [$testPath],
|
||||
'.env.testing' => [$testPath],
|
||||
'.env.local' => [$testPath],
|
||||
'.env.*.local' => [$testPath],
|
||||
|
||||
// Docker / CI — can affect integration test infrastructure.
|
||||
'docker-compose.yml' => [$testPath],
|
||||
|
||||
@ -46,7 +46,11 @@ final readonly class Symfony implements WatchDefault
|
||||
'src/Kernel.php' => [$testPath],
|
||||
|
||||
// Migrations — run during setUp (before coverage window).
|
||||
// DoctrineMigrationsBundle's default is `migrations/` at the
|
||||
// project root; many Symfony projects relocate to
|
||||
// `src/Migrations/` — both covered.
|
||||
'migrations/**/*.php' => [$testPath],
|
||||
'src/Migrations/**/*.php' => [$testPath],
|
||||
|
||||
// Twig templates — compiled, source not PHP-executed.
|
||||
'templates/**/*.html.twig' => [$testPath],
|
||||
|
||||
Reference in New Issue
Block a user