Files
workinf_Blender_Wasm/tools/web/check-glb-desktop-fixtures.mjs
mes123456 380cbed4ff
Some checks are pending
M6 deployable RC / quick (push) Waiting to run
M6 deployable RC / chromium (push) Blocked by required conditions
M6 deployable RC / release (push) Blocked by required conditions
Checkpoint web parity through Chromium input tasks
2026-08-19 10:39:03 -04:00

124 lines
6.0 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-06A/manifest.json");
const reportPath = path.join(root, "tests/golden/M12-06A/desktop-fixtures.json");
const fixtureRoot = path.join(root, "tests/files/web/m12_glb_desktop_v1");
const generator = path.join(root, "tools/web/generate-glb-desktop-fixtures.py");
const blender = process.env.BLENDER_BIN ?? path.join(root, "build_blender_5.2.0/bin/blender");
const sha256 = (bytes) => crypto.createHash("sha256").update(bytes).digest("hex");
const fileHash = (file) => sha256(fs.readFileSync(file));
const readJson = (file) => JSON.parse(fs.readFileSync(file, "utf8"));
const manifest = readJson(manifestPath);
const report = readJson(reportPath);
assert.equal(manifest.schemaVersion, 1);
assert.equal(manifest.task, "M12-06A");
assert.equal(manifest.parentTask, "M12-05F");
assert.equal(manifest.nextTask, "M12-06B");
assert.equal(report.schemaVersion, 1);
assert.equal(report.task, "M12-06A");
assert.equal(report.operation, "DESKTOP_GLB_FIXTURE_GENERATION");
assert.equal(report.nextTask, "M12-06B");
assert.equal(report.fixtureCount, 5);
assert.equal(report.maxFixtureBytes, 512 * 1024);
for (const artifact of Object.values(manifest.artifacts)) {
if (artifact.path === manifestPath) continue;
const absolute = path.join(root, artifact.path);
assert.ok(fs.existsSync(absolute), `missing artifact ${artifact.path}`);
assert.equal(fileHash(absolute), artifact.sha256, `hash mismatch ${artifact.path}`);
}
const inventory = readJson(path.join(root, "tests/golden/M12-05A/format-inventory.json"));
for (const key of ["blenderVersion", "versionTuple", "buildDate", "buildTime", "buildHash", "buildBranch", "buildPlatform", "buildType", "binarySha256"]) {
assert.deepEqual(report.runtime[key], inventory.runtime[key], `runtime drift in ${key}`);
}
const expectedIds = ["mesh", "pbr", "uv", "skin", "animation"];
assert.deepEqual(report.fixtures.map((fixture) => fixture.id), expectedIds);
for (const fixture of report.fixtures) {
assert.ok(fixture.byteLength > 128, `${fixture.id} is unexpectedly empty`);
assert.ok(fixture.byteLength <= report.maxFixtureBytes, `${fixture.id} exceeds fixture budget`);
const file = path.join(fixtureRoot, fixture.file);
assert.equal(fileHash(file), fixture.sha256, `${fixture.id} fixture hash`);
assert.equal(fs.statSync(file).size, fixture.byteLength, `${fixture.id} fixture byte length`);
assert.equal(fixture.semantic.asset.version, "2.0");
assert.equal(fixture.semantic.extensionsUsed.length, 0, `${fixture.id} unexpectedly uses an extension`);
assert.equal(fixture.semantic.extensionsRequired.length, 0, `${fixture.id} unexpectedly requires an extension`);
assert.equal(fixture.semantic.meshes.length, 1);
assert.equal(fixture.semantic.meshes[0].primitives.length, 1);
const primitive = fixture.semantic.meshes[0].primitives[0];
assert.equal(primitive.mode, 4, `${fixture.id} is not triangle geometry`);
assert.ok(primitive.attributes.POSITION, `${fixture.id} lacks POSITION`);
assert.ok(primitive.indices, `${fixture.id} lacks indexed topology`);
if (fixture.id === "mesh") {
assert.ok(primitive.attributes.NORMAL);
assert.ok(primitive.attributes.COLOR_0);
assert.equal(primitive.indices.count, 6);
}
if (fixture.id === "pbr") {
assert.equal(fixture.semantic.materials.length, 1);
const pbr = fixture.semantic.materials[0].pbr;
assert.deepEqual(pbr.baseColorFactor.map((value) => Number(value.toFixed(3))), [0.31, 0.57, 0.91, 1]);
assert.equal(Number(pbr.metallicFactor.toFixed(3)), 0.72);
assert.equal(Number(pbr.roughnessFactor.toFixed(3)), 0.28);
assert.ok(fixture.semantic.materials[0].emissiveFactor);
}
if (fixture.id === "uv") {
assert.ok(primitive.attributes.TEXCOORD_0);
assert.equal(fixture.semantic.textures.length, 1);
assert.equal(fixture.semantic.images.length, 1);
assert.equal(fixture.semantic.images[0].mimeType, "image/png");
}
if (fixture.id === "skin") {
assert.ok(primitive.attributes.JOINTS_0);
assert.ok(primitive.attributes.WEIGHTS_0);
assert.equal(fixture.semantic.skins.length, 1);
assert.equal(fixture.semantic.skins[0].joints.length, 2);
assert.equal(fixture.semantic.skins[0].inverseBindMatrices.count, 2);
}
if (fixture.id === "animation") {
assert.equal(fixture.semantic.animations.length, 1);
assert.equal(fixture.semantic.animations[0].name, "M12 Animation Action");
assert.deepEqual(
fixture.semantic.animations[0].channels.map((channel) => channel.target.path).sort(),
["rotation", "translation"],
);
assert.equal(fixture.semantic.animations[0].samplers[0].input.count, 25);
}
}
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "m12-06a-glb-"));
try {
const regeneratedReport = path.join(temporary, "desktop-fixtures.json");
const result = spawnSync(blender, ["-b", "--factory-startup", "--python", generator, "--", temporary, regeneratedReport], {
cwd: root,
encoding: "utf8",
maxBuffer: 20 * 1024 * 1024,
});
assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`);
assert.deepEqual(readJson(regeneratedReport), report, "desktop semantic report is not deterministic");
for (const fixture of report.fixtures) {
assert.deepEqual(
fs.readFileSync(path.join(temporary, fixture.file)),
fs.readFileSync(path.join(fixtureRoot, fixture.file)),
`${fixture.id} GLB bytes are not deterministic`,
);
}
} finally {
fs.rmSync(temporary, { recursive: true, force: true });
}
process.stdout.write(
`glb-desktop-fixtures-ok fixtures=${report.fixtures.length} ` +
`bytes=${report.fixtures.reduce((total, fixture) => total + fixture.byteLength, 0)} ` +
`features=mesh,pbr,uv,skin,animation deterministic=true next=${manifest.nextTask}\n`,
);