Reduce CI worker count to mitigate resource contention and increase timeouts to prevent flaky failures. Add Node.js memory limit and crash detection to parallel test runner for better error diagnostics.
92 lines
3.2 KiB
JavaScript
92 lines
3.2 KiB
JavaScript
import { spawn } from "node:child_process";
|
|
import os from "node:os";
|
|
|
|
const pnpm = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
|
|
|
|
const runs = [
|
|
{
|
|
name: "unit",
|
|
args: ["vitest", "run", "--config", "vitest.unit.config.ts"],
|
|
},
|
|
{
|
|
name: "extensions",
|
|
args: ["vitest", "run", "--config", "vitest.extensions.config.ts"],
|
|
},
|
|
{
|
|
name: "gateway",
|
|
args: ["vitest", "run", "--config", "vitest.gateway.config.ts"],
|
|
},
|
|
];
|
|
|
|
const parallelRuns = runs.filter((entry) => entry.name !== "gateway");
|
|
const serialRuns = runs.filter((entry) => entry.name === "gateway");
|
|
|
|
const children = new Set();
|
|
const isCI = process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true";
|
|
const isMacOS = process.platform === "darwin" || process.env.RUNNER_OS === "macOS";
|
|
const overrideWorkers = Number.parseInt(process.env.CLAWDBOT_TEST_WORKERS ?? "", 10);
|
|
const resolvedOverride = Number.isFinite(overrideWorkers) && overrideWorkers > 0 ? overrideWorkers : null;
|
|
const localWorkers = Math.max(4, Math.min(16, os.cpus().length));
|
|
const perRunWorkers = Math.max(1, Math.floor(localWorkers / parallelRuns.length));
|
|
const macCiWorkers = isCI && isMacOS ? 1 : perRunWorkers;
|
|
// Keep worker counts predictable for local runs; trim macOS CI workers to avoid worker crashes/OOM.
|
|
// In CI on linux/windows, prefer Vitest defaults to avoid cross-test interference from lower worker counts.
|
|
const maxWorkers = resolvedOverride ?? (isCI && !isMacOS ? null : macCiWorkers);
|
|
|
|
const WARNING_SUPPRESSION_FLAGS = [
|
|
"--disable-warning=ExperimentalWarning",
|
|
"--disable-warning=DEP0040",
|
|
"--disable-warning=DEP0060",
|
|
"--max-old-space-size=4096",
|
|
];
|
|
|
|
const run = (entry) =>
|
|
new Promise((resolve) => {
|
|
const args = maxWorkers ? [...entry.args, "--maxWorkers", String(maxWorkers)] : entry.args;
|
|
const nodeOptions = process.env.NODE_OPTIONS ?? "";
|
|
const nextNodeOptions = WARNING_SUPPRESSION_FLAGS.reduce(
|
|
(acc, flag) => (acc.includes(flag) ? acc : `${acc} ${flag}`.trim()),
|
|
nodeOptions,
|
|
);
|
|
const child = spawn(pnpm, args, {
|
|
stdio: "inherit",
|
|
env: { ...process.env, VITEST_GROUP: entry.name, NODE_OPTIONS: nextNodeOptions },
|
|
shell: process.platform === "win32",
|
|
});
|
|
children.add(child);
|
|
child.on("exit", (code, signal) => {
|
|
if (signal === 'SIGKILL' || signal === 'SIGABRT' || signal === 'SIGSEGV') {
|
|
console.error(`Worker ${entry.name} crashed with signal ${signal} (possible OOM or resource exhaustion)`);
|
|
} else if (signal) {
|
|
console.warn(`Worker ${entry.name} terminated with signal ${signal}`);
|
|
}
|
|
children.delete(child);
|
|
resolve(code ?? (signal ? 1 : 0));
|
|
});
|
|
});
|
|
|
|
const shutdown = (signal) => {
|
|
for (const child of children) {
|
|
child.kill(signal);
|
|
}
|
|
};
|
|
|
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
|
|
const parallelCodes = await Promise.all(parallelRuns.map(run));
|
|
const failedParallel = parallelCodes.find((code) => code !== 0);
|
|
if (failedParallel !== undefined) {
|
|
process.exit(failedParallel);
|
|
}
|
|
|
|
for (const entry of serialRuns) {
|
|
// eslint-disable-next-line no-await-in-loop
|
|
const code = await run(entry);
|
|
if (code !== 0) {
|
|
process.exit(code);
|
|
}
|
|
}
|
|
|
|
process.exit(0);
|