Files
wehub-resource-sync e7738de6d2
CI / Deep Native Runtime Cases (1/6) (push) Has been skipped
CI / Native Preflight (push) Failing after 1s
CI / Native Runtime Cases (1/2) (push) Failing after 0s
CI / Native Runtime Cases (2/2) (push) Failing after 1s
CI / Native Metadata Reports (push) Failing after 0s
CI / Native Direct Backend Artifacts (push) Failing after 0s
CI / Native Sanitizer Smoke (push) Failing after 1s
CI / Command Contract Snapshots (push) Failing after 1s
CI / Deep Conformance Suite (push) Has been skipped
CI / Graph Build Perf (push) Failing after 1s
CI / Deep Native Preflight (push) Has been skipped
CI / Deep Native Runtime Cases (2/6) (push) Has been skipped
CI / Deep Native Runtime Cases (3/6) (push) Has been skipped
CI / Conformance Suite (push) Failing after 1s
CI / Workspace Checks (push) Failing after 0s
CI / Deep Native Runtime Cases (5/6) (push) Has been skipped
CI / Deep Native Runtime Cases (6/6) (push) Has been skipped
CI / Deep Native Runtime Cases (4/6) (push) Has been skipped
CI / Deep Graph Build Perf (push) Has been skipped
chore: import upstream snapshot with attribution
2026-07-13 12:29:30 +08:00

273 lines
9.8 KiB
TypeScript

#!/usr/bin/env -S node --experimental-strip-types --disable-warning=ExperimentalWarning
import { spawn } from "node:child_process";
import { mkdirSync, writeFileSync } from "node:fs";
import { join } from "node:path";
const nodeArgs = ["--experimental-strip-types", "--disable-warning=ExperimentalWarning"];
const reportDir = ".zero/validation";
type Phase = {
name: string;
command: string;
args: string[];
setup?: boolean;
};
type Suite = {
setup: Phase[];
phases: Phase[];
defaultJobs?: number;
};
type PhaseResult = {
name: string;
command: string;
ok: boolean;
code: number | null;
signal: NodeJS.Signals | null;
durationMs: number;
stdout: string;
stderr: string;
};
const suites: Record<string, Suite> = {
conformance: {
setup: [
{ name: "native-build", command: "make", args: ["-C", "native/zero-c"], setup: true },
],
phases: [
{ name: "graph-input-policy", command: process.execPath, args: [...nodeArgs, "scripts/graph-input-policy.mts"] },
{ name: "native-contracts", command: process.execPath, args: [...nodeArgs, "scripts/native-contracts.mts"] },
{ name: "provenance-guardrails", command: process.execPath, args: [...nodeArgs, "scripts/provenance-guardrails.mts"] },
{ name: "type-core-smoke", command: process.execPath, args: [...nodeArgs, "scripts/type-core-smoke.mts"] },
{ name: "mir-verifier-smoke", command: process.execPath, args: [...nodeArgs, "scripts/mir-verifier-smoke.mts"] },
{ name: "program-graph-smoke", command: process.execPath, args: [...nodeArgs, "scripts/program-graph-smoke.mts"] },
{ name: "program-graph-parity", command: process.execPath, args: [...nodeArgs, "scripts/program-graph-parity.mts"] },
{ name: "canonical-text-smoke", command: process.execPath, args: [...nodeArgs, "scripts/canonical-text-smoke.mts"] },
{ name: "examples-gate", command: process.execPath, args: [...nodeArgs, "scripts/examples-gate.mts"] },
{ name: "conformance-run", command: process.execPath, args: ["conformance/run.mjs"] },
],
},
"command-contracts": {
setup: [
{ name: "native-build", command: "make", args: ["-C", "native/zero-c"], setup: true },
],
phases: [
{ name: "graph-input-policy", command: process.execPath, args: [...nodeArgs, "scripts/graph-input-policy.mts"] },
{ name: "snapshot-command-contracts", command: process.execPath, args: [...nodeArgs, "scripts/snapshot-command-contracts.mts"] },
],
},
};
function usage() {
const suiteNames = Object.keys(suites).join("|");
console.log(`Run validation phases with aggregate failure reporting.
Usage:
node --experimental-strip-types --disable-warning=ExperimentalWarning scripts/validation-suite.mts <${suiteNames}> [options]
Options:
--list List phases and exit.
--phases <a,b> Run only the named comma-separated phases after setup.
--shard <index/count> Run only this 1-based phase shard after setup.
--jobs <count> Run selected phases concurrently. Defaults to 1.
--fail-fast Stop after the first failing phase.
Environment:
ZERO_VALIDATION_PHASES Default phase allowlist, for example a,b,c.
ZERO_VALIDATION_SHARD Default shard, for example 1/4.
ZERO_VALIDATION_JOBS Default jobs count.
ZERO_VALIDATION_FAIL_FAST=1 Stop after the first failing phase.`);
}
const rawArgs = process.argv.slice(2);
if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
usage();
process.exit(0);
}
const suiteName = rawArgs.find((arg) => !arg.startsWith("--"));
if (!suiteName || !suites[suiteName]) {
usage();
process.exit(2);
}
function optionValue(name: string) {
const index = rawArgs.indexOf(name);
return index === -1 ? undefined : rawArgs[index + 1];
}
function parsePositiveInt(value: string | undefined, fallback: number) {
const parsed = Number.parseInt(value ?? "", 10);
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
}
function parseShard(value: string | undefined) {
const text = value || process.env.ZERO_VALIDATION_SHARD || "";
if (!text) return null;
const match = /^([1-9][0-9]*)\/([1-9][0-9]*)$/.exec(text);
if (!match) throw new Error(`invalid shard ${text}; expected index/count, for example 1/4`);
const index = Number.parseInt(match[1], 10);
const count = Number.parseInt(match[2], 10);
if (index > count) throw new Error(`invalid shard ${text}; index must be <= count`);
return { index, count, text };
}
function parsePhaseFilter(value: string | undefined, suite: Suite) {
const text = value || process.env.ZERO_VALIDATION_PHASES || "";
if (!text.trim()) return null;
const wanted = new Set(text.split(",").map((part) => part.trim()).filter(Boolean));
const known = new Set(suite.phases.map((phase) => phase.name));
const unknown = [...wanted].filter((name) => !known.has(name));
if (unknown.length > 0) {
throw new Error(`unknown validation phase(s): ${unknown.join(", ")}`);
}
return wanted;
}
const suite = suites[suiteName];
const listOnly = rawArgs.includes("--list");
const phaseFilter = parsePhaseFilter(optionValue("--phases"), suite);
const shard = parseShard(optionValue("--shard"));
const jobs = parsePositiveInt(optionValue("--jobs") ?? process.env.ZERO_VALIDATION_JOBS, suite.defaultJobs ?? 1);
const failFast = rawArgs.includes("--fail-fast") || process.env.ZERO_VALIDATION_FAIL_FAST === "1";
const filteredPhases = phaseFilter
? suite.phases.filter((phase) => phaseFilter.has(phase.name))
: suite.phases;
const selectedPhases = shard
? filteredPhases.filter((_, index) => index % shard.count === shard.index - 1)
: filteredPhases;
if (listOnly) {
for (const phase of suite.setup) console.log(`${phase.name} setup`);
for (const [index, phase] of suite.phases.entries()) {
const selected = selectedPhases.includes(phase)
? "selected"
: phaseFilter && !phaseFilter.has(phase.name) ? "filtered" : "skipped";
console.log(`${index + 1}. ${phase.name} ${selected}`);
}
process.exit(0);
}
function runPhase(phase: Phase): Promise<PhaseResult> {
const startedAt = Date.now();
process.stderr.write(`validation phase start: ${phase.name}\n`);
return new Promise((resolve) => {
const child = spawn(phase.command, phase.args, {
cwd: process.cwd(),
env: process.env,
stdio: ["ignore", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
child.stdout.on("data", (chunk) => {
const text = chunk.toString();
stdout += text;
process.stdout.write(text);
});
child.stderr.on("data", (chunk) => {
const text = chunk.toString();
stderr += text;
process.stderr.write(text);
});
child.on("error", (error) => {
stderr += `${error.message}\n`;
});
child.on("close", (code, signal) => {
const durationMs = Date.now() - startedAt;
const ok = code === 0 && signal === null;
process.stderr.write(`validation phase ${ok ? "ok" : "failed"}: ${phase.name} (${durationMs}ms)\n`);
resolve({
name: phase.name,
command: [phase.command, ...phase.args].join(" "),
ok,
code,
signal,
durationMs,
stdout,
stderr,
});
});
});
}
async function runSequential(phases: Phase[]) {
const results: PhaseResult[] = [];
for (const phase of phases) {
const result = await runPhase(phase);
results.push(result);
if (!result.ok && failFast) break;
}
return results;
}
async function runConcurrent(phases: Phase[]) {
const results: PhaseResult[] = [];
let next = 0;
let stop = false;
const workerCount = Math.min(jobs, Math.max(1, phases.length));
async function worker() {
for (;;) {
if (stop) return;
const index = next++;
if (index >= phases.length) return;
const result = await runPhase(phases[index]);
results[index] = result;
if (!result.ok && failFast) stop = true;
}
}
await Promise.all(Array.from({ length: workerCount }, () => worker()));
return results.filter(Boolean);
}
function printFailures(results: any[]) {
const failures = results.filter((result) => !result.ok);
if (failures.length === 0) return;
process.stderr.write(`\n${suiteName} collected ${failures.length} failing phase(s):\n`);
for (const [index, failure] of failures.entries()) {
process.stderr.write(`\n${index + 1}. ${failure.name}\n`);
process.stderr.write(` command: ${failure.command}\n`);
process.stderr.write(` exit: ${failure.code ?? "signal"}${failure.signal ? ` signal ${failure.signal}` : ""}\n`);
const detail = (failure.stderr || failure.stdout || "").trim().split("\n").slice(-40).join("\n");
if (detail) process.stderr.write(`${detail}\n`);
}
}
async function main() {
mkdirSync(reportDir, { recursive: true });
const startedAt = Date.now();
const setupResults = await runSequential(suite.setup);
const setupFailed = setupResults.some((result) => !result.ok);
const phaseResults = setupFailed && failFast
? []
: jobs > 1 ? await runConcurrent(selectedPhases) : await runSequential(selectedPhases);
const results = [...setupResults, ...phaseResults];
const ok = results.every((result) => result.ok);
const report = {
suite: suiteName,
ok,
phases: phaseFilter ? [...phaseFilter] : null,
shard: shard?.text ?? null,
jobs,
failFast,
durationMs: Date.now() - startedAt,
results,
};
const reportPath = join(reportDir, `${suiteName}${shard ? `-shard-${shard.index}-of-${shard.count}` : ""}.json`);
writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`);
printFailures(results);
if (!ok) {
process.stderr.write(`\nvalidation suite failed: ${suiteName}\nreport: ${reportPath}\n`);
process.exit(1);
}
process.stderr.write(`validation suite ok: ${suiteName}\nreport: ${reportPath}\n`);
}
main().catch((error) => {
console.error(error instanceof Error ? error.stack || error.message : String(error));
process.exit(1);
});