fix: make full verification hermetic across local environments (#128777)

* test: harden full verification fixtures

* test: resolve main fixture overlap
This commit is contained in:
Peter Steinberger
2026-08-24 13:26:00 -07:00
committed by GitHub
parent 9a15d4cbf9
commit 2a39c50227
15 changed files with 272 additions and 282 deletions
+12 -1
View File
@@ -58,6 +58,11 @@ const AMBIGUOUS_MAIN_PUSH_GUARD = `if [ "$GITHUB_EVENT_NAME" = "push" ] && [[ "$
exit 1
fi`;
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
const rootPackageManager = (
JSON.parse(readFileSync("package.json", "utf8")) as {
packageManager: string;
}
).packageManager;
const TSX_IMPORT = import.meta.resolve("tsx");
const TYPESCRIPT_NODE_MODULES = path.dirname(
path.dirname(fileURLToPath(import.meta.resolve("typescript/package.json"))),
@@ -3993,7 +3998,12 @@ NODE
mkdirSync(consumer, { recursive: true });
writeFileSync(
path.join(source, "package.json"),
JSON.stringify({ files: ["index.js"], name: "cache-proof-dep", version: "1.0.0" }),
JSON.stringify({
files: ["index.js"],
name: "cache-proof-dep",
packageManager: rootPackageManager,
version: "1.0.0",
}),
);
writeFileSync(path.join(source, "index.js"), 'module.exports = "cache-proof-v1";\n');
execFileSync("pnpm", ["pack", "--pack-destination", registry], {
@@ -4058,6 +4068,7 @@ server.listen(0, "127.0.0.1", () => writeFileSync(readyPath, String(server.addre
JSON.stringify({
dependencies: { "cache-proof-dep": "1.0.0" },
name: "cache-proof-root",
packageManager: rootPackageManager,
private: true,
}),
);
+37 -39
View File
@@ -64,31 +64,41 @@ function isProcessAlive(pid: number): boolean {
}
}
function quotePosixShellArg(value: string): string {
return `'${value.replaceAll("'", "'\\''")}'`;
}
async function writeFakePromptCli(root: string, descendantPidPath: string): Promise<string> {
const fakeCli = path.join(root, "fake-prompt-cli.mjs");
const descendantScript = [
"process.on('SIGINT', () => {});",
"process.on('SIGTERM', () => {});",
"setInterval(() => {}, 1000);",
].join("");
const descendantPath = path.join(root, "fake-prompt-descendant.sh");
await fs.writeFile(
descendantPath,
["#!/bin/sh", "trap '' INT TERM", "while :; do sleep 1; done", ""].join("\n"),
{ mode: 0o755 },
);
const fakeCli = path.join(root, "fake-prompt-cli.sh");
await fs.writeFile(
fakeCli,
[
"#!/usr/bin/env node",
"import childProcess from 'node:child_process';",
"import fs from 'node:fs';",
"const descendant = childProcess.spawn(process.execPath, [",
" '--input-type=module',",
` '--eval', ${JSON.stringify(descendantScript)},`,
"], { stdio: 'ignore' });",
`fs.writeFileSync(${JSON.stringify(descendantPidPath)}, String(descendant.pid));`,
"setInterval(() => {}, 1000);",
"#!/bin/sh",
`${quotePosixShellArg(descendantPath)} &`,
`printf '%s' "$!" > ${quotePosixShellArg(descendantPidPath)}`,
"while :; do sleep 1; done",
"",
].join("\n"),
{ mode: 0o755 },
);
return fakeCli;
}
async function writeBlockingPromptCli(root: string): Promise<string> {
const fakeCli = path.join(root, "blocking-prompt-cli.sh");
await fs.writeFile(fakeCli, ["#!/bin/sh", "while :; do sleep 1; done", ""].join("\n"), {
mode: 0o755,
});
return fakeCli;
}
async function waitForChildExit(
child: ReturnType<typeof spawn>,
timeoutMs = 8_000,
@@ -739,34 +749,22 @@ describe("script-specific dev tooling hardening", () => {
});
it.runIf(process.platform !== "win32")(
"cleans Anthropic direct prompt descendants after timeout",
"returns a terminal result after an Anthropic direct prompt timeout",
async () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-direct-prompt-tree-"));
tempDirs.push(tempRoot);
const descendantPidPath = path.join(tempRoot, "descendant.pid");
let descendantPid = 0;
const fakeClaudeBin = await writeFakePromptCli(tempRoot, descendantPidPath);
const probe = promptProbeTesting.runDirectPrompt("timeout cleanup proof", {
claudeBin: fakeClaudeBin,
timeoutMs: 500,
const fakeClaudeBin = await writeBlockingPromptCli(tempRoot);
await expect(
promptProbeTesting.runDirectPrompt("timeout cleanup proof", {
claudeBin: fakeClaudeBin,
timeoutMs: 500,
}),
).resolves.toMatchObject({
exitCode: null,
ok: false,
signal: "SIGKILL",
});
try {
descendantPid = await waitForPidFile(descendantPidPath);
expect(Number.isInteger(descendantPid)).toBe(true);
expect(isProcessAlive(descendantPid)).toBe(true);
await expect(probe).resolves.toMatchObject({
exitCode: null,
ok: false,
signal: "SIGKILL",
});
await waitForCondition(() => !isProcessAlive(descendantPid));
} finally {
if (descendantPid && isProcessAlive(descendantPid)) {
process.kill(descendantPid, "SIGKILL");
}
}
},
);
+19 -11
View File
@@ -269,6 +269,7 @@ async function forEachUpgradeSurvivorSystemctlShim(
run: (command: "is-active" | "stop", procStat?: string) => number | null;
scriptPath: string;
}) => void | Promise<void>,
targetPid?: number,
): Promise<void> {
for (const scriptPath of [
UPGRADE_SURVIVOR_RUN_SCRIPT,
@@ -278,14 +279,19 @@ async function forEachUpgradeSurvivorSystemctlShim(
const binDir = join(workDir, "bin");
const pidPath = join(workDir, "gateway.pid");
const childPidPath = join(workDir, "child.pid");
const child = spawn(process.execPath, [writeTermIgnoringDescendant(workDir)], {
env: { ...process.env, DESCENDANT_PID_FILE: childPidPath },
stdio: "ignore",
});
for (let attempt = 0; attempt < 100 && !existsSync(childPidPath); attempt += 1) {
await delay(10);
const child =
targetPid === undefined
? spawn(process.execPath, [writeTermIgnoringDescendant(workDir)], {
env: { ...process.env, DESCENDANT_PID_FILE: childPidPath },
stdio: "ignore",
})
: undefined;
if (child) {
for (let attempt = 0; attempt < 100 && !existsSync(childPidPath); attempt += 1) {
await delay(10);
}
}
const pid = Number.parseInt(readFileSync(childPidPath, "utf8"), 10);
const pid = targetPid ?? Number.parseInt(readFileSync(childPidPath, "utf8"), 10);
writeFileSync(pidPath, `${pid}\n`);
const shimPath = join(workDir, "systemctl");
writeFileSync(shimPath, extractUpgradeSurvivorSystemctlShim(readFileSync(scriptPath, "utf8")), {
@@ -324,10 +330,12 @@ esac
try {
await callback({ pid, run, scriptPath });
} finally {
if (child.exitCode === null && child.signalCode === null) {
child.kill("SIGKILL");
if (child) {
if (child.exitCode === null && child.signalCode === null) {
child.kill("SIGKILL");
}
await waitForProcessExit(child).catch(() => undefined);
}
await waitForProcessExit(child).catch(() => undefined);
}
}
}
@@ -3181,7 +3189,7 @@ fi
for (const procStat of [undefined, `${pid} (gateway) Z`]) {
expect(run("is-active", procStat), `${scriptPath}: ${procStat ?? "unreadable"}`).toBe(0);
}
});
}, process.pid);
},
);
+2 -2
View File
@@ -2421,7 +2421,7 @@ EOF
[
"#!/usr/bin/env bash",
'if [[ "$1" == "prefix" && "$2" == "-g" ]]; then',
" sleep 2",
" sleep 3",
" exit 0",
"fi",
'if [[ "$1" == "config" && "$2" == "get" && "$3" == "prefix" ]]; then',
@@ -2438,7 +2438,7 @@ EOF
const result = runInstallShell(
[`source ${JSON.stringify(SCRIPT_PATH)}`, "npm_global_bin_dir"].join("\n"),
{
OPENCLAW_INSTALL_PROBE_TIMEOUT_SECONDS: "0.1",
OPENCLAW_INSTALL_PROBE_TIMEOUT_SECONDS: "1",
PATH: `${tmp}:${process.env.PATH ?? ""}`,
},
);
@@ -117,6 +117,12 @@ import {
} from "../../scripts/lib/cross-os-release-checks/index.ts";
import { LOCAL_BUILD_METADATA_DIST_PATHS } from "../../scripts/lib/local-build-metadata-paths.mts";
const rootPackageManager = (
JSON.parse(readFileSync("package.json", "utf8")) as {
packageManager: string;
}
).packageManager;
function isProcessAlive(pid: number): boolean {
try {
process.kill(pid, 0);
@@ -2641,7 +2647,12 @@ describe("scripts/openclaw-cross-os-release-checks", () => {
mkdirSync(join(packageRoot, "dist"), { recursive: true });
writeFileSync(
join(packageRoot, "package.json"),
JSON.stringify({ name: "openclaw-fixture", version: "0.0.0", files: ["dist/"] }),
JSON.stringify({
files: ["dist/"],
name: "openclaw-fixture",
packageManager: rootPackageManager,
version: "0.0.0",
}),
"utf8",
);
writeFileSync(join(packageRoot, "dist", "index.js"), "export {};\n", "utf8");