93 lines
5.9 KiB
JavaScript
93 lines
5.9 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import crypto from "node:crypto";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
const reportRoot = path.join(repoRoot, "release/ci-reports");
|
|
const requested = process.argv.slice(2);
|
|
const paths = requested.length > 0
|
|
? requested.map((value) => path.isAbsolute(value) ? value : path.resolve(repoRoot, value))
|
|
: ["quick", "chromium", "release"].map((lane) => path.join(reportRoot, `${lane}.json`)).filter((file) => fs.existsSync(file));
|
|
assert.ok(paths.length > 0, "no CI lane reports were provided or found");
|
|
|
|
const sha256 = (value) => crypto.createHash("sha256").update(value).digest("hex");
|
|
const commonBindings = ["packageJson", "lockfile", "ledger", "engineManifest", "releaseNotes", "knownLimitations", "releaseRecovery", "sbom"];
|
|
const releaseBindings = ["binaryArchive", "sourceArchive", "sha256Sums", "rcManifest"];
|
|
const expectedRecordIds = {
|
|
quick: ["lockfile-install", "typecheck", "lint", "unit", "status", "evidence-schema", "rc-docs"],
|
|
chromium: ["lockfile-install", "p0-user-loop", "offline-browser-smoke", "network-interruption", "device-loss", "oom-recovery", "opfs-quota", "malicious-blend", "malicious-archive"],
|
|
release: [
|
|
"lockfile-install", "full-e2e", "performance-100k-1m", "performance-10m",
|
|
"performance-texture-4k", "performance-texture-8k", "performance-simulation",
|
|
"performance-long-media", "vdb-native", "vdb-server", "vdb-opfs", "vdb-webgpu",
|
|
"vdb-viewport", "vdb-faults", "v1-acceptance", "offline-reproducibility",
|
|
"archive-browser", "binary-archive", "source-archive", "deployment-runbook",
|
|
"upgrade-runbook", "rollback-runbook", "operations-diagnostics", "operations-rehearsal",
|
|
"rc-manifest",
|
|
],
|
|
};
|
|
const reports = [];
|
|
for (const reportPath of paths) {
|
|
const bytes = fs.readFileSync(reportPath);
|
|
const report = JSON.parse(bytes);
|
|
assert.equal(report.schemaVersion, 1, `${reportPath} schemaVersion drifted`);
|
|
assert.ok(["quick", "chromium", "release"].includes(report.lane), `${reportPath} lane is invalid`);
|
|
assert.equal(report.status, "READY", `${reportPath} is not READY`);
|
|
assert.match(report.commit, /^[a-f0-9]{40}$/);
|
|
assert.ok(!Number.isNaN(Date.parse(report.generatedAt)), `${reportPath} generatedAt is invalid`);
|
|
assert.equal(report.retentionDays, report.lane === "quick" ? 7 : report.lane === "chromium" ? 14 : 30);
|
|
for (const name of ["os", "node", "npm", "chrome", "blender", "emscripten"]) assert.equal(typeof report.environment[name], "string", `${reportPath} omits environment.${name}`);
|
|
for (const name of [...commonBindings, ...releaseBindings]) {
|
|
const binding = report.bindings[name];
|
|
assert.ok(binding && typeof binding.path === "string", `${reportPath} omits binding ${name}`);
|
|
if (binding.sha256 !== null) {
|
|
assert.match(binding.sha256, /^[a-f0-9]{64}$/);
|
|
const file = path.join(repoRoot, binding.path);
|
|
assert.equal(sha256(fs.readFileSync(file)), binding.sha256, `${reportPath} binding drifted: ${name}`);
|
|
}
|
|
}
|
|
if (report.lane === "release") {
|
|
for (const name of releaseBindings) assert.match(report.bindings[name].sha256, /^[a-f0-9]{64}$/);
|
|
}
|
|
else {
|
|
for (const name of releaseBindings) assert.equal(report.bindings[name].sha256, null);
|
|
}
|
|
assert.ok(Array.isArray(report.records) && report.records.length > 0, `${reportPath} has no command records`);
|
|
assert.deepEqual(report.records.map((record) => record.id), expectedRecordIds[report.lane], `${reportPath} command coverage drifted`);
|
|
for (const record of report.records) {
|
|
assert.equal(record.exitCode, 0, `${reportPath} command failed: ${record.id}`);
|
|
assert.ok(Number.isSafeInteger(record.durationMs) && record.durationMs >= 0);
|
|
assert.match(record.output.sha256, /^[a-f0-9]{64}$/);
|
|
const artifact = path.join(repoRoot, record.output.path);
|
|
assert.equal(fs.statSync(artifact).size, record.output.bytes, `${record.id} output length drifted`);
|
|
assert.equal(sha256(fs.readFileSync(artifact)), record.output.sha256, `${record.id} output hash drifted`);
|
|
}
|
|
const sidecar = fs.readFileSync(`${reportPath}.sha256`, "utf8").trim().split(/\s+/)[0];
|
|
assert.equal(sidecar, sha256(bytes), `${reportPath} sidecar hash drifted`);
|
|
reports.push(report);
|
|
}
|
|
|
|
if (reports.length > 1) {
|
|
assert.equal(new Set(reports.map((report) => report.commit)).size, 1, "CI lane reports do not bind one commit");
|
|
for (const binding of commonBindings) {
|
|
assert.equal(new Set(reports.map((report) => report.bindings[binding].sha256)).size, 1, `CI lane ${binding} bindings differ`);
|
|
}
|
|
}
|
|
if (reports.length === 3) {
|
|
assert.deepEqual(new Set(reports.map((report) => report.lane)), new Set(["quick", "chromium", "release"]));
|
|
const releaseReport = reports.find((report) => report.lane === "release");
|
|
const rcManifest = JSON.parse(fs.readFileSync(path.join(repoRoot, releaseReport.bindings.rcManifest.path), "utf8"));
|
|
assert.equal(rcManifest.gitCommit, releaseReport.commit, "RC manifest and CI reports bind different commits");
|
|
assert.equal(rcManifest.bindings.packageJsonSha256, releaseReport.bindings.packageJson.sha256);
|
|
assert.equal(rcManifest.bindings.packageLockSha256, releaseReport.bindings.lockfile.sha256);
|
|
assert.equal(rcManifest.bindings.ledgerSha256, releaseReport.bindings.ledger.sha256);
|
|
assert.equal(rcManifest.bindings.engineManifestSha256, releaseReport.bindings.engineManifest.sha256);
|
|
assert.equal(rcManifest.artifacts.sbom.sha256, releaseReport.bindings.sbom.sha256);
|
|
assert.equal(rcManifest.artifacts.binaryArchive.sha256, releaseReport.bindings.binaryArchive.sha256);
|
|
assert.equal(rcManifest.artifacts.sourceArchive.sha256, releaseReport.bindings.sourceArchive.sha256);
|
|
assert.equal(rcManifest.bindings.sha256SumsSha256, releaseReport.bindings.sha256Sums.sha256);
|
|
}
|
|
process.stdout.write(`ci-report-ok lanes=${reports.map((report) => report.lane).join(",")} commit=${reports[0].commit}\n`);
|