Files
workinf_Blender_Wasm/web/tests/unit/engine-manifest-v2.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

98 lines
6.5 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, "../../..");
const protocolPath = path.join(repoRoot, "web/protocol/manifest.ts");
const protocolSource = fs.readFileSync(protocolPath, "utf8");
const transpiled = ts.transpileModule(protocolSource, {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: protocolPath,
reportDiagnostics: true,
});
assert.deepEqual(transpiled.diagnostics, []);
const protocol = await import(`data:text/javascript;base64,${Buffer.from(transpiled.outputText).toString("base64")}`);
const { validateWebEngineManifestV2 } = protocol;
const goldenPath = path.join(repoRoot, "tests/golden/M6-04A/engine-manifest-v2.json");
const golden = JSON.parse(fs.readFileSync(goldenPath, "utf8"));
const packageJson = JSON.parse(fs.readFileSync(path.join(repoRoot, "web/package.json"), "utf8"));
function deepFreeze(value) {
if (value && typeof value === "object") {
Object.freeze(value);
for (const item of Object.values(value)) deepFreeze(item);
}
return value;
}
function invalidCase(name, mutate, code, errorPath) {
return { name, mutate, code, errorPath };
}
test("M6-04A accepts an immutable single/pthread manifest without mutating it", () => {
const input = deepFreeze(structuredClone(golden));
const parsed = validateWebEngineManifestV2(input);
assert.deepEqual(parsed, golden);
assert.notEqual(parsed, input);
assert.notEqual(parsed.variants[0], input.variants[0]);
assert.notEqual(parsed.variants[0].resources.js, input.variants[0].resources.js);
assert.equal(parsed.variants[1].resources.pthreadWorker.url, parsed.variants[1].resources.js.url);
assert.equal(protocol.WEB_ENGINE_WASM_PAGE_BYTES, 65_536);
assert.deepEqual(protocol.WEB_ENGINE_MEMORY_LIMITS, {
minimumInitialPages: 256,
maximumPages: 32_768,
});
const reversed = validateWebEngineManifestV2({ ...golden, variants: [...golden.variants].reverse() });
assert.deepEqual(reversed.variants.map((variant) => variant.id), ["single", "pthread"]);
});
test("M6-04A rejects malformed variants, resources and memory declarations", async (t) => {
const cases = [
invalidCase("old schema", (value) => { value.schemaVersion = 1; }, "PROTOCOL_MISMATCH", "schemaVersion"),
invalidCase("unsupported protocol", (value) => { value.protocolVersion = 2; }, "PROTOCOL_MISMATCH", "protocolVersion"),
invalidCase("missing release ID", (value) => { delete value.releaseId; }, "ENGINE_MANIFEST_INVALID", "releaseId"),
invalidCase("invalid release ID", (value) => { value.releaseId = "release/id"; }, "ENGINE_MANIFEST_INVALID", "releaseId"),
invalidCase("missing pthread variant", (value) => { value.variants.pop(); }, "ENGINE_MANIFEST_INVALID", "variants"),
invalidCase("duplicate single variant", (value) => { value.variants[1].id = "single"; delete value.variants[1].resources.pthreadWorker; value.variants[1].memory.shared = false; }, "ENGINE_MANIFEST_INVALID", "variants"),
invalidCase("single shared memory", (value) => { value.variants[0].memory.shared = true; }, "ENGINE_MANIFEST_INVALID", "variants[0].memory.shared"),
invalidCase("single pthread worker", (value) => { value.variants[0].resources.pthreadWorker = structuredClone(value.variants[1].resources.pthreadWorker); }, "ENGINE_MANIFEST_INVALID", "variants[0].resources.pthreadWorker"),
invalidCase("pthread unshared memory", (value) => { value.variants[1].memory.shared = false; }, "ENGINE_MANIFEST_INVALID", "variants[1].memory.shared"),
invalidCase("missing pthread worker", (value) => { delete value.variants[1].resources.pthreadWorker; }, "ENGINE_MANIFEST_INVALID", "variants[1].resources.pthreadWorker"),
invalidCase("invalid SHA-256", (value) => { value.variants[1].resources.wasm.sha256 = "ABC"; }, "ENGINE_MANIFEST_INVALID", "variants[1].resources.wasm.sha256"),
invalidCase("remote resource URL", (value) => { value.variants[0].resources.js.url = "https://cdn.invalid/web_engine.single.js"; }, "ENGINE_MANIFEST_INVALID", "variants[0].resources.js.url"),
invalidCase("file name mismatch", (value) => { value.variants[0].resources.wasm.url = "/vendor/blender/wrong.wasm"; }, "ENGINE_MANIFEST_INVALID", "variants[0].resources.wasm.url"),
invalidCase("initial memory below floor", (value) => { value.variants[0].memory.initialPages = 255; }, "ENGINE_MANIFEST_INVALID", "variants[0].memory.initialPages"),
invalidCase("maximum memory below initial", (value) => { value.variants[1].memory.maximumPages = 255; }, "ENGINE_MANIFEST_INVALID", "variants[1].memory.maximumPages"),
invalidCase("maximum memory above ceiling", (value) => { value.variants[1].memory.maximumPages = 32_769; }, "ENGINE_MANIFEST_INVALID", "variants[1].memory.maximumPages"),
invalidCase("aliased variant WASM", (value) => { value.variants[1].resources.wasm = structuredClone(value.variants[0].resources.wasm); }, "ENGINE_MANIFEST_INVALID", "variants.wasm"),
invalidCase("worker aliases single JS", (value) => { value.variants[1].resources.pthreadWorker = structuredClone(value.variants[0].resources.js); }, "ENGINE_MANIFEST_INVALID", "variants.pthreadWorker"),
invalidCase("same worker URL with another hash", (value) => { value.variants[1].resources.pthreadWorker.sha256 = "5".repeat(64); }, "ENGINE_MANIFEST_INVALID", "variants.pthreadWorker.sha256"),
invalidCase("unknown manifest field", (value) => { value.defaultVariant = "pthread"; }, "ENGINE_MANIFEST_INVALID", "manifest.defaultVariant"),
];
for (const item of cases) {
await t.test(item.name, () => {
const input = structuredClone(golden);
item.mutate(input);
assert.throws(
() => validateWebEngineManifestV2(input),
(error) => error?.name === "WebEngineManifestValidationError" &&
error.code === item.code && error.path === item.errorPath,
);
});
}
});
test("M6-04B installs the production manifest with explicit variant paths", () => {
const production = JSON.parse(fs.readFileSync(path.join(repoRoot, "web/app/public/engine-manifest.json"), "utf8"));
const parsed = validateWebEngineManifestV2(production);
assert.equal(parsed.releaseId, `blender-wasm-${packageJson.version}`);
assert.equal(parsed.variants[0].resources.wasm.url, "/vendor/blender/single/web_engine.wasm");
assert.equal(parsed.variants[1].resources.wasm.url, "/vendor/blender/pthread/web_engine.wasm");
assert.notEqual(parsed.variants[0].resources.wasm.sha256, parsed.variants[1].resources.wasm.sha256);
});