Checkpoint web parity through Chromium input tasks
This commit is contained in:
89
web/tests/e2e/ply-web-roundtrip.spec.ts
Normal file
89
web/tests/e2e/ply-web-roundtrip.spec.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
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 { expect, test, type Page } from "@playwright/test";
|
||||
|
||||
const root = path.resolve(import.meta.dirname, "../../..");
|
||||
const fixtureRoot = path.join(root, "tests/files/web/m12_ply_mapping_v1");
|
||||
const sourceAscii = fs.readFileSync(path.join(fixtureRoot, "mapping-ascii.ply"));
|
||||
const sourceBinary = fs.readFileSync(path.join(fixtureRoot, "mapping-binary-le.ply"));
|
||||
const sourceUnknown = fs.readFileSync(path.join(fixtureRoot, "unknown-property-ascii.ply"));
|
||||
const reportPath = path.join(root, "tests/golden/M12-07H/web-roundtrip-report.json");
|
||||
const sha256 = (bytes: Uint8Array | Buffer) => crypto.createHash("sha256").update(bytes).digest("hex");
|
||||
|
||||
async function runWorker(page: Page, bytes: Buffer, format: "ascii" | "binary_little_endian") {
|
||||
return page.evaluate(async (input) => new Promise<any>((resolve, reject) => {
|
||||
const worker = new Worker("/src/workers/ply-roundtrip-test.worker.ts", { type: "module" });
|
||||
worker.onmessage = (event: MessageEvent<any>) => { worker.terminate(); resolve({ ...event.data, output: event.data.output ? Array.from(new Uint8Array(event.data.output)) : null }); };
|
||||
worker.onerror = (event) => { worker.terminate(); reject(new Error(event.message)); };
|
||||
const payload = Uint8Array.from(input.bytes).buffer;
|
||||
worker.postMessage({ bytes: payload, format: input.format }, [payload]);
|
||||
}), { bytes: Array.from(bytes), format });
|
||||
}
|
||||
|
||||
test("M12-07H maps PLY vertex/face/color/custom fields and reports unknown property loss", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
const ascii = await runWorker(page, sourceAscii, "ascii");
|
||||
const binary = await runWorker(page, sourceBinary, "binary_little_endian");
|
||||
const unknown = await runWorker(page, sourceUnknown, "ascii");
|
||||
expect(ascii.ok).toBe(true);
|
||||
expect(binary.ok).toBe(true);
|
||||
expect(unknown.ok).toBe(true);
|
||||
expect(ascii.imported.vertices).toHaveLength(4);
|
||||
expect(ascii.imported.faces).toHaveLength(2);
|
||||
expect(ascii.imported.vertices[0].customProperties).toEqual({ label: 1, temperature: 10 });
|
||||
expect(binary.imported.vertices).toEqual(ascii.imported.vertices);
|
||||
expect(binary.imported.faces).toEqual(ascii.imported.faces);
|
||||
expect(ascii.lossReport).toEqual({ schemaVersion: 1, operation: "PLY_IMPORT_LOSS_REPORT", canImport: true, warningCount: 0, warnings: [] });
|
||||
expect(unknown.lossReport.warningCount).toBe(1);
|
||||
expect(unknown.lossReport.warnings[0].code).toBe("PLY_UNKNOWN_PROPERTY");
|
||||
|
||||
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "m12-07h-web-ply-"));
|
||||
try {
|
||||
const outputPath = path.join(temporary, "web-output.ply");
|
||||
fs.writeFileSync(outputPath, Buffer.from(ascii.output));
|
||||
const desktopReportPath = path.join(temporary, "desktop-report.json");
|
||||
const blender = process.env.BLENDER_BIN ?? path.join(root, "build_blender_5.2.0/bin/blender");
|
||||
const command = spawnSync(blender, ["-b", "--factory-startup", "--python", path.join(root, "tools/web/check-ply-web-roundtrip.py"), "--", outputPath, desktopReportPath], { cwd: root, encoding: "utf8", maxBuffer: 20 * 1024 * 1024 });
|
||||
expect(command.status, `${command.stdout}\n${command.stderr}`).toBe(0);
|
||||
const desktop = JSON.parse(fs.readFileSync(desktopReportPath, "utf8"));
|
||||
const report = {
|
||||
schemaVersion: 1,
|
||||
task: "M12-07H",
|
||||
operation: "PLY_VERTEX_FACE_COLOR_CUSTOM_MAPPING",
|
||||
source: { asciiSha256: sha256(sourceAscii), binarySha256: sha256(sourceBinary), unknownSha256: sha256(sourceUnknown) },
|
||||
browser: {
|
||||
format: ascii.imported.format,
|
||||
vertexCount: ascii.imported.vertices.length,
|
||||
faceCount: ascii.imported.faces.length,
|
||||
colors: ascii.imported.vertices.map((vertex: any) => vertex.color),
|
||||
customProperties: ascii.imported.vertices.map((vertex: any) => vertex.customProperties),
|
||||
outputSha256: sha256(Buffer.from(ascii.output)),
|
||||
outputBytes: ascii.output.length,
|
||||
lossReport: ascii.lossReport,
|
||||
binarySemanticEqual: JSON.stringify(binary.imported.vertices) === JSON.stringify(ascii.imported.vertices) && JSON.stringify(binary.imported.faces) === JSON.stringify(ascii.imported.faces),
|
||||
unknownPropertyLoss: unknown.lossReport,
|
||||
},
|
||||
desktop,
|
||||
comparisons: {
|
||||
vertexCountExact: desktop.vertexCount === ascii.imported.vertices.length,
|
||||
faceCountExact: desktop.triangleCount === ascii.imported.faces.length,
|
||||
positionExact: JSON.stringify(desktop.positions) === JSON.stringify(ascii.imported.vertices.map((vertex: any) => vertex.position)),
|
||||
customPropertiesPresent: desktop.attributes.filter((attribute: any) => ["temperature", "label"].includes(attribute.name)).length === 2,
|
||||
colorMapped: desktop.attributes.some((attribute: any) => attribute.name === "Col" && attribute.values.length === 4),
|
||||
},
|
||||
nextTask: "M12-07I",
|
||||
};
|
||||
if (process.env.UPDATE_PLY_MAPPING_REPORT === "1") {
|
||||
fs.mkdirSync(path.dirname(reportPath), { recursive: true });
|
||||
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2) + "\n");
|
||||
}
|
||||
expect(report).toEqual(JSON.parse(fs.readFileSync(reportPath, "utf8")));
|
||||
expect(report.comparisons).toEqual({ vertexCountExact: true, faceCountExact: true, positionExact: true, customPropertiesPresent: true, colorMapped: true });
|
||||
}
|
||||
finally {
|
||||
fs.rmSync(temporary, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user