q
This commit is contained in:
parent
810ad6f5cb
commit
de7c6c32bd
123 changed files with 13201 additions and 6213 deletions
|
|
@ -2,54 +2,70 @@ import { performance } from "node:perf_hooks";
|
|||
import { spawn } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const defaultSuites = [
|
||||
"additional-generation-unit",
|
||||
"additional-generation-coverage-worker",
|
||||
"core",
|
||||
"terrain",
|
||||
"terrain-name",
|
||||
"admin",
|
||||
"patch",
|
||||
"patch-large",
|
||||
"determinism-114514",
|
||||
"determinism-12345",
|
||||
"determinism-54321",
|
||||
"determinism-777",
|
||||
"determinism-999",
|
||||
const cwd = fileURLToPath(new URL(".", import.meta.url));
|
||||
const testFile = fileURLToPath(new URL("./test.js", import.meta.url));
|
||||
const suiteDefinitions = [
|
||||
{ name: "additional-generation-unit", file: "./additional-generation-unit.mjs", timeoutMs: 180_000, canonical: true },
|
||||
{ name: "additional-generation-coverage-worker", file: "./additional-generation-coverage-worker.mjs", timeoutMs: 240_000, canonical: true },
|
||||
{ name: "r10-exact-production-worker", file: "./r10-exact-production-worker.mjs", timeoutMs: 180_000, canonical: true },
|
||||
{ name: "r11-selection-native-production", file: "./r11-selection-native-production.mjs", timeoutMs: 180_000, canonical: true },
|
||||
{ name: "additional-generation-max-worker", file: "./additional-generation-max-worker.mjs", timeoutMs: 240_000, release: true },
|
||||
{ name: "patch-worker-cancel", file: "./patch-worker-cancel.mjs", timeoutMs: 180_000, release: true },
|
||||
{ name: "patch-worker-mirror-sync", file: "./patch-worker-mirror-sync.mjs", timeoutMs: 180_000, release: true },
|
||||
{ name: "r11.4-literal-initial-overscan-worker", file: "./r11.4-literal-initial-overscan-worker.mjs", timeoutMs: 300_000, release: true },
|
||||
{ name: "r11.4-transport-demography-overscan", file: "./r11.4-transport-demography-overscan.mjs", timeoutMs: 300_000, release: true },
|
||||
{ name: "r11.5-visible-quality-finalizer", file: "./r11.5-visible-quality-finalizer.mjs", timeoutMs: 600_000, release: true },
|
||||
{ name: "r11.6-large-bestof-quality", file: "./r11.6-large-bestof-quality.mjs", timeoutMs: 600_000, release: true },
|
||||
{ name: "r11.7-terrain-routed-transport-density", file: "./r11.7-terrain-routed-transport-density.mjs", timeoutMs: 300_000, release: true },
|
||||
{ name: "r11.8-terrain-topology-tooltip", file: "./r11.8-terrain-topology-tooltip.mjs", timeoutMs: 600_000, release: true },
|
||||
{ name: "additional-generation-browser", file: "./run-additional-generation-browser.mjs", timeoutMs: 600_000, browser: true },
|
||||
{ name: "core", timeoutMs: 360_000, canonical: true },
|
||||
{ name: "terrain", timeoutMs: 600_000, canonical: true },
|
||||
{ name: "terrain-name", timeoutMs: 240_000, canonical: true },
|
||||
{ name: "admin", timeoutMs: 300_000, canonical: true },
|
||||
{ name: "patch", timeoutMs: 300_000, canonical: true },
|
||||
{ name: "patch-large", timeoutMs: 600_000, canonical: true },
|
||||
{ name: "determinism-114514", timeoutMs: 240_000, canonical: true },
|
||||
{ name: "determinism-12345", timeoutMs: 240_000, canonical: true },
|
||||
{ name: "determinism-54321", timeoutMs: 240_000, canonical: true },
|
||||
{ name: "determinism-777", timeoutMs: 240_000, canonical: true },
|
||||
{ name: "determinism", timeoutMs: 240_000 },
|
||||
{ name: "all", timeoutMs: 900_000 },
|
||||
];
|
||||
const suiteRegistry = new Map(suiteDefinitions.map((definition) => [definition.name, definition]));
|
||||
const suiteGroups = new Map([
|
||||
["default", suiteDefinitions.filter((definition) => definition.canonical).map((definition) => definition.name)],
|
||||
["release", suiteDefinitions.filter((definition) => definition.canonical || definition.release).map((definition) => definition.name)],
|
||||
["browser", suiteDefinitions.filter((definition) => definition.browser).map((definition) => definition.name)],
|
||||
]);
|
||||
const defaultTimeoutMs = 240_000;
|
||||
|
||||
function resolveSuite(name) {
|
||||
const registered = suiteRegistry.get(name);
|
||||
if (registered) return registered;
|
||||
if (/^determinism-(?:0|[1-9]\d*)$/.test(name)) return { name, timeoutMs: defaultTimeoutMs };
|
||||
return null;
|
||||
}
|
||||
|
||||
const requestedSuites = String(process.env.TEST_SUITES || "").split(",").map((value) => value.trim()).filter(Boolean);
|
||||
const suites = requestedSuites.length ? requestedSuites : defaultSuites;
|
||||
const requestedGroup = String(process.env.TEST_GROUP || "default").trim();
|
||||
if (!suiteGroups.has(requestedGroup)) throw new Error(`Unknown test group: ${requestedGroup}`);
|
||||
const suites = requestedSuites.length ? requestedSuites : suiteGroups.get(requestedGroup);
|
||||
const unknownSuites = suites.filter((suite) => !resolveSuite(suite));
|
||||
if (unknownSuites.length) throw new Error(`Unknown test suite${unknownSuites.length === 1 ? "" : "s"}: ${unknownSuites.join(", ")}`);
|
||||
const concurrency = Math.max(1, Math.min(2, Number(process.env.TEST_CONCURRENCY) || 1));
|
||||
// Full-map shards can briefly peak at several hundred MB. CI may opt into a
|
||||
// handoff delay when its runtime needs extra time to reclaim a completed child.
|
||||
const suiteCooldownMs = Math.max(0, Number(process.env.TEST_SUITE_COOLDOWN_MS ?? 0));
|
||||
const suiteTimeoutMs = {
|
||||
"additional-generation-unit": 180_000,
|
||||
"additional-generation-coverage-worker": 120_000,
|
||||
core: 360_000,
|
||||
terrain: 600_000,
|
||||
"terrain-name": 240_000,
|
||||
admin: 300_000,
|
||||
patch: 300_000,
|
||||
"patch-large": 600_000,
|
||||
};
|
||||
const defaultTimeoutMs = 240_000;
|
||||
const maxOutputBytes = 32 * 1024 * 1024;
|
||||
const cwd = fileURLToPath(new URL(".", import.meta.url));
|
||||
const testFile = fileURLToPath(new URL("./test.js", import.meta.url));
|
||||
const additionalUnitFile = fileURLToPath(new URL("./additional-generation-unit.mjs", import.meta.url));
|
||||
const additionalCoverageWorkerFile = fileURLToPath(new URL("./additional-generation-coverage-worker.mjs", import.meta.url));
|
||||
|
||||
function runSuite(suite) {
|
||||
return new Promise((resolve) => {
|
||||
const timeoutMs = suiteTimeoutMs[suite] || defaultTimeoutMs;
|
||||
const descriptor = resolveSuite(suite);
|
||||
const timeoutMs = descriptor.timeoutMs || defaultTimeoutMs;
|
||||
const started = performance.now();
|
||||
console.error(`[test-all] start ${suite}`);
|
||||
const standaloneFile = suite === "additional-generation-unit"
|
||||
? additionalUnitFile
|
||||
: suite === "additional-generation-coverage-worker"
|
||||
? additionalCoverageWorkerFile
|
||||
: null;
|
||||
const standaloneFile = descriptor.file ? fileURLToPath(new URL(descriptor.file, import.meta.url)) : null;
|
||||
const commandFile = standaloneFile || testFile;
|
||||
const commandArgs = standaloneFile ? [commandFile] : [commandFile, `--suite=${suite}`];
|
||||
const child = spawn(process.execPath, commandArgs, {
|
||||
|
|
@ -57,25 +73,33 @@ function runSuite(suite) {
|
|||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
const stdoutChunks = [];
|
||||
const stderrChunks = [];
|
||||
let outputBytes = 0;
|
||||
let outputOverflow = false;
|
||||
let timedOut = false;
|
||||
let forceKillTimer = null;
|
||||
let resolved = false;
|
||||
|
||||
const append = (current, chunk) => {
|
||||
if (outputOverflow) return current;
|
||||
const next = current + chunk.toString("utf8");
|
||||
if (Buffer.byteLength(next, "utf8") > maxOutputBytes) {
|
||||
const finish = (payload) => {
|
||||
if (resolved) return;
|
||||
resolved = true;
|
||||
resolve(payload);
|
||||
};
|
||||
const append = (target, chunk) => {
|
||||
if (outputOverflow) return;
|
||||
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
||||
if (outputBytes + buffer.byteLength > maxOutputBytes) {
|
||||
outputOverflow = true;
|
||||
child.kill("SIGTERM");
|
||||
return current;
|
||||
return;
|
||||
}
|
||||
return next;
|
||||
outputBytes += buffer.byteLength;
|
||||
target.push(buffer);
|
||||
};
|
||||
|
||||
child.stdout.on("data", (chunk) => { stdout = append(stdout, chunk); });
|
||||
child.stderr.on("data", (chunk) => { stderr = append(stderr, chunk); });
|
||||
child.stdout.on("data", (chunk) => append(stdoutChunks, chunk));
|
||||
child.stderr.on("data", (chunk) => append(stderrChunks, chunk));
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
timedOut = true;
|
||||
|
|
@ -90,7 +114,7 @@ function runSuite(suite) {
|
|||
if (forceKillTimer) clearTimeout(forceKillTimer);
|
||||
const seconds = Math.round((performance.now() - started) / 10) / 100;
|
||||
console.error(`[test-all] error ${suite}: ${error.message}`);
|
||||
resolve({
|
||||
finish({
|
||||
suite,
|
||||
seconds,
|
||||
fullMapGenerations: null,
|
||||
|
|
@ -107,22 +131,31 @@ function runSuite(suite) {
|
|||
clearTimeout(timeout);
|
||||
if (forceKillTimer) clearTimeout(forceKillTimer);
|
||||
const seconds = Math.round((performance.now() - started) / 10) / 100;
|
||||
const stdout = Buffer.concat(stdoutChunks).toString("utf8");
|
||||
const stderr = Buffer.concat(stderrChunks).toString("utf8");
|
||||
const output = `${stdout}\n${stderr}`;
|
||||
const ng = (output.match(/^NG:/gm) || []).length;
|
||||
const info = output.match(/INFO: suite=([^;]+); fullMapGenerations=(\d+); elapsedMs=(\d+)/);
|
||||
const infrastructureError = outputOverflow
|
||||
? `Output exceeded ${maxOutputBytes} bytes`
|
||||
: null;
|
||||
const testFailure = status !== 0 && !signal && !timedOut && !outputOverflow && !infrastructureError;
|
||||
const failedAssertions = ng > 0 ? ng : testFailure ? 1 : 0;
|
||||
console.error(
|
||||
`[test-all] end ${suite}: status=${status} signal=${signal || "none"} ` +
|
||||
`failures=${ng} seconds=${seconds}`,
|
||||
`failures=${failedAssertions} seconds=${seconds}`,
|
||||
);
|
||||
resolve({
|
||||
if (testFailure || signal || timedOut || outputOverflow || infrastructureError) {
|
||||
const tail = output.trim().slice(-16_000);
|
||||
if (tail) console.error(`[test-all] output ${suite}:\n${tail}`);
|
||||
}
|
||||
finish({
|
||||
suite,
|
||||
seconds,
|
||||
fullMapGenerations: info ? Number(info[2]) : null,
|
||||
elapsedMsReported: info ? Number(info[3]) : null,
|
||||
failedAssertions: ng,
|
||||
failedAssertions,
|
||||
testFailure,
|
||||
status,
|
||||
signal: signal || null,
|
||||
timedOut,
|
||||
|
|
@ -157,7 +190,7 @@ const failures = completed.reduce(
|
|||
0,
|
||||
);
|
||||
const infrastructureFailure = completed.some(
|
||||
(row) => row.status !== 0 || row.signal || row.timedOut || row.outputOverflow || row.infrastructureError,
|
||||
(row) => row.signal || row.timedOut || row.outputOverflow || row.infrastructureError,
|
||||
);
|
||||
const withinSuiteBudgets = completed.every((row) => !row.timedOut);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue