openclaw/test/scripts/direct-run-entrypoints.test.ts
Peter Steinberger e9f37f3c1d
fix(test): CLI startup, dead-code, boundary, oxlint, and tsgo tooling tests time out on loaded hosts while polling ready files (#162643)
Tooling tests for CLI startup benchmarking, dead-code checks, dev-tooling safety, direct-run entrypoints, additional boundary checks, the oxlint runner, tsgo core-test shards, the upgrade-survivor plugin proxy, and CLI startup metadata waited for fixture runners, compilers, and descendants by polling ready files, PID files, and the process table against 1-15 s wall-clock deadlines, which a loaded host outlasts.

Readiness and PID publication now arrive on owned channels (stdout/IPC lines written after the fact they announce, relayed through wrappers for nested children), completion is joined through retained child close promises and runner settlement, and waits raced against a command that can settle first fail fast with the old diagnostic. Foreign-descendant extinction without an owner join keeps a deadline-free check bound to the test signal, preserving the dead-versus-zombie policy. No product source, test timeout, product timeout, or assertion meaning changed.

Part of the polling audit from #162274. Proof on Blacksmith Testbox: delayed-startup fault probes fail the original bytes and pass these bytes; forced-abort probes reach cleanup; 180/180 standalone runs across the nine files; 3/3 replays of each of the six owning tooling shards; tsgo; Codex autoreview and ClawSweeper clean.
2026-10-01 04:34:15 -07:00

1015 lines
37 KiB
TypeScript

import { spawnSync } from "node:child_process";
import {
existsSync,
mkdirSync,
mkdtempSync,
readFileSync,
readdirSync,
rmSync,
writeFileSync,
} from "node:fs";
import { createRequire } from "node:module";
import { tmpdir, userInfo } from "node:os";
import path from "node:path";
import { setTimeout as waitForProcessTick } from "node:timers/promises";
import { pathToFileURL } from "node:url";
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
import { detectChangedScope } from "../../scripts/ci-changed-scope.mjs";
import { isDirectRunPath } from "../../scripts/lib/direct-run.mjs";
import * as managedChild from "../../scripts/lib/managed-child-process.mts";
import { scriptModuleEntrypoints } from "../../scripts/script-module-runtime.test-support.mjs";
import {
resolveRuntimeWorkerArgv,
resolveRuntimeWorkerUrl,
} from "../../src/infra/runtime-worker-url.js";
import { readWindowsProcessStartTimeSync } from "../../src/infra/windows-process-start.js";
import {
fixtureReceiptClientSource,
openFixtureReceiptChannel,
type FixtureReceiptChannel,
} from "../helpers/fixture-receipts.js";
import { isProcessAlive } from "../helpers/process-wait.js";
import { createDeferred, withinTest } from "../helpers/promise.js";
import { runQaGatewayFixture } from "../helpers/qa-gateway-cleanup.js";
import type { runNodeScript } from "../helpers/run-node-script.js";
import {
formatShimResult,
TSX_SHIM_WRAPPERS,
withShimFixture,
writeEsmPluginFixture,
} from "./direct-run-entrypoints.test-support.js";
import { preparedScriptWrapperEnv } from "./prepared-script-wrapper.test-support.js";
let receipts: FixtureReceiptChannel;
beforeAll(async () => {
receipts = await openFixtureReceiptChannel();
});
afterAll(async () => {
await receipts?.close();
});
async function implementationReadyBeforeSettlement(
pidPath: string,
operation: PromiseLike<unknown>,
): Promise<number> {
// The PID record precedes the receipt; command output and exit use different pipes.
const recorded = () => existsSync(pidPath) && readFileSync(pidPath, "utf8").trim() !== "";
const settled = Promise.resolve(operation).then(
() => {
if (!recorded()) {
throw new Error(`timeout waiting for pid in ${pidPath}`);
}
},
(error: unknown) => {
if (!recorded()) {
throw error;
}
},
);
await Promise.race([receipts.waitFor(pidPath, "ready"), settled]);
return Number.parseInt(readFileSync(pidPath, "utf8"), 10);
}
// Recovery owns only foreign PIDs, so it has no ChildProcess exit handle to join.
async function waitForExtinction(pid: number, signal: AbortSignal): Promise<void> {
try {
while (isProcessAlive(pid)) {
await waitForProcessTick(10, undefined, { signal });
}
} catch (error) {
throw new Error(`process still alive: ${pid}`, { cause: error });
}
}
const DIRECT_RUN_SCRIPTS = [
"scripts/android-app-i18n.ts",
"scripts/android-pin-version.ts",
"scripts/ci-run-timings.mjs",
"scripts/e2e/lib/package-compat.mjs",
"scripts/generate-bundled-channel-config-metadata.ts",
"scripts/plan-release-workflow-matrix.mjs",
"scripts/run-additional-boundary-checks.mts",
"scripts/verify-docker-attestations.mjs",
] as const;
const EXECUTABLE_ENTRYPOINTS = [
{
args: ["--direct-run-smoke"],
output: "Unknown CI run timing option: --direct-run-smoke",
script: "scripts/ci-run-timings.mjs",
status: 1,
},
{
args: ["2026.7.33"],
output: "0",
script: "scripts/e2e/lib/package-compat.mjs",
status: 0,
},
{
args: ["--clawhub-release-security-mode", "2026.6.35"],
output: "absent",
script: "scripts/e2e/lib/package-compat.mjs",
status: 0,
},
{
args: ["--clawhub-release-security-mode", "2026.8.33"],
output: "required",
script: "scripts/e2e/lib/package-compat.mjs",
status: 0,
},
{
args: [],
output: "docker_e2e_count=",
script: "scripts/plan-release-workflow-matrix.mjs",
status: 0,
},
{
args: ["--help"],
output: "Usage: node --import tsx scripts/run-additional-boundary-checks.mts",
script: "scripts/run-additional-boundary-checks.mts",
status: 0,
},
{
args: ["--help"],
output: "Usage: node scripts/verify-docker-attestations.mjs",
script: "scripts/verify-docker-attestations.mjs",
status: 0,
},
] as const;
function runEntrypoint(entrypoint: (typeof EXECUTABLE_ENTRYPOINTS)[number]) {
const script = path.resolve(entrypoint.script);
const args =
entrypoint.script === "scripts/run-additional-boundary-checks.mts"
? [
...resolveRuntimeWorkerArgv(
resolveRuntimeWorkerUrl(scriptModuleEntrypoints.additionalBoundaryChecks),
),
...entrypoint.args,
]
: [script, ...entrypoint.args];
return spawnSync(process.execPath, args, {
cwd: process.cwd(),
encoding: "utf8",
env: {
...process.env,
DOCKER_LANES: "",
GITHUB_STEP_SUMMARY: "",
INCLUDE_LIVE_SUITES: "",
INCLUDE_RELEASE_PATH_SUITES: "",
LIVE_MODEL_PROVIDERS: "",
LIVE_SUITE_FILTER: "",
RELEASE_TEST_PROFILE: "",
},
timeout: 30_000,
});
}
type ModulesEnv = Partial<Record<"PNPM_CONFIG_MODULES_DIR" | "npm_config_modules_dir", string>>;
function writeTsxFixture(modulesDir: string, marker: string) {
const packageDir = path.join(modulesDir, "tsx");
mkdirSync(packageDir, { recursive: true });
writeFileSync(
path.join(packageDir, "package.json"),
JSON.stringify({ name: "tsx", type: "module", exports: { "./esm": "./loader.mjs" } }),
);
writeFileSync(
path.join(packageDir, "loader.mjs"),
`process.env.OPENCLAW_TSX_FIXTURE_LOADER = ${JSON.stringify(marker)};\n`,
);
const dependencyDir = path.join(modulesDir, "shim-dependency");
mkdirSync(dependencyDir, { recursive: true });
writeFileSync(
path.join(dependencyDir, "package.json"),
JSON.stringify({ name: "shim-dependency", type: "module", exports: "./index.js" }),
);
writeFileSync(path.join(dependencyDir, "index.js"), 'export const value = "loaded";\n');
}
function runShimFixture(
wrapper: (typeof TSX_SHIM_WRAPPERS)[number],
configureModules: (paths: {
checkoutRoot: string;
fixtureRoot: string;
}) => ModulesEnv = () => ({}),
nodeArgs: readonly string[] = [],
) {
return withShimFixture(
wrapper,
({ checkoutRoot, fixtureRoot, implementationPath, wrapperPath, runNode }) => {
writeFileSync(
implementationPath,
'import { value } from "shim-dependency";\nprocess.stdout.write(JSON.stringify({ loader: process.env.OPENCLAW_TSX_FIXTURE_LOADER, dependency: value, args: process.argv.slice(2), execArgv: process.execArgv }));\n',
);
writeTsxFixture(path.join(checkoutRoot, "node_modules"), "checkout");
const modulesEnv = configureModules({ checkoutRoot, fixtureRoot });
const env = { ...process.env };
delete env.NODE_OPTIONS;
delete env.NODE_PATH;
delete env.PNPM_CONFIG_MODULES_DIR;
delete env.npm_config_modules_dir;
Object.assign(env, modulesEnv);
return runNode(
[...nodeArgs, wrapperPath, "--hydrated-proof", "--no-maglev"],
env,
fixtureRoot,
);
},
);
}
function expectShimLoader(
result: Awaited<ReturnType<typeof runShimFixture>>,
loader: string,
nodeArgs: readonly string[] = [],
) {
expect(result.error, formatShimResult(result)).toBeUndefined();
expect(result.status, formatShimResult(result)).toBe(0);
expect(JSON.parse(result.stdout)).toEqual({
loader,
dependency: "loaded",
args: ["--hydrated-proof", "--no-maglev"],
execArgv: ["--import", expect.stringMatching(/^file:.*\/scripts\/tsx\.mjs$/u), ...nodeArgs],
});
}
describe("script direct-run entrypoints", () => {
it
.skipIf(process.platform === "win32")
.for([
"scripts/run-vitest.mjs",
"scripts/check-changed.mjs",
"scripts/run-tsgo.mjs",
"scripts/run-oxlint.mjs",
"scripts/run-tsgo-core-test-shards.mjs",
] as const)(
"lets %s finish implementation cleanup beyond the shim force-kill window",
async (wrapper, { signal }) => {
await withShimFixture(wrapper, async (fixture) => {
const { checkoutRoot, fixtureRoot, implementationPath, wrapperPath, runNode } = fixture;
const ownerPath = path.join(fixtureRoot, "owner.pid");
const settledPath = path.join(fixtureRoot, "cleanup-settled");
const clockPath = path.join(fixtureRoot, "supervisor-clock.mjs");
// Scale both owners equally: a competing 5s or 10s cutoff must still fail.
// Readiness and the test harness retain real time.
writeFileSync(
clockPath,
`const realSetTimeout = globalThis.setTimeout;
globalThis.setTimeout = (callback, delay, ...args) =>
realSetTimeout(callback, delay / 20, ...args);\n`,
);
writeTsxFixture(path.join(checkoutRoot, "node_modules"), "checkout");
writeFileSync(
implementationPath,
`import fs from "node:fs";
${fixtureReceiptClientSource(receipts.endpoint)}
const keepAlive = setInterval(() => {}, 1000);
process.once("SIGTERM", () => {
setTimeout(() => {
fs.writeFileSync(${JSON.stringify(settledPath)}, "settled");
clearInterval(keepAlive);
process.exitCode = 143;
}, 11000);
});
fs.writeFileSync(${JSON.stringify(ownerPath)}, String(process.ppid));
sendReceipt(${JSON.stringify(ownerPath)}, "ready");
`,
);
const completion = runNode(
[wrapperPath],
{
...process.env,
NODE_OPTIONS: [process.env.NODE_OPTIONS, `--import=${pathToFileURL(clockPath).href}`]
.filter(Boolean)
.join(" "),
},
fixtureRoot,
);
const owner = await withinTest(
implementationReadyBeforeSettlement(ownerPath, completion),
signal,
);
process.kill(owner, "SIGTERM");
const result = await completion;
expect(result.status, formatShimResult(result)).toBe(143);
expect(readFileSync(settledPath, "utf8")).toBe("settled");
expect(isProcessAlive(owner)).toBe(false);
});
},
);
it.each(["wrapper", "preload"])(
"loads compiled ESM through require from the %s with import-only dependencies",
async (entrypoint) => {
await withShimFixture(TSX_SHIM_WRAPPERS[0], async (fixture) => {
const { fixtureRoot, implementationPath, wrapperPath, runNode } = fixture;
writeFileSync(implementationPath, writeEsmPluginFixture(fixtureRoot));
const env: NodeJS.ProcessEnv = {
...process.env,
PNPM_CONFIG_MODULES_DIR: path.dirname(
path.dirname(createRequire(import.meta.url).resolve("tsx/package.json")),
),
};
delete env.NODE_OPTIONS;
const args =
entrypoint === "wrapper"
? [wrapperPath]
: ["--import", "./scripts/tsx.mjs", implementationPath];
const result = await runNode(args, env, process.cwd());
expect(result.error, formatShimResult(result)).toBeUndefined();
expect(result.status, formatShimResult(result)).toBe(0);
expect(JSON.parse(result.stdout)).toEqual({
value: "import-only",
evaluations: 1,
transformed: "transformed",
sourceAlias: true,
});
});
},
);
it.each([false, true])(
"preserves preloads when forking into another cwd (equals=%s)",
async (equals) => {
await withShimFixture(TSX_SHIM_WRAPPERS[0], async (fixture) => {
const { checkoutRoot, fixtureRoot, implementationPath, runNode } = fixture;
const forkCwd = path.join(fixtureRoot, "child cwd");
mkdirSync(forkCwd);
const childPath = path.join(fixtureRoot, "fork-child.mts");
const extraPreloadPath = path.join(fixtureRoot, "extra-preload.mjs");
writeFileSync(extraPreloadPath, 'globalThis.fixturePreload = "preserved";\n');
const snapshotSource = `
enum Transformed { Value = "transformed" }
console.log(JSON.stringify({ transformed: Transformed.Value, preload: globalThis.fixturePreload,
args: process.argv.slice(2), cwd: process.cwd(), execArgv: process.execArgv }));
`;
writeFileSync(childPath, `${snapshotSource}\nprocess.exitCode = 17;\n`);
writeFileSync(
implementationPath,
`${snapshotSource}
import { fork } from "node:child_process";
const child = fork(${JSON.stringify(childPath)}, process.argv.slice(2), {
cwd: ${JSON.stringify(forkCwd)}, stdio: "inherit",
});
process.exitCode = await new Promise((resolve, reject) => {
child.once("error", reject);
child.once("exit", code => resolve(code ?? 1));
});
`,
);
const nodeFlags = ["--no-warnings", "--import", pathToFileURL(extraPreloadPath).href];
const trailingFlags = ["--title", "./scripts/tsx.mjs", "--import=node:fs"];
const preload = equals ? ["--import=./scripts/tsx.mjs"] : ["--import", "./scripts/tsx.mjs"];
const bootstrapUrl = pathToFileURL(path.join(checkoutRoot, "scripts/tsx.mjs")).href;
const expectedPreload = equals ? [`--import=${bootstrapUrl}`] : ["--import", bootstrapUrl];
const env: NodeJS.ProcessEnv = {
...process.env,
TMPDIR: fixtureRoot,
TMP: fixtureRoot,
TEMP: fixtureRoot,
PNPM_CONFIG_MODULES_DIR: path.dirname(
path.dirname(createRequire(import.meta.url).resolve("tsx/package.json")),
),
};
delete env.TSX_DISABLE_CACHE;
delete env.NODE_OPTIONS;
const result = await runNode(
[
...nodeFlags,
...preload,
...trailingFlags,
implementationPath,
"argument with spaces",
"--fork-proof",
],
env,
checkoutRoot,
);
expect(result.error, formatShimResult(result)).toBeUndefined();
expect(result.status, formatShimResult(result)).toBe(17);
expect(
result.stdout
.trim()
.split("\n")
.map((line) => JSON.parse(line)),
).toEqual(
[checkoutRoot, forkCwd].map((cwd) => ({
transformed: "transformed",
preload: "preserved",
args: ["argument with spaces", "--fork-proof"],
cwd,
execArgv: [...nodeFlags, ...expectedPreload, ...trailingFlags],
})),
);
});
},
);
it.each(["wrapper", "root package preloads"])(
"keeps %s and raw tsx children off disk caches without changing other cache settings",
async (entrypoint) => {
await withShimFixture(TSX_SHIM_WRAPPERS[0], async (fixture) => {
const { fixtureRoot, implementationPath, wrapperPath, runNode } = fixture;
const require = createRequire(import.meta.url);
const modulesDir = path.dirname(path.dirname(require.resolve("tsx/package.json")));
const tempRoot = path.join(fixtureRoot, "temp");
const cacheRoots = ["tsx", `tsx-${process.geteuid?.() ?? userInfo().username}`].map(
(name) => path.join(tempRoot, name),
);
for (const cacheRoot of cacheRoots) {
mkdirSync(cacheRoot, { recursive: true });
writeFileSync(path.join(cacheRoot, "0-sentinel"), "keep");
}
const accessLog = path.join(fixtureRoot, "cache-access.log");
const guard = path.join(fixtureRoot, "cache-guard.cjs");
writeFileSync(
guard,
`
const fs = require("node:fs");
const path = require("node:path");
const readdirSync = fs.readdirSync;
fs.readdirSync = function (directory, ...args) {
if (/^tsx(?:-|$)/.test(path.basename(String(directory)))) {
fs.appendFileSync(${JSON.stringify(accessLog)}, "cache scan\\n");
throw new Error("Unexpected tsx disk cache access");
}
return readdirSync.call(this, directory, ...args);
};
`,
);
const preservedEnv = Object.fromEntries(
[
"TMPDIR",
"TMP",
"TEMP",
"XDG_CACHE_HOME",
"NODE_COMPILE_CACHE",
"OPENCLAW_VITEST_FS_MODULE_CACHE_PATH",
].map((key) => [
key,
key === "TMPDIR" || key === "TEMP" ? tempRoot : path.join(fixtureRoot, key),
]),
);
const childPath = path.join(fixtureRoot, "child.mts");
const snapshotSource = `
enum Transformed { Value = "transformed" }
console.log(JSON.stringify({
transformed: Transformed.Value,
args: process.argv.slice(2),
cwd: process.cwd(),
env: Object.fromEntries(${JSON.stringify(Object.keys(preservedEnv))}.map(key => [key, process.env[key]])),
}));
`;
writeFileSync(childPath, `${snapshotSource}\nprocess.exitCode = 17;\n`);
writeFileSync(
implementationPath,
`${snapshotSource}
import { spawnSync } from "node:child_process";
const child = spawnSync(process.execPath, ["--import", "tsx", ${JSON.stringify(childPath)}, ...process.argv.slice(2)], { stdio: "inherit" });
if (child.error) throw child.error;
process.exitCode = child.status ?? 1;
`,
);
const { scripts } = JSON.parse(readFileSync("package.json", "utf8")) as {
scripts: Record<string, string>;
};
const preloads = [
...new Set(
Object.values(scripts).flatMap((command) =>
[...command.matchAll(/--import(?:=|\s+)(\S+)/gu)].map((match) => match[1]!),
),
),
];
expect(preloads.length).toBeGreaterThan(0);
const launches =
entrypoint === "wrapper"
? [[wrapperPath]]
: preloads.map((preload) => ["--import", preload, implementationPath]);
for (const cacheFlag of [undefined, ""]) {
const env: NodeJS.ProcessEnv = {
...process.env,
...preservedEnv,
PNPM_CONFIG_MODULES_DIR: modulesDir,
NODE_OPTIONS: `--require ${JSON.stringify(guard)}`,
};
delete env.TSX_DISABLE_CACHE;
if (cacheFlag !== undefined) {
env.TSX_DISABLE_CACHE = cacheFlag;
}
for (const launch of launches) {
const result = await runNode(
[...launch, "argument with spaces", "--proof"],
env,
process.cwd(),
);
expect(result.error, formatShimResult(result)).toBeUndefined();
expect(result.status, formatShimResult(result)).toBe(17);
expect(
result.stdout
.trim()
.split("\n")
.map((line) => JSON.parse(line)),
).toEqual(
Array.from({ length: 2 }, () => ({
transformed: "transformed",
args: ["argument with spaces", "--proof"],
cwd: process.cwd(),
env: preservedEnv,
})),
);
if (entrypoint === "wrapper") {
expect(result.stderr.trim().split("\n").at(-1)).toBe("[test] FAILED (exit 17)");
}
}
}
expect(existsSync(accessLog)).toBe(false);
for (const cacheRoot of cacheRoots) {
expect(readdirSync(cacheRoot)).toEqual(["0-sentinel"]);
expect(readFileSync(path.join(cacheRoot, "0-sentinel"), "utf8")).toBe("keep");
}
});
},
);
it.each(EXECUTABLE_ENTRYPOINTS)("runs $script through its guarded CLI", (entrypoint) => {
const result = runEntrypoint(entrypoint);
const output = `${result.stdout}${result.stderr}`;
expect(result.error).toBeUndefined();
expect(result.status).toBe(entrypoint.status);
expect(output).toContain(entrypoint.output);
});
it("runs the checked-out Crabbox wrapper through its managed child", async () => {
await withShimFixture("scripts/crabbox-wrapper.mjs", async ({ fixtureRoot, runNode }) => {
const fixtureVersion = "999.0.0";
const binDir = path.join(fixtureRoot, "fake bin");
const home = path.join(fixtureRoot, "home");
const state = path.join(fixtureRoot, "state");
const invocationLog = path.join(fixtureRoot, "invocations.jsonl");
mkdirSync(binDir);
mkdirSync(state);
// A failed version probe must fail before managed installation can download anything.
writeFileSync(path.join(state, "tools"), "managed installation disabled for this fixture\n");
const responses = {
"--version": `crabbox ${fixtureVersion}`,
"run --help": "provider: ssh\n -provider string\n",
"config show --json": JSON.stringify({ provider: "ssh" }),
};
writeFileSync(
path.join(binDir, process.platform === "win32" ? "crabbox.cjs" : "crabbox"),
String.raw`#!/usr/bin/env node
const fs = require("node:fs");
const args = process.argv.slice(2);
const record = (stage, details = {}) => fs.appendFileSync(${JSON.stringify(invocationLog)}, JSON.stringify({ stage, args, pid: process.pid, atMs: Date.now(), ...details }) + "\n");
record("entered");
const { readWindowsProcessStartTimeSync } = require(${JSON.stringify(path.resolve("src/infra/windows-process-start.ts"))});
record("identity-module-loaded");
record("identity-read", { startTimeMs: readWindowsProcessStartTimeSync(process.pid, 0) });
const response = ${JSON.stringify(responses)}[args.join(" ")];
if (response === undefined) throw new Error("Unexpected fixture command: " + JSON.stringify(args));
process.stdout.write(response + "\n");
record("stdout-write-returned");
`,
{ mode: 0o755 },
);
writeFileSync(
path.join(binDir, "crabbox.cmd"),
[
"@echo off",
`"${process.execPath}" "%~dp0crabbox.cjs" %*`,
"exit /b %errorlevel%",
"",
].join("\r\n"),
);
const env: NodeJS.ProcessEnv = {
SystemRoot: process.env.SystemRoot,
ComSpec: process.env.ComSpec,
PATH: [binDir, path.dirname(process.execPath), process.env.PATH ?? ""].join(path.delimiter),
HOME: home,
USERPROFILE: home,
APPDATA: path.join(home, "AppData", "Roaming"),
LOCALAPPDATA: path.join(home, "AppData", "Local"),
XDG_CONFIG_HOME: path.join(home, "config"),
XDG_STATE_HOME: path.join(home, "state"),
OPENCLAW_STATE_DIR: state,
TMPDIR: fixtureRoot,
TMP: fixtureRoot,
TEMP: fixtureRoot,
CRABBOX_PROVIDER: "ssh",
OPENCLAW_CRABBOX_WRAPPER_IGNORE_REPO_BINARY: "1",
};
// Preserve the real wrapper and Job lifecycle; compile its graph before the deadline.
const result = await runNode(
[path.resolve("scripts/crabbox-wrapper.mjs"), "--version"],
preparedScriptWrapperEnv(
[
[
new URL("../../scripts/crabbox-wrapper.mts", import.meta.url),
resolveRuntimeWorkerUrl(scriptModuleEntrypoints.crabboxWrapper),
],
],
env,
),
process.cwd(),
);
// Read before asserting: joined timeout cleanup removes the fixture even on failure.
// A returned stdout write records progress, not completed pipe drainage.
const trace = existsSync(invocationLog) ? readFileSync(invocationLog, "utf8") : "";
const details = `${formatShimResult(result)}\nfixture stages:\n${trace || "no invocation recorded"}`;
expect(result.error, details).toBeUndefined();
expect(result.status, details).toBe(0);
expect(result.stdout, details).toBe(`crabbox ${fixtureVersion}\n`);
const invocations = trace
.trim()
.split("\n")
.map(
(line) =>
JSON.parse(line) as {
stage: string;
args: string[];
pid: number;
startTimeMs?: number | null;
},
)
.filter(({ stage }) => stage === "identity-read");
expect(invocations.map(({ args }) => args)).toEqual([
["--version"],
["run", "--help"],
["config", "show", "--json"],
["--version"],
]);
for (const invocation of invocations) {
if (process.platform === "win32") {
expect(
Number.isSafeInteger(invocation.startTimeMs) && (invocation.startTimeMs ?? 0) > 0,
`${JSON.stringify(invocation)}\n${details}`,
).toBe(true);
}
// Windows may reuse an exited probe's PID before the remaining probes finish.
// An unreadable identity for a live PID still cannot prove child cleanup.
const observedStartTimeMs = readWindowsProcessStartTimeSync(invocation.pid, 0);
const alive = isProcessAlive(invocation.pid);
expect(
alive &&
(process.platform !== "win32" ||
observedStartTimeMs === null ||
observedStartTimeMs === invocation.startTimeMs),
alive
? `${JSON.stringify({
invocation,
observedStartTimeMs,
invocations,
})}\n${formatShimResult(result)}`
: undefined,
).toBe(false);
}
expect(readdirSync(state)).toEqual(["tools"]);
});
});
it.each([
{
envKey: "PNPM_CONFIG_MODULES_DIR",
mode: "absolute",
wrapper: TSX_SHIM_WRAPPERS[0],
nodeArgs: [],
inherited: [],
},
{
envKey: "npm_config_modules_dir",
mode: "relative",
wrapper: TSX_SHIM_WRAPPERS[1],
nodeArgs: ["--no-maglev", "--no-concurrent-sparkplug"],
inherited: ["--no-maglev", "--no-concurrent-sparkplug"],
},
{
envKey: "PNPM_CONFIG_MODULES_DIR",
mode: "relative",
wrapper: TSX_SHIM_WRAPPERS[2],
nodeArgs: ["--no-maglev", "--maglev", "--no-concurrent-sparkplug"],
inherited: ["--no-maglev", "--maglev", "--no-concurrent-sparkplug"],
},
{
envKey: "npm_config_modules_dir",
mode: "absolute",
wrapper: TSX_SHIM_WRAPPERS[3],
nodeArgs: ["--title=--no-maglev", "--no-concurrent-sparkplug"],
inherited: ["--no-concurrent-sparkplug"],
},
] as const)(
"boots $wrapper from a $mode $envKey",
async ({ envKey, mode, wrapper, nodeArgs, inherited }) => {
const result = await runShimFixture(
wrapper,
({ checkoutRoot, fixtureRoot }) => {
const modulesDir = path.join(fixtureRoot, "hydrated-modules");
writeTsxFixture(modulesDir, "hydrated");
const configuredDir =
mode === "absolute" ? modulesDir : path.relative(checkoutRoot, modulesDir);
return { [envKey]: configuredDir };
},
nodeArgs,
);
expectShimLoader(result, "hydrated", inherited);
},
);
it("prefers PNPM_CONFIG_MODULES_DIR over npm_config_modules_dir", async () => {
const result = await runShimFixture(TSX_SHIM_WRAPPERS[2], ({ fixtureRoot }) => {
const preferredDir = path.join(fixtureRoot, "preferred-modules");
const fallbackDir = path.join(fixtureRoot, "fallback-modules");
writeTsxFixture(preferredDir, "preferred");
writeTsxFixture(fallbackDir, "lowercase");
return {
PNPM_CONFIG_MODULES_DIR: preferredDir,
npm_config_modules_dir: fallbackDir,
};
});
expectShimLoader(result, "preferred");
});
it("falls back to checkout dependencies without an external modules directory", async () => {
expectShimLoader(await runShimFixture(TSX_SHIM_WRAPPERS[3]), "checkout");
});
it.each(["hydrated", "primary"] as const)(
"requires explicit hydration to bootstrap from %s without local modules",
async (source) => {
const result = await runShimFixture(TSX_SHIM_WRAPPERS[0], ({ checkoutRoot, fixtureRoot }) => {
rmSync(path.join(checkoutRoot, "node_modules"), { recursive: true });
const primaryRoot = path.join(fixtureRoot, "primary");
const modulesDir = path.join(primaryRoot, "node_modules");
writeTsxFixture(modulesDir, source);
if (source === "hydrated") {
return { PNPM_CONFIG_MODULES_DIR: modulesDir };
}
const initialized = spawnSync(
"git",
["init", "--quiet", "--separate-git-dir", path.join(primaryRoot, ".git"), checkoutRoot],
{ encoding: "utf8" },
);
expect(initialized.status, initialized.stderr).toBe(0);
return {};
});
if (source === "hydrated") {
expectShimLoader(result, source);
} else {
expect(result.error, formatShimResult(result)).toBeUndefined();
expect(result.status, formatShimResult(result)).toBe(1);
expect(result.stdout).toBe("");
expect(result.stderr).toContain(
"Run pnpm install --frozen-lockfile in an independently owned checkout.",
);
}
},
);
it("matches Windows drive paths case-insensitively", () => {
expect(
isDirectRunPath(
"C:\\repo\\scripts\\android-app-i18n.ts",
"c:\\repo\\scripts\\android-app-i18n.ts",
"win32",
),
).toBe(true);
});
it.each(["scripts/android-app-i18n.ts", "scripts/generate-bundled-channel-config-metadata.ts"])(
"uses the canonical guard in %s",
(script) => {
const source = readFileSync(script, "utf8");
expect(
source.match(/isDirectRunUrl\(process\.argv\[1\], import\.meta\.url\)/gu),
).toHaveLength(1);
},
);
it.each([
...DIRECT_RUN_SCRIPTS,
"scripts/lib/direct-run.mjs",
"scripts/lib/tsx-cli-shim.mjs",
"test/scripts/direct-run-entrypoints.test.ts",
"test/scripts/install-ps1.test.ts",
"scripts/tsx.mjs",
])("routes %s through Windows CI", (changedPath) => {
expect(detectChangedScope([changedPath]).runWindows).toBe(true);
});
});
it.each([false, true])(
"keeps the fixture until its callback settles (reject=%s)",
async (reject) => {
const release = createDeferred();
const failure = new Error("callback rejected after its final write");
let root = "";
let finalWrite = "";
const completion = Promise.resolve(
withShimFixture(TSX_SHIM_WRAPPERS[0], async ({ fixtureRoot }) => {
root = fixtureRoot;
await release.promise;
const marker = path.join(fixtureRoot, "callback-finished");
writeFileSync(marker, "finished");
finalWrite = readFileSync(marker, "utf8");
if (reject) {
throw failure;
}
return "callback result";
}),
).then(
(value) => ({ value }),
(error: unknown) => ({ error }),
);
try {
expect(existsSync(root), "pending callbacks still own their fixture").toBe(true);
} finally {
release.resolve();
await completion;
}
expect(await completion).toEqual(reject ? { error: failure } : { value: "callback result" });
expect(finalWrite).toBe("finished");
expect(existsSync(root)).toBe(false);
},
);
it.each(["callback", "command"])(
"retains fixture evidence when %s cleanup is unconfirmed",
async (source) => {
// Fault injection is limited to the receipt: real OS cleanup failure is unsafe to force.
const failure = Object.assign(new Error("child cleanup unverified"), {
code: "EPROCESSGROUP_CLEANUP_FAILED",
processTreeState: "indeterminate",
});
const runManaged = managedChild.runManagedCommand;
const spy = vi
.spyOn(managedChild, "runManagedCommand")
.mockImplementationOnce(async (options) => {
await runManaged(options);
throw failure;
});
let root = "";
try {
const error = await withShimFixture(
TSX_SHIM_WRAPPERS[0],
async ({ fixtureRoot, runNode }) => {
root = fixtureRoot;
writeFileSync(path.join(root, "evidence"), "keep");
if (source === "callback") {
throw failure;
}
await runNode(
[
"-e",
'process.stdout.write("retained stdout"); process.stderr.write("retained stderr");',
],
process.env,
root,
);
},
).catch((cause: unknown) => cause);
expect(existsSync(root), "unverified writers still own the fixture").toBe(true);
expect(readFileSync(path.join(root, "evidence"), "utf8")).toBe("keep");
expect(error).toMatchObject({ message: expect.stringContaining(root), cause: failure });
if (source === "command") {
const output = readFileSync(path.join(root, "command-output.log"), "utf8");
expect(output).toContain("retained stdout");
expect(output).toContain("retained stderr");
expect(output).toContain("EPROCESSGROUP_CLEANUP_FAILED");
}
} finally {
spy.mockRestore();
// The real command has joined; only the injected receipt prevents removal.
rmSync(root, { recursive: true, force: true });
}
},
);
it("joins owned descendants and captures timeout output before deleting a rejected fixture", async ({
signal,
}) => {
const evidence = mkdtempSync(path.join(tmpdir(), "openclaw-shim-owned-pids-"));
const pidPaths = ["wrapper", "implementation", "descendant"].map((role) =>
path.join(evidence, `${role}.pid`),
);
const wrapperPidProbe = path.join(evidence, "wrapper-pid.mjs");
writeFileSync(
wrapperPidProbe,
`import fs from "node:fs"; fs.writeFileSync(${JSON.stringify(pidPaths[0])}, String(process.pid));`,
);
const failure = new Error("fixture callback rejected while the command was running");
let root = "";
let fixturePresentAtCommandSettlement: boolean | undefined;
let command: ReturnType<typeof runNodeScript> | undefined;
await runQaGatewayFixture(
async () => {
const error = await withShimFixture(TSX_SHIM_WRAPPERS[0], async (fixture) => {
const { fixtureRoot, implementationPath, wrapperPath, runNode } = fixture;
root = fixtureRoot;
const descendant = `
const fs = require("node:fs");
const timer = setInterval(() => {}, 1000);
process.on("SIGTERM", () => {
fs.writeFileSync(${JSON.stringify(path.join(evidence, "shutdown-root-present"))}, String(fs.existsSync(${JSON.stringify(root)})));
process.stdout.write("shutdown stdout\\n");
process.stderr.write("shutdown stderr\\n");
clearInterval(timer);
});
fs.writeFileSync(${JSON.stringify(pidPaths[2])}, String(process.pid));
process.stdout.write("descendant stdout\\n");
process.stderr.write("descendant stderr\\n");
process.send("ready");
process.disconnect();
`;
writeFileSync(
implementationPath,
`
import fs from "node:fs";
import { spawn } from "node:child_process";
${fixtureReceiptClientSource(receipts.endpoint)}
enum Transformed { Value = "transformed" }
console.log(Transformed.Value);
const child = spawn(process.execPath, ["-e", ${JSON.stringify(descendant)}], { stdio: ["ignore", "inherit", "inherit", "ipc"] });
child.once("message", () => {
fs.writeFileSync(${JSON.stringify(pidPaths[1])}, String(process.pid));
sendReceipt(${JSON.stringify(pidPaths[1])}, "ready");
});
`,
);
const env: NodeJS.ProcessEnv = {
...process.env,
PNPM_CONFIG_MODULES_DIR: path.dirname(
path.dirname(createRequire(import.meta.url).resolve("tsx/package.json")),
),
};
delete env.NODE_OPTIONS;
command = runNode(
["--import", pathToFileURL(wrapperPidProbe).href, wrapperPath],
env,
fixtureRoot,
).then((result) => {
fixturePresentAtCommandSettlement = existsSync(fixtureRoot);
return result;
});
const implementationPid = await withinTest(
implementationReadyBeforeSettlement(pidPaths[1]!, command),
signal,
);
expect(isProcessAlive(implementationPid)).toBe(true);
throw failure;
}).catch((cause: unknown) => cause);
expect(error).toBe(failure);
expect(command).toBeDefined();
const result = await command!;
expect(result.error, formatShimResult(result)).toMatchObject({
code: "ETIMEDOUT",
message: "Managed command timed out after 10000ms",
});
expect(result.status).toBeNull();
expect(fixturePresentAtCommandSettlement, "command teardown still owns its fixture").toBe(
true,
);
expect(result.stdout).toContain("transformed");
expect(result.stdout).toContain("descendant stdout");
expect(result.stderr).toContain("descendant stderr");
for (const pidPath of pidPaths) {
expect(isProcessAlive(Number(readFileSync(pidPath, "utf8"))), pidPath).toBe(false);
}
if (process.platform !== "win32") {
expect(readFileSync(path.join(evidence, "shutdown-root-present"), "utf8")).toBe("true");
expect(result.stdout).toContain("shutdown stdout");
expect(result.stderr).toContain("shutdown stderr");
}
expect(existsSync(root)).toBe(false);
},
async () => {
await command;
},
...pidPaths.toReversed().map((pidPath) => async () => {
// Independent recovery also runs after a failed assertion; never rely on the fixture under test.
if (existsSync(pidPath)) {
const pid = Number(readFileSync(pidPath, "utf8"));
if (isProcessAlive(pid)) {
managedChild.terminateManagedChild(
{ pid, kill: (killSignal) => process.kill(pid, killSignal) },
"SIGKILL",
{ useProcessGroup: false },
);
}
await waitForExtinction(pid, signal);
}
}),
() => {
if (
pidPaths.some(
(pidPath) => existsSync(pidPath) && isProcessAlive(Number(readFileSync(pidPath, "utf8"))),
)
) {
throw new Error(`Owned proof children remain; retained PID evidence ${evidence}`);
}
rmSync(evidence, { recursive: true, force: true });
},
);
});