chore: import upstream snapshot with attribution
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
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
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
#!/usr/bin/env -S node --experimental-strip-types --disable-warning=ExperimentalWarning
|
||||
import { spawn, spawnSync } from "node:child_process";
|
||||
import { existsSync, mkdtempSync, readdirSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join, resolve } from "node:path";
|
||||
|
||||
// Every example package with a committed zero.graph must pass `zero check`,
|
||||
// build an executable for the canonical linux-musl-x64 target, and `zero run`
|
||||
// cleanly. This closes the gap where a stale example keeps passing check while
|
||||
// run-compilation fails. Host-backend gaps (for example fs programs on the
|
||||
// Mach-O direct backend) are tolerated only after the linux build proves the
|
||||
// package is not stale; hosts whose backend supports the package still run it.
|
||||
|
||||
const zeroBin = resolve("bin/zero");
|
||||
const examplesDir = resolve("examples");
|
||||
const buildTarget = "linux-musl-x64";
|
||||
const runTimeoutMs = 120_000;
|
||||
const buildTimeoutMs = 300_000;
|
||||
|
||||
type Expectation = {
|
||||
exitCode?: number;
|
||||
output?: string;
|
||||
server?: { readyText: string };
|
||||
};
|
||||
|
||||
const expectations: Record<string, Expectation> = {
|
||||
"batch3-cli": { output: "batch3 cli ok" },
|
||||
"direct-package-arrays": { exitCode: 13 },
|
||||
"direct-package-call-order": { exitCode: 27 },
|
||||
"memory-package": { output: "memory package ok" },
|
||||
"ping-pong-api": { server: { readyText: "listening on " } },
|
||||
"readall-cli": { output: "readall cli ok" },
|
||||
"resource-cli": { output: "resource cli ok" },
|
||||
"systems-package": { output: "systems package" },
|
||||
"zero-hash": { output: "zero-hash ok" },
|
||||
};
|
||||
|
||||
const packages = readdirSync(examplesDir, { withFileTypes: true })
|
||||
.filter((entry) => entry.isDirectory() && existsSync(join(examplesDir, entry.name, "zero.graph")))
|
||||
.map((entry) => entry.name)
|
||||
.sort();
|
||||
|
||||
if (packages.length === 0) {
|
||||
console.error("examples gate found no example packages with committed zero.graph stores");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const staleExpectations = Object.keys(expectations).filter((name) => !packages.includes(name));
|
||||
if (staleExpectations.length > 0) {
|
||||
console.error(`examples gate expectations name missing packages: ${staleExpectations.join(", ")}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function runZero(args: string[], cwd: string, timeoutMs = runTimeoutMs) {
|
||||
const result = spawnSync(zeroBin, args, {
|
||||
cwd,
|
||||
encoding: "utf8",
|
||||
timeout: timeoutMs,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
return {
|
||||
code: result.status,
|
||||
signal: result.signal,
|
||||
stdout: result.stdout ?? "",
|
||||
stderr: result.stderr ?? "",
|
||||
error: result.error?.message ?? "",
|
||||
timedOut: result.error !== undefined && "code" in result.error && result.error.code === "ETIMEDOUT",
|
||||
timeoutMs,
|
||||
};
|
||||
}
|
||||
|
||||
function zeroFailure(action: string, result: ReturnType<typeof runZero>) {
|
||||
const detail = result.stderr || result.stdout || result.error;
|
||||
if (result.timedOut) {
|
||||
return `${action} timed out after ${result.timeoutMs}ms${detail ? `\n${detail}` : ""}`;
|
||||
}
|
||||
return `${action} failed (exit ${result.code}${result.signal ? ` signal ${result.signal}` : ""})${detail ? `\n${detail}` : ""}`;
|
||||
}
|
||||
|
||||
function isHostBackendGap(text: string) {
|
||||
return text.includes("BLD004") && text.includes("executable buildability subset");
|
||||
}
|
||||
|
||||
async function probeServer(packagePath: string, readyText: string, workDir: string) {
|
||||
return await new Promise<{ ok: boolean; detail: string }>((settle) => {
|
||||
const child = spawn(zeroBin, ["run", packagePath], { cwd: workDir, detached: true, stdio: ["ignore", "pipe", "pipe"] });
|
||||
let output = "";
|
||||
let done = false;
|
||||
const finish = (ok: boolean, detail: string) => {
|
||||
if (done) return;
|
||||
done = true;
|
||||
clearTimeout(timer);
|
||||
if (child.pid !== undefined && child.exitCode === null) {
|
||||
try {
|
||||
process.kill(-child.pid, "SIGKILL");
|
||||
} catch {
|
||||
child.kill("SIGKILL");
|
||||
}
|
||||
}
|
||||
settle({ ok, detail });
|
||||
};
|
||||
const timer = setTimeout(() => finish(false, `server never printed '${readyText}'\n${output}`), runTimeoutMs);
|
||||
const onChunk = (chunk: Buffer) => {
|
||||
output += chunk.toString();
|
||||
if (output.includes(readyText)) finish(true, "server ready");
|
||||
};
|
||||
child.stdout.on("data", onChunk);
|
||||
child.stderr.on("data", onChunk);
|
||||
child.on("error", (error) => finish(false, error.message));
|
||||
child.on("exit", (code, signal) => finish(false, `server exited before readiness: code ${code} signal ${signal}\n${output}`));
|
||||
});
|
||||
}
|
||||
|
||||
const failures: string[] = [];
|
||||
const startedAt = Date.now();
|
||||
|
||||
for (const name of packages) {
|
||||
const packagePath = join(examplesDir, name);
|
||||
const expectation = expectations[name] ?? {};
|
||||
const workDir = mkdtempSync(join(tmpdir(), `zero-examples-gate-${name}-`));
|
||||
const steps: string[] = [];
|
||||
let failure = "";
|
||||
|
||||
const check = runZero(["check", packagePath], workDir);
|
||||
if (check.code === 0) {
|
||||
steps.push("check");
|
||||
} else {
|
||||
failure = zeroFailure("zero check", check);
|
||||
}
|
||||
|
||||
let linuxBuildOk = false;
|
||||
if (!failure && !expectation.server) {
|
||||
const build = runZero(["build", "--emit", "exe", "--target", buildTarget, packagePath, "--out", join(workDir, `${name}-${buildTarget}`)], workDir, buildTimeoutMs);
|
||||
if (build.code === 0) {
|
||||
linuxBuildOk = true;
|
||||
steps.push(`build ${buildTarget}`);
|
||||
} else {
|
||||
failure = zeroFailure(`zero build --target ${buildTarget}`, build);
|
||||
}
|
||||
}
|
||||
|
||||
if (!failure && expectation.server) {
|
||||
const probe = await probeServer(packagePath, expectation.server.readyText, workDir);
|
||||
if (probe.ok) {
|
||||
steps.push("run server-ready");
|
||||
} else {
|
||||
failure = `zero run server probe failed: ${probe.detail}`;
|
||||
}
|
||||
} else if (!failure) {
|
||||
const expectedExit = expectation.exitCode ?? 0;
|
||||
// Arguments after the input belong to the program (zero run [input] -- <args>),
|
||||
// so --out must precede the package path or it lands in the program argv.
|
||||
const run = runZero(["run", "--out", join(workDir, `${name}-exe`), packagePath], workDir);
|
||||
const runText = `${run.stdout}${run.stderr}`;
|
||||
if (run.code !== expectedExit && linuxBuildOk && isHostBackendGap(runText)) {
|
||||
steps.push("run host-backend-gap");
|
||||
} else if (run.code !== expectedExit) {
|
||||
failure = `zero run exited ${run.code}${run.signal ? ` signal ${run.signal}` : ""}, expected ${expectedExit}\n${runText}`;
|
||||
} else if (expectation.output && !run.stdout.includes(expectation.output)) {
|
||||
failure = `zero run output missing '${expectation.output}'\n${runText}`;
|
||||
} else {
|
||||
steps.push("run");
|
||||
}
|
||||
}
|
||||
|
||||
// Retry the teardown: macOS indexing can briefly repopulate the temp tree
|
||||
// while rm walks it, surfacing as ENOTEMPTY on an otherwise clean removal.
|
||||
rmSync(workDir, { force: true, recursive: true, maxRetries: 5, retryDelay: 100 });
|
||||
if (failure) {
|
||||
failures.push(`${name}: ${failure.trimEnd()}`);
|
||||
console.error(`examples gate FAIL: ${name}`);
|
||||
} else {
|
||||
console.log(`examples gate ok: ${name} (${steps.join(", ")})`);
|
||||
}
|
||||
}
|
||||
|
||||
if (failures.length > 0) {
|
||||
console.error(`\nexamples gate failed for ${failures.length} package(s):\n`);
|
||||
for (const failure of failures) console.error(`${failure}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`examples gate ok: ${packages.length} packages in ${((Date.now() - startedAt) / 1000).toFixed(1)}s`);
|
||||
Reference in New Issue
Block a user