This commit is contained in:
nuno maduro
2026-08-07 01:11:53 +01:00
parent 7bfb2a6185
commit cc7acfe485
16 changed files with 900 additions and 8 deletions
+5 -1
View File
@@ -130,7 +130,11 @@ final readonly class GitRepo
private function process(array $arguments, bool $mustSucceed): Process
{
$process = new Process(['git', ...$arguments], $this->path, self::ENV);
$process->setTimeout(30.0);
// Generous on purpose: these rows each spawn a real pest subprocess, so
// a loaded machine — a shared CI runner, or two of these suites at once
// — can starve a git call for tens of seconds. A timeout here fails the
// row for reasons that have nothing to do with what it asserts.
$process->setTimeout(120.0);
$process->run();
if ($mustSucceed && ! $process->isSuccessful()) {
+68
View File
@@ -124,6 +124,26 @@ final class Project
}
}
/**
* A second copy of the fixture app in a subdirectory of this project, so a
* run can be started from a root that sits *below* the git repository root.
*
* @return string The nested project's absolute path.
*/
public function nested(string $directory = 'nested'): string
{
$path = $this->path($directory);
if (! is_dir($path) && ! @mkdir($path, 0755, true) && ! is_dir($path)) {
throw new RuntimeException(sprintf('Unable to create [%s].', $path));
}
self::copy(__DIR__.'/app', $path);
$this->scaffoldVendor($path);
return $path;
}
public function worktree(string $branch): string
{
$path = $this->path.'-worktree-'.preg_replace('/[^a-z0-9]+/i', '-', $branch);
@@ -159,6 +179,12 @@ final class Project
'COLLISION_IGNORE_DURATION' => 'true',
'PARATEST' => '0',
'PAO_DISABLE' => '1',
// Recording is what needs a driver, and recording happens here,
// in the subprocess — never in the process running these rows.
// Asking for coverage mode only here lets a CI job leave xdebug
// off for the suite it is running (whose collection under xdebug
// costs more than every scenario put together) and still record.
'XDEBUG_MODE' => 'coverage',
'HOME' => $this->home(),
'GITHUB_EVENT_PATH' => '',
'CI_DEFAULT_BRANCH' => '',
@@ -239,6 +265,48 @@ final class Project
$sentinel ? $this->sentinel() : $this->snapshot();
}
/**
* Take the graph out of the state dir and hand back its JSON, so it can be
* served as the artifact a remote baseline fetch downloads.
*/
public function detachGraph(): string
{
$json = $this->state()->read(Tia::KEY_GRAPH);
if ($json === null) {
throw new RuntimeException('There is no graph to detach.');
}
if (! $this->state()->delete(Tia::KEY_GRAPH)) {
throw new RuntimeException('Unable to remove the detached graph.');
}
$this->snapshot();
return $json;
}
/**
* Install a stand-in for the GitHub CLI and return the environment that
* points a run at it. `$mode` names the failure it should serve (see
* `stubs/gh`); `$payload` is the graph.json its artifact carries.
*
* @return array<string, string>
*/
public function gh(string $mode = 'ok', string $payload = '{}'): array
{
self::mirror(__DIR__.'/stubs/gh', $this->path('stub/gh'));
chmod($this->path('stub/gh'), 0755);
$this->write('payload/graph.json', $payload);
return [
'PATH' => $this->path('stub').PATH_SEPARATOR.(string) getenv('PATH'),
'GH_STUB_MODE' => $mode,
'GH_STUB_PAYLOAD' => $this->path('payload/graph.json'),
];
}
public static function testId(string $testFile, string $description): string
{
$basename = basename($testFile, '.php');
+50
View File
@@ -0,0 +1,50 @@
#!/bin/sh
# A stand-in for the GitHub CLI, so the remote-baseline path can be exercised
# without a network. `GH_STUB_MODE` picks the failure to serve; `GH_STUB_PAYLOAD`
# names the graph.json the fake artifact carries.
if [ "$1" = "auth" ]; then
[ "$GH_STUB_MODE" = "unauthenticated" ] && exit 1
exit 0
fi
if [ "$1" = "run" ] && [ "$2" = "list" ]; then
case "$GH_STUB_MODE" in
no-runs) exit 0 ;;
list-404) echo "HTTP 404: Not Found" >&2; exit 1 ;;
list-network) echo "could not resolve host: api.github.com" >&2; exit 1 ;;
esac
echo 987654321
exit 0
fi
if [ "$1" = "api" ]; then
echo 2048
exit 0
fi
if [ "$1" = "run" ] && [ "$2" = "download" ]; then
case "$GH_STUB_MODE" in
download-403) echo "HTTP 403: Forbidden" >&2; exit 1 ;;
download-network) echo "connection refused" >&2; exit 1 ;;
esac
dir=""
previous=""
for argument in "$@"; do
[ "$previous" = "-D" ] && dir="$argument"
previous="$argument"
done
[ -z "$dir" ] && exit 1
case "$GH_STUB_MODE" in
missing-asset) echo "{}" > "$dir/other.json" ;;
corrupt) printf 'not json at all' > "$dir/graph.json" ;;
*) cp "$GH_STUB_PAYLOAD" "$dir/graph.json" ;;
esac
exit 0
fi
exit 1