* test: strengthen integration oracles and verification * test: bound lifecycle gates and strengthen workflow checks * test: verify complete paged and extracted results * test: preserve bytecode and cancellation evidence * test: strengthen retained evidence and fixture cleanup * test: require cross-projection bridge identities * test: retain fixture owners through cleanup failures * test: verify graph examples and project selectors * test: tighten provider and lifecycle regressions * test: consolidate hex coverage and bound cutter cleanup * test: bound subprocess output waits and strengthen graph checks * test: check mcp recovery and preserve cleanup owners * test: verify structured mcp argument failures * test: preserve artifact cleanup and gdb limit evidence * test: preserve evidence identities and bound provider gates * test: retain browser and comparison cleanup owners * test: bound startup and strengthen fixture oracles * test: bound lifecycle gates and verify recovered evidence * test: verify public outcomes and lifecycle ownership * test: assert precise evidence and release lifecycle gates * test: verify selected facts and bound cleanup waits * test: check retained evidence and decoded records * test: verify final reports and retain cleanup ownership * test: bound reviewed startup gates and retain cleanup * test: verify PE errors and preserve MCP cleanup * test: preserve MCP fixture cleanup ownership * test: release provider gates before late startup
137 lines
4.3 KiB
JavaScript
137 lines
4.3 KiB
JavaScript
import { spawn } from "node:child_process";
|
|
import { stat } from "node:fs/promises";
|
|
import { resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
import {
|
|
developmentTestPlan,
|
|
parseDevelopmentTestRequest,
|
|
} from "./lib/development-tests.mjs";
|
|
|
|
let child;
|
|
let forceTimer;
|
|
let shutdownSignal;
|
|
let childOwnsTree = false;
|
|
const signalExitCodes = { SIGINT: 130, SIGTERM: 143 };
|
|
for (const signal of Object.keys(signalExitCodes))
|
|
process.on(signal, () => {
|
|
if (shutdownSignal !== undefined) return;
|
|
shutdownSignal = signal;
|
|
if (child !== undefined && child.exitCode === null) {
|
|
child.kill(signal);
|
|
if (!childOwnsTree) {
|
|
forceTimer = setTimeout(() => child?.kill("SIGKILL"), 1_000);
|
|
forceTimer.unref?.();
|
|
}
|
|
}
|
|
});
|
|
|
|
try {
|
|
const [mode, ...arguments_] = process.argv.slice(2);
|
|
const request = parseDevelopmentTestRequest(mode, arguments_);
|
|
for (const path of request.paths) {
|
|
if (!(await stat(path)).isFile())
|
|
throw new Error(`Test path is not a file: ${path}`);
|
|
}
|
|
let baseCommit;
|
|
if (request.mode === "changed") {
|
|
const base = await run("git", ["merge-base", "HEAD", request.base], true);
|
|
if (base.code !== 0)
|
|
throw new Error(
|
|
`Cannot resolve test base ${request.base}. Fetch origin/main or pass --base REVISION.\n${base.output.trim()}`,
|
|
);
|
|
baseCommit = base.output.trim();
|
|
}
|
|
const plan = developmentTestPlan(request, baseCommit);
|
|
if (request.dryRun) {
|
|
console.log(JSON.stringify({ ...request, baseCommit, ...plan }, null, 2));
|
|
} else {
|
|
console.error(
|
|
request.mode === "focused"
|
|
? `Focused tests: ${request.paths.join(", ")}`
|
|
: `Source-test feedback${baseCommit ? ` from merge base ${baseCommit}` : ""}; compiled boundaries and real providers need explicit verification.`,
|
|
);
|
|
if (plan.needsBuild || plan.artifactTasks.length > 0) {
|
|
const dependencies = await run(process.execPath, [
|
|
"scripts/check-dependency-install.mjs",
|
|
]);
|
|
if (dependencies.code !== 0)
|
|
throw new Error(
|
|
"Test prerequisites require the locked dependency installation",
|
|
);
|
|
const prepared = await run(
|
|
process.execPath,
|
|
[
|
|
"scripts/run-exclusive.mjs",
|
|
"artifacts",
|
|
process.execPath,
|
|
resolve("scripts/run-turbo.mjs"),
|
|
"run",
|
|
...new Set([
|
|
...(plan.needsBuild ? ["artifacts:skills"] : []),
|
|
...plan.artifactTasks,
|
|
]),
|
|
],
|
|
false,
|
|
"existing",
|
|
);
|
|
if (prepared.code !== 0) process.exitCode = prepared.code;
|
|
}
|
|
if (!process.exitCode && !shutdownSignal) {
|
|
const tested = await run(
|
|
process.execPath,
|
|
[
|
|
resolve(
|
|
request.paths.length > 0
|
|
? "scripts/run-exact-tests.mjs"
|
|
: "node_modules/vitest/vitest.mjs",
|
|
),
|
|
...plan.vitestArguments,
|
|
],
|
|
false,
|
|
"test",
|
|
);
|
|
process.exitCode = tested.code;
|
|
}
|
|
}
|
|
} catch (cause) {
|
|
console.error(cause instanceof Error ? cause.message : String(cause));
|
|
process.exitCode = 1;
|
|
}
|
|
if (shutdownSignal) process.exitCode = signalExitCodes[shutdownSignal];
|
|
|
|
function run(executable, arguments_, capture = false, supervision) {
|
|
if (shutdownSignal)
|
|
return Promise.resolve({
|
|
code: signalExitCodes[shutdownSignal],
|
|
output: "",
|
|
});
|
|
// Source-only tests do not require the built Windows Job Object artifact.
|
|
const wrap = supervision === "test" && process.platform !== "win32";
|
|
childOwnsTree = supervision === "existing" || wrap;
|
|
child = spawn(
|
|
wrap ? process.execPath : executable,
|
|
wrap
|
|
? [
|
|
fileURLToPath(new URL("./run-exclusive.mjs", import.meta.url)),
|
|
"--unlocked",
|
|
executable,
|
|
...arguments_,
|
|
]
|
|
: arguments_,
|
|
{ stdio: capture ? ["ignore", "pipe", "pipe"] : "inherit" },
|
|
);
|
|
let output = "";
|
|
if (capture) {
|
|
child.stdout.on("data", (data) => (output += data.toString()));
|
|
child.stderr.on("data", (data) => (output += data.toString()));
|
|
}
|
|
return new Promise((resolveResult, reject) => {
|
|
child.once("error", reject);
|
|
child.once("close", (code) => {
|
|
clearTimeout(forceTimer);
|
|
resolveResult({ code: code ?? 1, output });
|
|
});
|
|
});
|
|
}
|