Files
workinf_Blender_Wasm/tools/web/check-glb-web-reimport.mjs
mes123456 380cbed4ff
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
Checkpoint web parity through Chromium input tasks
2026-08-19 10:39:03 -04:00

95 lines
5.2 KiB
JavaScript

import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const manifestPath = path.join(root, "tests/golden/M12-06E/manifest.json");
const reportPath = path.join(root, "tests/golden/M12-06E/desktop-reimport-report.json");
const generator = path.join(root, "tools/web/generate-glb-web-reimport-report.py");
const blender = process.env.BLENDER_BIN ?? path.join(root, "build_blender_5.2.0/bin/blender");
const read = (file) => fs.readFileSync(file);
const readJson = (file) => JSON.parse(read(file));
const sha256 = (bytes) => crypto.createHash("sha256").update(bytes).digest("hex");
const fileHash = (file) => sha256(read(file));
const manifest = readJson(manifestPath);
const report = readJson(reportPath);
const base = readJson(path.join(root, "tests/golden/M12-06C/desktop-main-report.json"));
const exportReport = readJson(path.join(root, "tests/golden/M12-06D/web-loss-report.json"));
assert.deepEqual(
{ schemaVersion: manifest.schemaVersion, task: manifest.task, parentTask: manifest.parentTask, nextTask: manifest.nextTask },
{ schemaVersion: 1, task: "M12-06E", parentTask: "M12-06D", nextTask: "M12-06F" },
);
assert.deepEqual(
{ schemaVersion: report.schemaVersion, task: report.task, operation: report.operation, fixtureCount: report.fixtureCount, nextTask: report.nextTask },
{ schemaVersion: 1, task: "M12-06E", operation: "DESKTOP_REIMPORT_WEB_GLB_CANONICAL_REPORT", fixtureCount: 4, nextTask: "M12-06F" },
);
assert.equal(fileHash(path.join(root, "tests/golden/M12-06D/manifest.json")), manifest.artifacts.parentManifest.sha256);
for (const artifact of Object.values(manifest.artifacts)) {
const file = path.join(root, artifact.path);
assert.ok(fs.existsSync(file), `missing artifact ${artifact.path}`);
assert.equal(fileHash(file), artifact.sha256, `hash mismatch ${artifact.path}`);
}
const expectedIds = ["pbr", "uv", "skin", "animation"];
assert.deepEqual(report.fixtures.map((fixture) => fixture.id), expectedIds);
for (const fixture of report.fixtures) {
const exportFixture = exportReport.fixtures.find((candidate) => candidate.fixtureId === fixture.id);
assert.ok(exportFixture?.output, `${fixture.id} has no Web GLB output`);
assert.equal(fixture.glb.sha256, exportFixture.output.sha256, `${fixture.id} Web GLB hash drift`);
assert.equal(fileHash(path.join(root, "tests/files/web/m12_glb_web_v1", fixture.glb.file)), fixture.glb.sha256);
}
function mismatches(expected, actual, pathName = "graph", output = []) {
if (Object.is(expected, actual)) return output;
if (Array.isArray(expected) || Array.isArray(actual)) {
if (!Array.isArray(expected) || !Array.isArray(actual)) { output.push(pathName); return output; }
if (expected.length !== actual.length) output.push(`${pathName}.length`);
for (let index = 0; index < Math.max(expected.length, actual.length); index++) {
if (index >= expected.length || index >= actual.length) output.push(`${pathName}[${index}]`);
else mismatches(expected[index], actual[index], `${pathName}[${index}]`, output);
}
return output;
}
if (expected && actual && typeof expected === "object" && typeof actual === "object") {
for (const key of new Set([...Object.keys(expected), ...Object.keys(actual)])) mismatches(expected[key], actual[key], `${pathName}.${key}`, output);
return output;
}
output.push(pathName);
return output;
}
const comparisons = report.fixtures.map((fixture) => {
const baseline = base.fixtures.find((candidate) => candidate.id === fixture.id);
assert.ok(baseline, `${fixture.id} missing M12-06C baseline`);
const paths = mismatches(baseline.graph, fixture.graph);
return { id: fixture.id, exact: paths.length === 0, mismatchCount: paths.length, mismatchPaths: paths };
});
assert.deepEqual(comparisons.map((comparison) => ({ id: comparison.id, exact: comparison.exact, mismatchCount: comparison.mismatchCount })), [
{ id: "pbr", exact: true, mismatchCount: 0 },
{ id: "uv", exact: false, mismatchCount: 4 },
{ id: "skin", exact: false, mismatchCount: 31 },
{ id: "animation", exact: true, mismatchCount: 0 },
]);
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "m12-06e-web-reimport-"));
try {
const regenerated = path.join(temporary, "desktop-reimport-report.json");
const result = spawnSync(blender, ["-b", "--factory-startup", "--python", generator, "--", path.join(root, "tests/files/web/m12_glb_web_v1"), path.join(temporary, "blend"), regenerated], {
cwd: root,
encoding: "utf8",
maxBuffer: 20 * 1024 * 1024,
});
assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
assert.deepEqual(readJson(regenerated), report, "desktop Web GLB report is not deterministic");
} finally {
fs.rmSync(temporary, { recursive: true, force: true });
}
process.stdout.write(`glb-web-reimport-ok fixtures=${report.fixtureCount} exact=${comparisons.filter((comparison) => comparison.exact).length} mismatched=${comparisons.filter((comparison) => !comparison.exact).map((comparison) => comparison.id).join(",")} deterministic=true next=${manifest.nextTask}\n`);