Files
workinf_Blender_Wasm/web/tests/e2e/io-format-recovery.spec.ts
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

69 lines
5.3 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 fixtures = {
OBJ: fs.readFileSync(path.join(root, "tests/files/web/m12_obj_multi_v1/multi-object.obj")),
STL: fs.readFileSync(path.join(root, "tests/files/web/m12_stl_capability_v1/capability-binary.stl")),
PLY: fs.readFileSync(path.join(root, "tests/files/web/m12_ply_mapping_v1/mapping-ascii.ply")),
};
const reportPath = path.join(root, "tests/golden/M12-07J/io-format-recovery-report.json");
const sha256 = (bytes: Uint8Array | Buffer) => crypto.createHash("sha256").update(bytes).digest("hex");
test("M12-07J recovers OBJ/STL/PLY after cancellation, OOM budget faults and Worker restart", async ({ page }) => {
await page.goto("/");
const result = await page.evaluate(async (input) => {
const recovery = await import("/src/testing/io-format-recovery.ts");
const run = (format: "OBJ" | "STL" | "PLY", bytes: number[], generation: number, phase: string) => new Promise<any>((resolve, reject) => {
const worker = new Worker("/src/workers/io-format-recovery-test.worker.ts", { type: "module" });
const requestId = `${format.toLowerCase()}-${phase}-${generation}`;
worker.onmessage = (event: MessageEvent<any>) => { worker.terminate(); if (!event.data.ok) reject(new Error(event.data.error)); else resolve(event.data); };
worker.onerror = (event) => { worker.terminate(); reject(new Error(event.message)); };
const payload = Uint8Array.from(bytes).buffer;
worker.postMessage({ type: "run", requestId, format, operation: "IMPORT", bytes: payload, workerGeneration: generation, baseRevision: 4 }, [payload]);
});
const cancel = (format: "OBJ" | "STL" | "PLY", bytes: number[]) => new Promise<any>((resolve, reject) => {
const worker = new Worker("/src/workers/io-format-recovery-test.worker.ts", { type: "module" });
const requestId = `${format.toLowerCase()}-cancel-1`;
worker.onmessage = (event: MessageEvent<any>) => { worker.terminate(); if (!event.data.ok) reject(new Error(event.data.error)); else resolve(event.data); };
worker.onerror = (event) => { worker.terminate(); reject(new Error(event.message)); };
const payload = Uint8Array.from(bytes).buffer;
worker.postMessage({ type: "run", requestId, format, operation: "IMPORT", bytes: payload, workerGeneration: 1, baseRevision: 4 }, [payload]);
setTimeout(() => worker.postMessage({ type: "cancel", targetRequestId: requestId }), 3);
});
const summary: Record<string, any> = {};
for (const format of ["OBJ", "STL", "PLY"] as const) {
const source = input[format];
const cancelled = await cancel(format, source);
const oversized = new Array(512 * 1024 + 1).fill(7);
const oom = await run(format, oversized, 1, "oom");
const first = await run(format, source, 1, "first");
const second = await run(format, source, 2, "second");
const recovered = recovery.recoverIOFormatRecoveryOperation(first.receipt, 2);
const small = await run(format, source, 3, "small");
summary[format] = { sourceSha256: await crypto.subtle.digest("SHA-256", Uint8Array.from(source)).then((digest) => Array.from(new Uint8Array(digest), (value) => value.toString(16).padStart(2, "0")).join("")), cancel: cancelled.receipt, oom: oom.receipt, first: first.receipt, second: second.receipt, recovered, small: small.receipt, hashes: { first: first.result.outputSha256, second: second.result.outputSha256, small: small.result.outputSha256 } };
}
return summary;
}, Object.fromEntries(Object.entries(fixtures).map(([format, bytes]) => [format, Array.from(bytes)])));
for (const format of ["OBJ", "STL", "PLY"] as const) {
const value = result[format];
expect(value.cancel).toMatchObject({ status: "CANCELLED", errorCode: "IO_FORMAT_OPERATION_CANCELLED", temporaryBytes: 0, liveRequests: 0, publishedResults: 0, committed: false });
expect(value.oom).toMatchObject({ status: "BLOCKED", errorCode: "IO_FORMAT_OOM", temporaryBytes: 0, liveRequests: 0, publishedResults: 0, committed: false });
expect(value.recovered).toMatchObject({ status: "RECOVERED", workerGeneration: 2, errorCode: "IO_FORMAT_WORKER_RESTARTED", committed: true });
expect(value.first).toMatchObject({ status: "COMMITTED", workerGeneration: 1, publishedResults: 1, committed: true });
expect(value.second).toMatchObject({ status: "COMMITTED", workerGeneration: 2, publishedResults: 1, committed: true });
expect(value.small).toMatchObject({ status: "COMMITTED", workerGeneration: 3, publishedResults: 1, committed: true });
expect(value.hashes.second).toBe(value.hashes.first);
expect(value.hashes.small).toBe(value.hashes.first);
}
const report = { schemaVersion: 1, task: "M12-07J", operation: "IO_FORMAT_THREE_WAY_RECOVERY", formats: result, assertions: { formats: ["OBJ", "STL", "PLY"], cancellationUnpublished: true, oomUnpublished: true, restartHashStable: true, smallRecoveryStable: true }, nextTask: "M13-01A" };
if (process.env.UPDATE_IO_FORMAT_RECOVERY_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")));
});