Files
workinf_Blender_Wasm/web/tests/e2e/sequencer-media-revision.spec.ts
mes123456 0fe8d2bb56
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
Advance M8-M11 parity workflows
2026-08-17 04:37:07 -04:00

95 lines
4.9 KiB
TypeScript

import { expect, test } from "@playwright/test";
import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
const root = path.resolve(import.meta.dirname, "../../..");
const codecGolden = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M11-09/sequencer-codec-probe.json"), "utf8")) as {
assets: Array<{ stripType: "IMAGE" | "SOUND" | "MOVIE"; mimeType: string; path: string; byteLength: number; sha256: string }>;
};
const revisionGolden = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M11-11/sequencer-media-revision.json"), "utf8")) as {
sourceSha256: string;
decisions: Array<[string, number, "PUBLISH" | "STALE", string | null]>;
published: string[];
cacheWrites: string[];
};
test("M11-11 gates late seek, scrub and real decode results before publish or cache", async ({ page }) => {
const movie = codecGolden.assets.find((asset) => asset.stripType === "MOVIE")!;
const bytes = fs.readFileSync(path.join(root, movie.path));
expect(crypto.createHash("sha256").update(bytes).digest("hex")).toBe(revisionGolden.sourceSha256);
await page.goto("/");
const result = await page.evaluate(async ({ movie, bytes }) => {
const [probeModule, revisionModule] = await Promise.all([
import("/src/sequencer/SequencerCodecProbe.ts"),
import("/src/sequencer/SequencerMediaRevisionGate.ts"),
]);
const gate = new revisionModule.SequencerMediaRevisionGate("sequencer:main", 7);
const published: string[] = [];
const cacheWrites: string[] = [];
const decisions: Array<[string, number, string, string | null]> = [];
const complete = (
request: import("/src/sequencer/SequencerMediaRevisionGate.ts").SequencerMediaRevisionRequestIR,
payloadSha256 = movie.sha256,
) => ({ ...request, status: "COMPLETED" as const, sourceFrame: request.frame, payloadSha256 });
const resolve = (
request: import("/src/sequencer/SequencerMediaRevisionGate.ts").SequencerMediaRevisionRequestIR,
response = complete(request),
) => {
const decision = gate.resolve(request, response, (accepted) => {
const label = `${accepted.operation}@${accepted.requestRevision}`;
published.push(label);
if (accepted.operation === "DECODE") cacheWrites.push(label);
});
decisions.push([decision.operation, decision.requestRevision, decision.status, decision.code]);
return decision;
};
const oldSeek = gate.begin("SEEK", 10);
const oldSeekResult = new Promise<ReturnType<typeof complete>>((resolveResult) =>
setTimeout(() => resolveResult(complete(oldSeek)), 30));
const currentScrub = gate.begin("SCRUB", 20);
resolve(currentScrub);
resolve(oldSeek, await oldSeekResult);
const oldDecode = gate.begin("DECODE", 30);
const request = probeModule.createSequencerCodecProbeRequest("MOVIE", movie.mimeType, movie.byteLength, movie.sha256);
const source = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
const lateDecode = new Promise<Awaited<ReturnType<typeof probeModule.probeSequencerCodec>>>((resolveReceipt) =>
setTimeout(() => { void probeModule.probeSequencerCodec(request, source.slice(0)).then(resolveReceipt); }, 20));
const replacedState = gate.replaceTimeline("sequencer:main", 8);
const lateReceipt = await lateDecode;
resolve(oldDecode, complete(oldDecode, lateReceipt.sourceSha256));
const currentDecode = gate.begin("DECODE", 40);
const currentReceipt = await probeModule.probeSequencerCodec(request, source.slice(0));
resolve(currentDecode, complete(currentDecode, currentReceipt.sourceSha256));
const forgedSeek = gate.begin("SEEK", 50);
resolve(forgedSeek, { ...complete(forgedSeek), requestId: "media:forged" });
let monotonicCode = "";
try { gate.replaceTimeline("sequencer:main", 8); }
catch (error) { monotonicCode = (error as { code?: string }).code ?? String(error); }
return {
decisions,
published,
cacheWrites,
replacedState,
finalState: gate.state(),
lateReceipt: { status: lateReceipt.status, backend: lateReceipt.backend },
currentReceipt: { status: currentReceipt.status, backend: currentReceipt.backend },
monotonicCode,
};
}, { movie, bytes: new Uint8Array(bytes) });
expect(result.decisions).toEqual(revisionGolden.decisions);
expect(result.published).toEqual(revisionGolden.published);
expect(result.cacheWrites).toEqual(revisionGolden.cacheWrites);
expect(result.replacedState).toEqual({ schemaVersion: 1, timelineId: "sequencer:main", timelineRevision: 8, latestRequestRevision: 4 });
expect(result.finalState).toEqual({ schemaVersion: 1, timelineId: "sequencer:main", timelineRevision: 8, latestRequestRevision: 6 });
expect(result.lateReceipt).toEqual({ status: "READY", backend: "HTML_MEDIA" });
expect(result.currentReceipt).toEqual({ status: "READY", backend: "HTML_MEDIA" });
expect(result.monotonicCode).toBe("REVISION_CONFLICT");
});