Files
workinf_Blender_Wasm/web/tests/unit/physics-solver-probe.test.mjs
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

99 lines
4.7 KiB
JavaScript

import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { pathToFileURL } from "node:url";
import test from "node:test";
import ts from "typescript";
const root = path.resolve(import.meta.dirname, "../../..");
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "physics-solver-probe-unit-"));
function transpile(sourceName, outputName, replacements = []) {
const sourcePath = path.join(root, "web/protocol", sourceName);
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: sourcePath,
reportDiagnostics: true,
});
assert.deepEqual(transpiled.diagnostics, []);
const output = replacements.reduce((source, [from, to]) => source.replaceAll(from, to), transpiled.outputText);
fs.writeFileSync(path.join(temporary, outputName), output);
}
transpile("capability-gates.ts", "capability-gates.mjs");
transpile("physics-simulation.ts", "physics-simulation.mjs", [
['from "./capability-gates"', 'from "./capability-gates.mjs"'],
]);
const physics = await import(pathToFileURL(path.join(temporary, "physics-simulation.mjs")));
const mib = 1024 * 1024;
test("M10-13 defaults every Physics family to desktop/server bake", async () => {
const capabilities = await physics.probePhysicsSolverCapabilities(undefined, {
threadMode: "PTHREAD",
memoryLimitBytes: 2_048 * mib,
});
assert.deepEqual(capabilities.map((entry) => entry.family), physics.PHYSICS_FAMILIES);
assert.ok(capabilities.every((entry) => entry.localSolver === "BLOCKED"));
assert.ok(capabilities.every((entry) => entry.solverProbe === "EXPORT_UNAVAILABLE"));
assert.ok(capabilities.every((entry) => entry.unsupportedRoute === "DESKTOP_SERVER_BAKE"));
});
test("M10-13 probes export, initialization, threads and memory per family", async () => {
const initialization = {
RIGID_BODY: { initialized: true, requiredThreadMode: "SINGLE", requiredMemoryBytes: 64 * mib },
SOFT_BODY: { initialized: false, requiredThreadMode: "SINGLE", requiredMemoryBytes: 64 * mib },
CLOTH: { initialized: true, requiredThreadMode: "PTHREAD", requiredMemoryBytes: 128 * mib },
FLUID: { initialized: true, requiredThreadMode: "SINGLE", requiredMemoryBytes: 512 * mib },
DYNAMIC_PAINT: { initialized: true, requiredThreadMode: "SINGLE", requiredMemoryBytes: Number.NaN },
};
const initialized = [];
const runtime = {
hasFamilyExport: (family) => family !== "HAIR",
initializeFamily: async (family) => {
initialized.push(family);
if (family === "PARTICLE") throw new Error("init failed");
return initialization[family];
},
};
const capabilities = await physics.probePhysicsSolverCapabilities(runtime, {
threadMode: "SINGLE",
memoryLimitBytes: 256 * mib,
});
assert.deepEqual(Object.fromEntries(capabilities.map((entry) => [entry.family, entry.solverProbe])), {
RIGID_BODY: "READY",
SOFT_BODY: "INITIALIZATION_FAILED",
CLOTH: "THREADS_UNAVAILABLE",
FLUID: "MEMORY_UNAVAILABLE",
DYNAMIC_PAINT: "INVALID_RESULT",
PARTICLE: "INITIALIZATION_FAILED",
HAIR: "EXPORT_UNAVAILABLE",
});
assert.deepEqual(initialized, ["RIGID_BODY", "SOFT_BODY", "CLOTH", "FLUID", "DYNAMIC_PAINT", "PARTICLE"]);
assert.deepEqual(physics.selectPhysicsExecutionRoute("RIGID_BODY", capabilities), {
family: "RIGID_BODY", mode: "LOCAL_SOLVER", probe: "READY",
});
assert.deepEqual(physics.selectPhysicsExecutionRoute("CLOTH", capabilities), {
family: "CLOTH", mode: "DESKTOP_SERVER_BAKE", probe: "THREADS_UNAVAILABLE",
});
assert.equal(physics.gatePhysicsExecution("RIGID_BODY", "LOCAL_SOLVER", capabilities).status, "READY");
const blocked = physics.gatePhysicsExecution("CLOTH", "LOCAL_SOLVER", capabilities);
assert.equal(blocked.status, "BLOCKED");
assert.equal(blocked.issues[0].code, "PHYSICS_SOLVER_UNAVAILABLE");
assert.match(blocked.issues[0].message, /desktop\/server bake/);
});
test("M10-13 fails closed on an invalid environment or forged probe result", async () => {
await assert.rejects(
physics.probePhysicsSolverCapabilities(undefined, { threadMode: "SINGLE", memoryLimitBytes: 0 }),
{ code: "PHYSICS_MANIFEST_INVALID" },
);
const capabilities = await physics.probePhysicsSolverCapabilities({
hasFamilyExport: () => true,
initializeFamily: () => ({ initialized: true, requiredThreadMode: "SINGLE", requiredMemoryBytes: -1 }),
}, { threadMode: "PTHREAD", memoryLimitBytes: 2_048 * mib });
assert.ok(capabilities.every((entry) => entry.solverProbe === "INVALID_RESULT"));
assert.ok(capabilities.every((entry) => entry.localSolver === "BLOCKED"));
});