95 lines
5.2 KiB
JavaScript
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`);
|