107 lines
5.6 KiB
TypeScript
107 lines
5.6 KiB
TypeScript
import crypto from "node:crypto";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { expect, test } from "@playwright/test";
|
|
|
|
const root = path.resolve(import.meta.dirname, "../../..");
|
|
const mainReport = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-06C/desktop-main-report.json"), "utf8"));
|
|
const reportPath = path.join(root, "tests/golden/M12-06D/web-loss-report.json");
|
|
const fixtureRoot = path.join(root, "tests/files/web/m12_glb_main_v1");
|
|
|
|
const sha256 = (bytes: Uint8Array): string => crypto.createHash("sha256").update(bytes).digest("hex");
|
|
|
|
test("writes a machine GLB loss report for every persisted Main fixture", async ({ page }) => {
|
|
await page.goto("/");
|
|
const fixtures = mainReport.fixtures.map((fixture: { id: string; blend: { file: string; sha256: string } }) => ({
|
|
id: fixture.id,
|
|
bytes: Array.from(fs.readFileSync(path.join(fixtureRoot, fixture.blend.file))),
|
|
sourceBlendSha256: fixture.blend.sha256,
|
|
}));
|
|
const generated = await page.evaluate(async (input) => {
|
|
const { WebEngineClient } = await import("/src/engine-client/WebEngineClient.ts");
|
|
const digest = async (bytes: ArrayBuffer): Promise<string> => {
|
|
const hash = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes));
|
|
return Array.from(hash, (value) => value.toString(16).padStart(2, "0")).join("");
|
|
};
|
|
const reports = [];
|
|
for (const fixture of input) {
|
|
const client = new WebEngineClient({ timeoutMs: 30_000 });
|
|
try {
|
|
const opened = await client.openBlend(Uint8Array.from(fixture.bytes).buffer);
|
|
const assets = [];
|
|
for (const image of opened.snapshot.images) {
|
|
const asset = await client.requestAsset(image.assetId);
|
|
if (asset.status === "packed" && asset.data && asset.mimeType) assets.push({ assetId: image.assetId, mimeType: asset.mimeType, data: asset.data });
|
|
}
|
|
const worker = new Worker("/src/workers/glb-loss-report-test.worker.ts", { type: "module" });
|
|
const result = await new Promise<{ report: any; output: ArrayBuffer | null }>((resolve, reject) => {
|
|
worker.onmessage = (event: MessageEvent<{ ok: boolean; report?: any; output?: ArrayBuffer | null; error?: string }>) => {
|
|
worker.terminate();
|
|
if (!event.data.ok || !event.data.report) reject(new Error(event.data.error ?? "GLB loss report worker failed"));
|
|
else resolve({ report: event.data.report, output: event.data.output ?? null });
|
|
};
|
|
worker.onerror = (event) => { worker.terminate(); reject(new Error(event.message)); };
|
|
const transfer: Transferable[] = [];
|
|
for (const geometry of opened.geometryBuffers) for (const value of Object.values(geometry)) if (value instanceof ArrayBuffer) transfer.push(value);
|
|
for (const asset of assets) transfer.push(asset.data);
|
|
for (const chunk of opened.nonMeshGeometryBuffers ?? []) for (const value of Object.values(chunk)) if (value instanceof ArrayBuffer) transfer.push(value);
|
|
worker.postMessage({ snapshot: opened.snapshot, geometryBuffers: opened.geometryBuffers, assetBuffers: assets, nonMeshGeometryBuffers: opened.nonMeshGeometryBuffers ?? [] }, transfer);
|
|
});
|
|
reports.push({
|
|
fixtureId: fixture.id,
|
|
sourceBlendSha256: fixture.sourceBlendSha256,
|
|
lossReport: result.report,
|
|
output: result.output ? { byteLength: result.output.byteLength, sha256: await digest(result.output), bytes: Array.from(new Uint8Array(result.output)) } : null,
|
|
});
|
|
}
|
|
finally {
|
|
client.terminate();
|
|
}
|
|
}
|
|
return reports;
|
|
}, fixtures);
|
|
|
|
const report = {
|
|
schemaVersion: 1,
|
|
task: "M12-06D",
|
|
operation: "WEB_GLB_EXPORT_LOSS_REPORT",
|
|
fixtureCount: generated.length,
|
|
fixtures: generated,
|
|
nextTask: "M12-06E",
|
|
};
|
|
if (process.env.UPDATE_GLB_LOSS_REPORT === "1") {
|
|
const outputRoot = path.join(root, "tests/files/web/m12_glb_web_v1");
|
|
fs.mkdirSync(outputRoot, { recursive: true });
|
|
for (const fixture of generated) {
|
|
if (fixture.output?.bytes) fs.writeFileSync(path.join(outputRoot, `${fixture.fixtureId}.glb`), Buffer.from(fixture.output.bytes));
|
|
}
|
|
}
|
|
for (const fixture of report.fixtures) if (fixture.output?.bytes) delete fixture.output.bytes;
|
|
if (process.env.UPDATE_GLB_LOSS_REPORT === "1") {
|
|
fs.mkdirSync(path.dirname(reportPath), { recursive: true });
|
|
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2) + "\n");
|
|
}
|
|
const expected = JSON.parse(fs.readFileSync(reportPath, "utf8"));
|
|
expect(report).toEqual(expected);
|
|
expect(report.fixtureCount).toBe(5);
|
|
for (const fixture of report.fixtures) {
|
|
expect(fixture.lossReport.schemaVersion).toBe(1);
|
|
expect(fixture.lossReport.operation).toBe("GLB_EXPORT_LOSS_REPORT");
|
|
if (fixture.fixtureId === "mesh") {
|
|
expect(fixture.lossReport.canExport).toBe(false);
|
|
expect(fixture.lossReport.errorCount).toBe(1);
|
|
expect(fixture.lossReport.losses.map((loss: { code: string }) => loss.code)).toEqual(["LINKED_MATERIAL_INPUT_UNEVALUATED", "SHADER_GRAPH_UNMAPPABLE"]);
|
|
expect(fixture.output).toBeNull();
|
|
}
|
|
else {
|
|
expect(fixture.lossReport.canExport).toBe(true);
|
|
expect(fixture.lossReport.errorCount).toBe(0);
|
|
expect(fixture.output?.byteLength).toBeGreaterThan(128);
|
|
expect(fixture.output?.sha256).toMatch(/^[0-9a-f]{64}$/);
|
|
}
|
|
expect(fixture.lossReport.losses).toEqual([...fixture.lossReport.losses].sort((left, right) =>
|
|
left.code.localeCompare(right.code) || left.severity.localeCompare(right.severity) ||
|
|
(left.id ?? "").localeCompare(right.id ?? "") || left.message.localeCompare(right.message)));
|
|
}
|
|
});
|