124 lines
6.0 KiB
JavaScript
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`,
|
|
);
|