openclaw/scripts/lib/vitest-batch-runner.mts
Peter Steinberger 9137cfcc5f
perf(ci): run qualified unit tests with native Bun (#159988)
* perf(ci): run qualified unit tests with native Bun

* test(ci): keep a V8-only cache warm fixture

* fix(ci): include shared timeout in PR wrapper inventory

* perf(test): skip Bun bytecode cache during test teardown

* test: handle worker fixture retirement notifications

Handle rejected observer returns without delaying retirement, matching the
worker pool contract, and avoid shadowing the lane fixture input helper.

* perf(ci): qualify and tune Bun test workers

Complete SQLite admission before Bun Vitest threads start and retain each
worker generation's canonical cleanup decision in lifecycle fixtures.
Keep proxy-agent fixture values out of Bun's native fetch cache and compare
large socket payloads with full byte equality.

Renew the 292-file native qualification from paired 2,518-case proof.
Apply the existing UI JIT and allocator settings to ordinary unit-fast
Bun/Vitest selections and their cache warmer, preserving Node and native
Bun settings and dual-runtime release coverage.

Proof: 208 routing/warming cases; 61 lifecycle cases on each runtime;
66 ordered proxy/fetch cases plus the canonical 11-case proxy owner on each
runtime; independent P0-P2 review. The changed-file gate was interrupted
by the local disk reserve guard after core typing and 14 core-test type
shards passed. Tuned full-cohort performance remains to be measured;
the completed untuned comparison was 4.60% slower than Node.

* perf(test): expand native Bun coverage and reduce artifact IO

* perf(test): yield between artifact verification batches

* test: qualify native Bun cases after runtime integration

* test: refresh native Bun qualification after main integration
2026-10-01 17:28:11 -07:00

120 lines
4.2 KiB
TypeScript

// Runs grouped batches through the repository's installed Vitest entrypoint.
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { assertTestHomeSelection, type TestHomeSelection } from "../../test/test-home-policy.mts";
import { installVitestProcessGroupCleanup } from "../vitest-process-group.mts";
import { resolveVitestCliEntry } from "./vitest-build-prerequisites.mts";
import { resolveExplicitVitestMode } from "./vitest-cli-mode.mts";
import { resolveVitestHomeSelection } from "./vitest-home-selection.mts";
import { resolveVitestNodeArgs } from "./vitest-process-env.mts";
import { exitVitestBySignal, spawnOwnedVitestProcess } from "./vitest-process.mts";
import type { VitestReportOutcome } from "./vitest-report-owner.mts";
import { resolveVitestTestCommand } from "./vitest-test-runtime.mts";
import { createVitestWorkerRun } from "./vitest-worker-run.mts";
export type VitestBatchRunParams = {
args: string[];
config: string;
env?: NodeJS.ProcessEnv;
// Owner-generated report configs retain their validated original selection.
homeMode?: TestHomeSelection;
targets: string[];
onComplete?: (outcome: VitestReportOutcome) => void;
};
const scriptFile = fileURLToPath(import.meta.url);
const scriptDir = path.dirname(scriptFile);
const repoRoot = path.resolve(scriptDir, "../..");
/**
* Runs one Vitest batch and forwards process-group cleanup signals.
*/
export async function runVitestBatch(params: VitestBatchRunParams): Promise<number> {
const env = params.env ?? process.env;
const homeMode =
params.homeMode ??
resolveVitestHomeSelection(["--config", params.config, ...params.args, ...params.targets], {
cwd: repoRoot,
env,
});
assertTestHomeSelection(env, homeMode);
const testCommand = resolveVitestTestCommand(
[
...resolveVitestNodeArgs(env),
resolveVitestCliEntry({ env }),
"run",
"--config",
params.config,
...params.args,
...params.targets,
],
env,
);
const workers =
resolveExplicitVitestMode(["run", ...params.args]) === "watch"
? undefined
: createVitestWorkerRun(env);
if (workers) {
const cliIndex = testCommand.args.findIndex((arg) => path.basename(arg) === "vitest.mjs");
testCommand.args.splice(
cliIndex,
0,
path.join(repoRoot, "scripts/lib/vitest-worker-bootstrap.mts"),
workers.descriptor.directory,
);
}
let interrupted: NodeJS.Signals | undefined;
const onSignal = (signal: NodeJS.Signals) => {
interrupted ??= signal;
};
// Artifact verification can outlive the child's signal handlers.
process.on("SIGINT", onSignal);
process.on("SIGTERM", onSignal);
let outcome: VitestReportOutcome;
try {
// Match project runs: native workers borrow this invocation's prepared source,
// rather than compiling the application independently inside every worker.
const { child, completion } = spawnOwnedVitestProcess({
homeMode,
...testCommand,
options: {
cwd: repoRoot,
env: testCommand.envOverrides ? { ...env, ...testCommand.envOverrides } : env,
stdio: workers ? ["inherit", "inherit", "inherit", "ipc"] : "inherit",
},
});
const cleanup = installVitestProcessGroupCleanup({
child,
forceSignal: "SIGKILL",
forceSignalDelayMs: 100,
});
try {
const { code, signal } = await (workers ? workers.borrow(child, completion) : completion);
interrupted ??= cleanup.getForwardedSignal() ?? signal ?? undefined;
outcome = { code: code ?? 1, signal: interrupted ?? null };
} finally {
cleanup.teardown();
}
} finally {
try {
await workers?.dispose();
} finally {
process.off("SIGINT", onSignal);
process.off("SIGTERM", onSignal);
if (interrupted && !params.onComplete) {
await exitVitestBySignal(interrupted);
}
}
}
outcome.signal = interrupted ?? outcome.signal;
params.onComplete?.(outcome);
return outcome.code;
}
/**
* Checks whether a module URL is the current direct script entrypoint.
*/
export function isDirectScriptRun(metaUrl: string): boolean {
const entryHref = process.argv[1] ? pathToFileURL(path.resolve(process.argv[1])).href : "";
return metaUrl === entryHref;
}