mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
perf(release-green): run the 4 slow suites concurrently — pre-flight wall time ~sum -> ~slowest (#6319)
The pre-flight ran unit (~25-35min) + vitest (~3-8min) + integration (~3-10min) + pack-artifact (~15min) SEQUENTIALLY via execFileSync — ~1h wall in the v3.8.45 release (the dominant cost of Phase 0). They are independent processes with per-process DATA_DIR isolation, so they now run in a single Promise.all over an async runAsync() twin of run(): total drops to ~the slowest single suite (~30min). Each still saves its per-gate log (_artifacts/release-green/) and reports HARD independently. --quick still skips them; --with-build folds pack-artifact into the same wave (or runs it alone under --quick). Guard: tests/unit/validate-release-green.test.ts (13/13).
This commit is contained in:
committed by
GitHub
parent
ab8b41bdaa
commit
5d07bdd76f
@@ -45,7 +45,8 @@
|
||||
// Per-gate output is saved to _artifacts/release-green/<gate>.log (gitignored) —
|
||||
// diagnose a red from the file instead of re-running the gate.
|
||||
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { execFile, execFileSync } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
@@ -189,7 +190,29 @@ function run(cmd, cmdArgs, opts = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
// Async twin of run() — same {code, out} contract, so the slow suites (unit /
|
||||
// vitest / integration / pack-artifact) can run CONCURRENTLY instead of in
|
||||
// series. Sequentially they dominate the pre-flight wall time (~2h in the
|
||||
// v3.8.45 run); they are independent processes with per-process DATA_DIR
|
||||
// isolation, so overlapping them cuts the pre-flight to ~the slowest single one.
|
||||
async function runAsync(cmd, cmdArgs, opts = {}) {
|
||||
try {
|
||||
const { stdout, stderr } = await execFileAsync(cmd, cmdArgs, {
|
||||
cwd: ROOT,
|
||||
encoding: "utf8",
|
||||
maxBuffer: 256 * 1024 * 1024,
|
||||
env: buildGateEnv(opts.env),
|
||||
...(opts.timeout ? { timeout: opts.timeout } : {}),
|
||||
});
|
||||
return { code: 0, out: `${stdout || ""}${stderr || ""}` };
|
||||
} catch (err) {
|
||||
return classifyRunError(err, opts.timeout);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = new Set(process.argv.slice(2));
|
||||
const JSON_OUT = args.has("--json");
|
||||
const WITH_BUILD = args.has("--with-build");
|
||||
@@ -386,35 +409,69 @@ function main() {
|
||||
// with 15 such reds). They run SILENTLY for many minutes; the announce line above + these
|
||||
// hard ceilings keep a long-but-healthy run from being mistaken for a hang (the ceiling also
|
||||
// converts a genuine DB-handle hang into a visible failure instead of an infinite block).
|
||||
hardCmd(
|
||||
"unit",
|
||||
"Unit tests (full suite, CI concurrency — runs ~20-35min silently)",
|
||||
npmCmd,
|
||||
["run", "test:unit:ci"],
|
||||
{ timeout: 45 * 60 * 1000 }
|
||||
// The slow suites are INDEPENDENT processes (each self-isolates DATA_DIR) with
|
||||
// no shared state, so they run CONCURRENTLY — the pre-flight wall time becomes
|
||||
// ~the slowest single suite instead of their sum (unit ~25-35min + vitest
|
||||
// ~3-8min + integration ~3-10min + pack-artifact ~15min was ~1h serial in the
|
||||
// v3.8.45 run). pack-artifact (--with-build) joins the same wave. Integration
|
||||
// runs ONLY on the release PR full CI, so a regression here is invisible until
|
||||
// release — that is why it is a HARD pre-flight gate.
|
||||
const slow = [
|
||||
{
|
||||
id: "unit",
|
||||
label: "Unit tests (full suite, CI concurrency — runs ~20-35min silently)",
|
||||
args: ["run", "test:unit:ci"],
|
||||
timeout: 45 * 60 * 1000,
|
||||
},
|
||||
{
|
||||
id: "vitest",
|
||||
label: "Vitest (MCP / autoCombo / cache — ~3-8min)",
|
||||
args: ["run", "test:vitest"],
|
||||
timeout: 15 * 60 * 1000,
|
||||
},
|
||||
{
|
||||
id: "integration",
|
||||
label: "Integration tests (~3-10min)",
|
||||
args: ["run", "test:integration"],
|
||||
timeout: 20 * 60 * 1000,
|
||||
},
|
||||
];
|
||||
if (WITH_BUILD) {
|
||||
slow.push({
|
||||
id: "pack-artifact",
|
||||
label: "Package artifact (npm pack policy)",
|
||||
args: ["run", "check:pack-artifact"],
|
||||
timeout: 20 * 60 * 1000,
|
||||
});
|
||||
}
|
||||
slow.forEach((g) => announce(`${g.label} [parallel]`));
|
||||
const slowResults = await Promise.all(
|
||||
slow.map((g) => runAsync(npmCmd, g.args, { timeout: g.timeout }))
|
||||
);
|
||||
hardCmd(
|
||||
"vitest",
|
||||
"Vitest (MCP / autoCombo / cache — ~3-8min)",
|
||||
npmCmd,
|
||||
["run", "test:vitest"],
|
||||
{ timeout: 15 * 60 * 1000 }
|
||||
);
|
||||
// Integration tests run ONLY on the release PR full CI (PR→main), so an assertion
|
||||
// regression here (e.g. a contributor flipping a Codex fingerprint key order) is
|
||||
// invisible until release — run them in the pre-flight as a HARD gate.
|
||||
hardCmd("integration", "Integration tests (~3-10min)", npmCmd, ["run", "test:integration"], {
|
||||
slow.forEach((g, i) => {
|
||||
const { code, out } = slowResults[i];
|
||||
saveGateLog(g.id, out);
|
||||
record({
|
||||
id: g.id,
|
||||
label: g.label,
|
||||
kind: "hard",
|
||||
ok: code === 0,
|
||||
detail: code === 0 ? "pass" : firstFailureLine(out),
|
||||
});
|
||||
});
|
||||
} else if (WITH_BUILD) {
|
||||
// --with-build without the suites (--quick): still verify the package artifact.
|
||||
const { code, out } = await runAsync(npmCmd, ["run", "check:pack-artifact"], {
|
||||
timeout: 20 * 60 * 1000,
|
||||
});
|
||||
}
|
||||
if (WITH_BUILD) {
|
||||
hardCmd(
|
||||
"pack-artifact",
|
||||
"Package artifact (npm pack policy)",
|
||||
npmCmd,
|
||||
["run", "check:pack-artifact"],
|
||||
{ timeout: 20 * 60 * 1000 }
|
||||
);
|
||||
saveGateLog("pack-artifact", out);
|
||||
record({
|
||||
id: "pack-artifact",
|
||||
label: "Package artifact (npm pack policy)",
|
||||
kind: "hard",
|
||||
ok: code === 0,
|
||||
detail: code === 0 ? "pass" : firstFailureLine(out),
|
||||
});
|
||||
}
|
||||
|
||||
const { releaseGreen, hardFailures, drift } = computeVerdict(results);
|
||||
|
||||
@@ -158,3 +158,22 @@ test("pre-flight --hermetic scrubs the live-test trigger vars (2026-07-05 false-
|
||||
assert.match(src, /saveGateLog/, "per-gate output must be persisted");
|
||||
assert.match(src, /_artifacts[/", ]+release-green/, "logs must land in _artifacts/release-green");
|
||||
});
|
||||
|
||||
test("pre-flight runs the slow suites CONCURRENTLY (v3.8.45 perf — was ~1h serial)", async () => {
|
||||
const fs = await import("node:fs");
|
||||
const src = fs.readFileSync(
|
||||
new URL("../../scripts/quality/validate-release-green.mjs", import.meta.url),
|
||||
"utf8"
|
||||
);
|
||||
// main() must be async and the slow suites (unit/vitest/integration/pack-artifact)
|
||||
// must run via a single Promise.all over runAsync — not four sequential hardCmd calls.
|
||||
assert.match(src, /async function main\(\)/, "main must be async to await the parallel wave");
|
||||
assert.match(src, /const execFileAsync = promisify\(execFile\)/, "async runner must exist");
|
||||
assert.match(src, /await Promise\.all\(\s*slow\.map\(/, "slow suites must run concurrently");
|
||||
// The four slow-gate ids must all be present in the parallel wave.
|
||||
for (const id of ["unit", "vitest", "integration", "pack-artifact"]) {
|
||||
assert.ok(src.includes(`id: "${id}"`), `slow gate ${id} must be in the parallel wave`);
|
||||
}
|
||||
// Each still saves its per-gate log for red diagnosis without a re-run.
|
||||
assert.match(src, /slow\.forEach\([\s\S]*?saveGateLog\(g\.id/, "each slow gate persists its log");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user