Files
workinf_Blender_Wasm/web/tests/unit/engine-variant-fallback.test.mjs
mes123456 7c16b279ae
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 M7 workflows and release operations
2026-08-15 17:43:53 -04:00

250 lines
7.3 KiB
JavaScript

import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import test from "node:test";
import ts from "typescript";
const repoRoot = path.resolve(import.meta.dirname, "../../..");
function transpileDataUrl(filePath, replacements = new Map()) {
const result = ts.transpileModule(fs.readFileSync(filePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: filePath,
reportDiagnostics: true,
});
assert.deepEqual(result.diagnostics, []);
let source = result.outputText;
for (const [specifier, replacement] of replacements) {
source = source.replaceAll(`"${specifier}"`, JSON.stringify(replacement));
}
return `data:text/javascript;base64,${Buffer.from(source).toString("base64")}`;
}
const gatesUrl = transpileDataUrl(path.join(repoRoot, "web/protocol/capability-gates.ts"));
const selectorUrl = transpileDataUrl(
path.join(repoRoot, "web/protocol/engine-variant.ts"),
new Map([["./capability-gates", gatesUrl]]),
);
const bootstrapUrl = transpileDataUrl(
path.join(repoRoot, "web/app/src/engine-client/engine-variant-bootstrap.ts"),
new Map([["../../../protocol/engine-variant", selectorUrl]]),
);
const {
bootstrapWebEngineRelease,
engineVariantStatusLabel,
EngineVariantLoadError,
} = await import(bootstrapUrl);
const manifest = JSON.parse(fs.readFileSync(
path.join(repoRoot, "tests/golden/M6-04A/engine-manifest-v2.json"),
"utf8",
));
const ready = { crossOriginIsolated: true, sharedArrayBuffer: true, worker: true };
class FakeSession {
constructor(variant) {
this.variant = variant;
this.opened = [];
this.disposals = 0;
this.state = {
handles: 1,
workers: variant.id === "pthread" ? 1 : 0,
timers: 0,
pendingRequests: 0,
};
}
async openProject(project) {
this.opened.push(project);
}
resourceState() {
return { ...this.state };
}
testOnlySeedTrackedResources() {
this.state.timers = 1;
this.state.pendingRequests = 1;
}
async dispose() {
this.disposals += 1;
this.state = { handles: 0, workers: 0, timers: 0, pendingRequests: 0 };
return this.resourceState();
}
}
function dependencies() {
const sessions = [];
return {
sessions,
initialize: async (variant) => {
const session = new FakeSession(variant);
sessions.push(session);
return session;
},
};
}
test("M6-05A/B/C injects one pthread failure, cleans it and falls back once", async () => {
const deps = dependencies();
const outcome = await bootstrapWebEngineRelease(
manifest.releaseId,
manifest,
"AUTO",
ready,
deps,
undefined,
{ failPthreadAfterInitialize: true, seedTrackedPthreadResources: true },
);
assert.deepEqual(outcome.result.attempted, ["pthread", "single"]);
assert.equal(deps.sessions.length, 2);
assert.equal(deps.sessions[0].disposals, 1);
assert.deepEqual(outcome.result.failedAttemptCleanup, {
handles: 0,
workers: 0,
timers: 0,
pendingRequests: 0,
});
assert.equal(outcome.result.selected, "single");
assert.deepEqual(outcome.result.fallbackReason, {
code: "PTHREAD_INITIALIZATION_FAILED",
message: "TEST_PTHREAD_INITIALIZATION_FAILURE",
});
assert.equal(outcome.result.openCount, 0);
assert.equal(deps.sessions[0].opened.length, 0);
assert.equal(deps.sessions[1].opened.length, 0);
});
test("M6-05D opens a pending project once only after the fallback settles", async () => {
const deps = dependencies();
const project = { id: "pending-project" };
const outcome = await bootstrapWebEngineRelease(
manifest.releaseId,
manifest,
"AUTO",
ready,
deps,
project,
{ failPthreadAfterInitialize: true },
);
assert.equal(outcome.result.openCount, 1);
assert.deepEqual(deps.sessions[0].opened, []);
assert.deepEqual(deps.sessions[1].opened, [project]);
});
test("M6-05E exposes selected, attempted and fallbackReason without pthread READY UI", async () => {
const deps = dependencies();
const outcome = await bootstrapWebEngineRelease(
manifest.releaseId,
manifest,
"AUTO",
ready,
deps,
undefined,
{ failPthreadAfterInitialize: true },
);
const label = engineVariantStatusLabel(outcome.result);
assert.deepEqual(
{
selected: outcome.result.selected,
attempted: outcome.result.attempted,
fallbackReason: outcome.result.fallbackReason?.code,
},
{
selected: "single",
attempted: ["pthread", "single"],
fallbackReason: "PTHREAD_INITIALIZATION_FAILED",
},
);
assert.equal(label, "Runtime: single (pthread fallback: PTHREAD_INITIALIZATION_FAILED)");
assert.doesNotMatch(label, /pthread\s+ready/i);
});
test("M6-08E treats a manifest hash mismatch as fatal without fallback or project open", async () => {
const initialized = [];
const project = { id: "must-stay-unopened" };
await assert.rejects(
bootstrapWebEngineRelease(
manifest.releaseId,
manifest,
"AUTO",
ready,
{
initialize: async (variant) => {
initialized.push(variant.id);
if (variant.id === "pthread") {
throw new EngineVariantLoadError("ENGINE_VARIANT_RESOURCE_HASH_MISMATCH", variant.resources.wasm.url);
}
return new FakeSession(variant);
},
},
project,
),
(error) => error?.code === "ENGINE_VARIANT_INTEGRITY_FAILED" &&
error.cause?.code === "ENGINE_VARIANT_RESOURCE_HASH_MISMATCH" &&
JSON.stringify(error.attempted) === JSON.stringify(["pthread"]),
);
assert.deepEqual(initialized, ["pthread"]);
});
test("M6-08D returns refresh-required before initialization or project open", async () => {
let initializationCount = 0;
const outcome = await bootstrapWebEngineRelease(
"blender-wasm-previous",
manifest,
"AUTO",
ready,
{
initialize: async (variant) => {
initializationCount += 1;
return new FakeSession(variant);
},
},
{ id: "must-stay-unopened" },
);
assert.deepEqual(outcome, {
result: {
status: "REFRESH_REQUIRED",
expectedReleaseId: "blender-wasm-previous",
actualReleaseId: manifest.releaseId,
manifest: null,
},
session: null,
});
assert.equal(initializationCount, 0);
});
test("M6-08E normalizes a fallback variant hash mismatch and never opens the project", async () => {
const initialized = [];
const pthreadSession = new FakeSession(manifest.variants[1]);
await assert.rejects(
bootstrapWebEngineRelease(
manifest.releaseId,
manifest,
"AUTO",
ready,
{
initialize: async (variant) => {
initialized.push(variant.id);
if (variant.id === "single") {
throw new EngineVariantLoadError("ENGINE_VARIANT_RESOURCE_HASH_MISMATCH", variant.resources.wasm.url);
}
return pthreadSession;
},
},
{ id: "must-stay-unopened" },
{ failPthreadAfterInitialize: true },
),
(error) => error?.code === "ENGINE_VARIANT_INTEGRITY_FAILED" &&
error.cause?.code === "ENGINE_VARIANT_RESOURCE_HASH_MISMATCH" &&
JSON.stringify(error.attempted) === JSON.stringify(["pthread", "single"]),
);
assert.deepEqual(initialized, ["pthread", "single"]);
assert.equal(pthreadSession.disposals, 1);
assert.deepEqual(pthreadSession.opened, []);
});