Files
workinf_Blender_Wasm/web/tests/e2e/sequencer-media-cache.spec.ts
mes123456 0fe8d2bb56
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
Advance M8-M11 parity workflows
2026-08-17 04:37:07 -04:00

133 lines
6.8 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;
decoded: Record<string, number>;
}>;
};
const cacheGolden = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M11-10/sequencer-media-cache.json"), "utf8")) as {
sourceSha256: string;
profile: { kind: "MOVIE_RGBA8_FRAME"; width: number; height: number; colorSpace: "SRGB8"; alphaMode: "STRAIGHT" };
identitySha256: string;
proxyByteLength: number;
};
test("M11-10 binds a real movie proxy cache to source hash and runtime decode capability", 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(cacheGolden.sourceSha256);
await page.goto("/");
const result = await page.evaluate(async ({ movie, profile, bytes }) => {
const [probeModule, cacheModule, storageModule] = await Promise.all([
import("/src/sequencer/SequencerCodecProbe.ts"),
import("/src/sequencer/SequencerMediaProxyCache.ts"),
import("/src/storage/StorageClient.ts"),
]);
const request = probeModule.createSequencerCodecProbeRequest("MOVIE", movie.mimeType, movie.byteLength, movie.sha256);
const sourceData = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
const capability = await probeModule.probeSequencerCodec(request, sourceData.slice(0));
const generated = await cacheModule.generateInitialSequencerMovieProxyFrame(request, capability, profile, sourceData.slice(0));
const cache = new cacheModule.SequencerMediaProxyCache(generated.data.byteLength);
const firstKey = await cache.put(generated.manifest, generated.data.slice(0), request, capability);
const hit = await cache.get(request, capability, profile, 0);
const secondManifest = await import("/src/sequencer/SequencerTimeline.ts").then((module) =>
module.createSequencerMediaCacheManifest(request, capability, profile, 1, generated.data.slice(0)));
const secondKey = await cache.put(secondManifest, generated.data.slice(0), request, capability);
const evicted = await cache.get(request, capability, profile, 0);
const projectId = `m11-10-${Date.now()}`;
const writer = new storageModule.StorageClient();
const storedPayload = await writer.putAsset(
projectId,
generated.data.slice(0),
"application/vnd.blender.sequencer-proxy-rgba8",
"cache/sequencer/proxy-frame-0.rgba8",
);
const manifestData = new TextEncoder().encode(JSON.stringify(generated.manifest)).buffer as ArrayBuffer;
const storedManifest = await writer.putAsset(
projectId,
manifestData,
"application/vnd.blender.sequencer-proxy-cache+json",
"cache/sequencer/proxy-frame-0.json",
);
writer.terminate();
const reader = new storageModule.StorageClient();
const [reopenedPayload, reopenedManifestAsset] = await Promise.all([
reader.readAsset(projectId, storedPayload.sha256),
reader.readAsset(projectId, storedManifest.sha256),
]);
reader.terminate();
const reopenedManifest = JSON.parse(new TextDecoder().decode(reopenedManifestAsset.data));
const verified = await import("/src/sequencer/SequencerTimeline.ts").then((module) =>
module.verifySequencerMediaCacheEntry(reopenedManifest, reopenedPayload.data, request, capability));
const changedSource = { ...request, sourceSha256: "e".repeat(64) };
const changedSourceCapability = { ...capability, sourceSha256: changedSource.sourceSha256 };
const changedCapability = {
...capability,
decoded: { ...capability.decoded!, durationMicros: capability.decoded!.durationMicros! + 1 },
};
const corruptPayload = reopenedPayload.data.slice(0);
new Uint8Array(corruptPayload)[0] ^= 0xff;
const code = async (operation: () => Promise<unknown>): Promise<string> => {
try { await operation(); return "unexpected-success"; }
catch (error) { return (error as { code?: string }).code ?? String(error); }
};
const protocol = await import("/src/sequencer/SequencerTimeline.ts");
const errors = {
source: await code(() => protocol.verifySequencerMediaCacheEntry(reopenedManifest, reopenedPayload.data, changedSource, changedSourceCapability)),
capability: await code(() => protocol.verifySequencerMediaCacheEntry(reopenedManifest, reopenedPayload.data, request, changedCapability)),
payload: await code(() => protocol.verifySequencerMediaCacheEntry(reopenedManifest, corruptPayload, request, capability)),
sourceBytes: await code(() => cacheModule.generateInitialSequencerMovieProxyFrame(request, capability, profile, new ArrayBuffer(request.byteLength))),
};
const statsBeforeClear = cache.stats();
const releasedBytes = cache.clear();
return {
capability,
manifest: generated.manifest,
firstKey,
secondKey,
hitBytes: hit?.data.byteLength,
evicted: evicted === undefined,
statsBeforeClear,
releasedBytes,
statsAfterClear: cache.stats(),
storedPayloadSha256: storedPayload.sha256,
verifiedIdentity: verified.identitySha256,
errors,
};
}, { movie, profile: cacheGolden.profile, bytes: new Uint8Array(bytes) });
expect(result.capability).toMatchObject({ status: "READY", backend: "HTML_MEDIA", decoded: movie.decoded });
expect(result.manifest).toMatchObject({
identitySha256: cacheGolden.identitySha256,
payloadByteLength: cacheGolden.proxyByteLength,
profile: cacheGolden.profile,
});
expect(result.manifest.payloadSha256).toBe(result.storedPayloadSha256);
expect(result.verifiedIdentity).toBe(cacheGolden.identitySha256);
expect(result.firstKey).toBe(`sequencer-media-cache:v1:${cacheGolden.identitySha256}`);
expect(result.secondKey).not.toBe(result.firstKey);
expect(result.hitBytes).toBe(cacheGolden.proxyByteLength);
expect(result.evicted).toBe(true);
expect(result.statsBeforeClear).toMatchObject({ entries: 1, bytes: cacheGolden.proxyByteLength, hits: 1, misses: 1, evictions: 1 });
expect(result.releasedBytes).toBe(cacheGolden.proxyByteLength);
expect(result.statsAfterClear).toMatchObject({ entries: 0, bytes: 0 });
expect(result.errors).toEqual({
source: "SEQUENCER_CACHE_SOURCE_MISMATCH",
capability: "SEQUENCER_CACHE_CAPABILITY_MISMATCH",
payload: "SEQUENCER_CACHE_HASH_MISMATCH",
sourceBytes: "SEQUENCER_CACHE_SOURCE_MISMATCH",
});
});