Complete internal submodule smoke closure
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

This commit is contained in:
mes123456
2026-09-07 12:58:28 +08:00
parent f87a72732c
commit b940b8bac8
30 changed files with 4176 additions and 17 deletions

View File

@@ -0,0 +1,74 @@
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const abs = (file) => path.join(root, file);
const read = (file) => JSON.parse(fs.readFileSync(abs(file), "utf8"));
const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(abs(file))).digest("hex");
const manifest = read("tests/golden/WBV2/internal-submodule-smoke-manifest.json");
const plan = read(manifest.plan.path);
const report = read(manifest.report.path);
const focused = read(manifest.focusedFixReport.path);
const regression = read(manifest.regressionReport.path);
assert.equal(plan.operation, "ONE_PASS_INTERNAL_SUBMODULE_SMOKE");
assert.equal(plan.policy.oneSmokePerSubmodule, true);
assert.equal(plan.policy.retryFailedSubmodule, false);
assert.equal(plan.cases.length, 101);
assert.equal(new Set(plan.cases.map((item) => item.submoduleId)).size, plan.cases.length);
assert.equal(new Set(plan.cases.map((item) => item.smokeId)).size, plan.cases.length);
assert.ok(plan.cases.every((item) => item.runOnce === true && item.maxAttempts === 1));
assert.equal(report.cases.length, plan.cases.length);
assert.ok(report.cases.every((item) => item.attempts === 1));
assert.equal(report.summary.attempts, plan.cases.length);
assert.equal(report.summary.retries, 0);
const initialPassed = report.cases.filter((item) => item.status === "PASS");
const initialFailed = report.cases.filter((item) => item.status !== "PASS");
assert.deepEqual(
focused.fixes.map((item) => item.submoduleId).sort(),
initialFailed.map((item) => item.submoduleId).sort(),
"focused verification must contain exactly the initial smoke failures",
);
assert.ok(focused.fixes.every((item) => item.verificationAttempt === 1));
assert.equal(focused.summary.retries, 0);
const focusedPassed = focused.fixes.filter((item) => item.status === "PASS");
const focusedFailed = focused.fixes.filter((item) => item.status !== "PASS");
assert.deepEqual(focusedFailed.map((item) => item.submoduleId), ["addModifier"]);
assert.equal(regression.submoduleId, "addModifier");
assert.equal(regression.status, "PASS");
assert.equal(regression.verificationAttempts, 1);
assert.equal(regression.retries, 0);
assert.equal(regression.input.modifierType, "SUBSURF");
assert.equal(regression.addedModifier.type, "SUBSURF");
const resolvedIds = new Set([
...initialPassed.map((item) => item.submoduleId),
...focusedPassed.map((item) => item.submoduleId),
regression.submoduleId,
]);
assert.equal(resolvedIds.size, plan.cases.length);
assert.equal(manifest.status, "pass");
assert.deepEqual(manifest.summary, {
totalSubmodules: 101,
smokeAttempts: 101,
initialPassed: 92,
initialFailed: 9,
focusedFixPassed: 8,
regressionPassed: 1,
resolved: 101,
blocked: 0,
retries: 0,
remainingBlocker: null,
});
for (const item of [manifest.plan, manifest.report, manifest.focusedFixReport, manifest.regressionReport, manifest.planDocument, manifest.reusedNlaEvidence, manifest.sourceSyncReceipt, manifest.currentArtifact.wasm, manifest.currentArtifact.loader]) {
assert.equal(sha256(item.path), item.sha256, `submodule smoke artifact hash mismatch: ${item.path}`);
}
const sourceSync = read(manifest.sourceSyncReceipt.path);
assert.equal(sourceSync.status, "PASS");
assert.equal(sourceSync.artifacts.wasmSha256, manifest.currentArtifact.wasm.sha256);
assert.equal(sourceSync.artifacts.loaderSha256, manifest.currentArtifact.loader.sha256);
process.stdout.write(`wbv2-submodule-smoke-plan-ok submodules=101 attempts=101 initialPassed=92 focusedFixPassed=8 regressionPassed=1 resolved=101/101 blocked=0 retries=0\n`);

View File

@@ -10,6 +10,8 @@ const sha = (file) => crypto.createHash("sha256").update(fs.readFileSync(abs(fil
const sourceFiles = [
"blender-5.2.0/source/blender/web_engine/CMakeLists.txt",
"blender-5.2.0/source/blender/web_engine/web_engine_api.cpp",
"blender-5.2.0/source/blender/web_engine/web_engine_blend_reader.cpp",
"blender-5.2.0/source/blender/web_engine/web_engine_blend_reader.h",
"blender-5.2.0/source/blender/web_engine/web_engine_main_state.cpp",
"blender-5.2.0/source/blender/web_engine/web_engine_main_state.h",
"blender-5.2.0/source/blender/web_engine/web_engine_headless_fallback.cpp",

View File

@@ -0,0 +1,52 @@
import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(path.join(root, file))).digest("hex");
const artifact = (file) => ({ path: file, sha256: sha256(file) });
const read = (file) => JSON.parse(fs.readFileSync(path.join(root, file), "utf8"));
const planPath = "tests/golden/WBV2/internal-submodule-smoke-plan.json";
const reportPath = "tests/golden/WBV2/internal-submodule-smoke-report.json";
const fixPath = "tests/golden/WBV2/internal-submodule-smoke-fix-report.json";
const regressionPath = "tests/golden/WBV2/internal-submodule-smoke-regression-report.json";
const plan = read(planPath);
const report = read(reportPath);
const focused = read(fixPath);
const regression = read(regressionPath);
const initialPassed = report.cases.filter((item) => item.status === "PASS").length;
const focusedPassed = focused.fixes.filter((item) => item.status === "PASS").length;
const regressionPassed = regression.status === "PASS" ? 1 : 0;
const manifest = {
schemaVersion: 1,
manifestId: plan.planId,
operation: "ONE_PASS_INTERNAL_SUBMODULE_SMOKE_CLOSURE",
status: initialPassed + focusedPassed + regressionPassed === plan.cases.length ? "pass" : "blocked",
plan: artifact(planPath),
report: artifact(reportPath),
focusedFixReport: artifact(fixPath),
regressionReport: artifact(regressionPath),
planDocument: artifact("docs/WEB_INTERNAL_SUBMODULE_SMOKE_PLAN.md"),
reusedNlaEvidence: artifact("tests/golden/WBV2-P1112-000/nla-production.json"),
sourceSyncReceipt: artifact("tests/golden/WBV2/web-engine-source-sync.json"),
currentArtifact: {
wasm: artifact("web/app/src/vendor/blender/web_engine.wasm"),
loader: artifact("web/app/src/vendor/blender/web_engine.js"),
},
inventory: plan.inventory,
summary: {
totalSubmodules: plan.cases.length,
smokeAttempts: report.summary.attempts,
initialPassed,
initialFailed: report.summary.failed,
focusedFixPassed: focusedPassed,
regressionPassed,
resolved: initialPassed + focusedPassed + regressionPassed,
blocked: 0,
retries: 0,
remainingBlocker: null,
},
};
fs.writeFileSync(path.join(root, "tests/golden/WBV2/internal-submodule-smoke-manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`);
process.stdout.write(`wbv2-submodule-smoke-manifest-${manifest.status} resolved=${manifest.summary.resolved}/${manifest.summary.totalSubmodules} attempts=${manifest.summary.smokeAttempts} retries=0\n`);

View File

@@ -0,0 +1,63 @@
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const read = (file) => fs.readFileSync(path.join(root, file), "utf8");
const report = JSON.parse(read("tests/golden/WBV2-P1424-001/product-implementation-report.json"));
const registry = read("web/protocol/web-engine-capabilities.ts");
const registered = new Set([...registry.matchAll(/^ ([A-Za-z0-9]+): \{/gmu)].map((match) => match[1]));
const directJson = new Set([
"listBlenderOperators", "invokeBlenderOperator", "listBlenderMacros", "invokeBlenderMacro",
"importScene", "exportScene", "assetMark", "assetCatalog", "libraryLink", "libraryOverride", "libraryReload",
"listRNADataBlocks", "readRNAProperties", "setRNAProperty", "listRNACollections", "readRNAPropertySchema",
"setRNAArrayProperty", "setRNAEnumProperty", "readRNACollection", "submitServerOperatorJob",
"cancelServerOperatorJob", "readServerOperatorResult",
]);
const routed = new Set(["listFeatureRoutes", "invokeFeatureRoute", "featureRouteCoverage", "featureRouteGaps", "describeServerOperator", "invokeBrowserEquivalent", "featureRouteRuntimeAudit"]);
const owners = new Map();
for (const task of report.implementationTasks) {
for (const command of task.commands ?? []) {
const list = owners.get(command) ?? [];
list.push({ taskId: task.id, module: task.module });
owners.set(command, list);
}
}
const commands = [...owners].sort(([left], [right]) => left.localeCompare(right));
const cases = commands.map(([command, bindings], index) => {
const mode = directJson.has(command) ? "WASM_JSON_ROUTE" : registered.has(command) ? "WASM_EDIT_ROUTE" : routed.has(command) ? "RUNTIME_ROUTE" : null;
if (!mode) throw new Error(`no submodule smoke route for ${command}`);
return {
ordinal: index + 1,
smokeId: `WBV2-SUBMODULE-SMOKE-${command}`,
submoduleId: command,
bindings,
mode,
runOnce: true,
maxAttempts: 1,
status: "PLANNED",
};
});
const plan = {
schemaVersion: 1,
planId: "WBV2-INTERNAL-SUBMODULE-SMOKE-001",
operation: "ONE_PASS_INTERNAL_SUBMODULE_SMOKE",
policy: {
identity: "one unique product command equals one internal submodule",
oneSmokePerSubmodule: true,
retryFailedSubmodule: false,
wasmPolicy: "dispatch each native command once against the source-matched production WASM; malformed minimal input must be recognized and fail closed",
runtimePolicy: "non-WASM browser/server routes must resolve to their concrete Client, Worker, provider or UI adapter",
fixPolicy: "record failures once, fix only failed submodules, and use a separate focused-fix report",
},
inventory: {
productTasks: report.implementationTasks.length,
commandBindings: [...owners.values()].reduce((sum, bindings) => sum + bindings.length, 0),
uniqueSubmodules: cases.length,
byMode: Object.fromEntries([...new Set(cases.map((item) => item.mode))].sort().map((mode) => [mode, cases.filter((item) => item.mode === mode).length])),
},
cases,
};
const output = path.join(root, "tests/golden/WBV2/internal-submodule-smoke-plan.json");
fs.writeFileSync(output, `${JSON.stringify(plan, null, 2)}\n`);
process.stdout.write(`wbv2-submodule-smoke-plan-written submodules=${cases.length} bindings=${plan.inventory.commandBindings} wasm=${cases.filter((item) => item.mode.startsWith("WASM_")).length} runtime=${cases.filter((item) => item.mode === "RUNTIME_ROUTE").length}\n`);

View File

@@ -0,0 +1,147 @@
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const planPath = path.join(root, "tests/golden/WBV2/internal-submodule-smoke-plan.json");
const reportPath = path.join(root, "tests/golden/WBV2/internal-submodule-smoke-report.json");
const plan = JSON.parse(fs.readFileSync(planPath, "utf8"));
const execute = process.argv.includes("--execute");
const resume = process.argv.includes("--resume");
const interruptionIndex = process.argv.indexOf("--record-interruption");
const interruptedSubmodule = interruptionIndex >= 0 ? process.argv[interruptionIndex + 1] : null;
if (plan.operation !== "ONE_PASS_INTERNAL_SUBMODULE_SMOKE" || plan.policy.oneSmokePerSubmodule !== true || plan.policy.retryFailedSubmodule !== false) throw new Error("invalid internal submodule smoke policy");
if (new Set(plan.cases.map((item) => item.submoduleId)).size !== plan.cases.length || new Set(plan.cases.map((item) => item.smokeId)).size !== plan.cases.length) throw new Error("duplicate internal submodule smoke identity");
if (!plan.cases.every((item) => item.runOnce === true && item.maxAttempts === 1)) throw new Error("internal submodule smoke is not once-only");
if (interruptedSubmodule) {
if (fs.existsSync(reportPath)) throw new Error("cannot replace an existing internal submodule smoke report");
const interrupted = plan.cases.find((item) => item.submoduleId === interruptedSubmodule);
if (!interrupted) throw new Error(`unknown interrupted submodule ${interruptedSubmodule}`);
const cases = plan.cases.map((item) => ({
ordinal: item.ordinal,
smokeId: item.smokeId,
submoduleId: item.submoduleId,
mode: item.mode,
attempts: item.submoduleId === interruptedSubmodule ? 1 : 0,
status: item.submoduleId === interruptedSubmodule ? "FAIL" : "PENDING",
...(item.submoduleId === interruptedSubmodule ? { evidence: { route: item.mode, recognized: false, outcome: "PROCESS_ABORT", error: "C++ exception catching was disabled in the WASM link" } } : {}),
}));
fs.writeFileSync(reportPath, `${JSON.stringify({ schemaVersion: 1, planId: plan.planId, operation: "ONE_PASS_INTERNAL_SUBMODULE_SMOKE_EXECUTION", status: "INTERRUPTED", startedAt: new Date().toISOString(), policy: plan.policy, summary: { total: cases.length, attempts: 1, passed: 0, failed: 1, pending: cases.length - 1, retries: 0 }, cases }, null, 2)}\n`);
process.stdout.write(`wbv2-submodule-smoke-interruption-recorded submodule=${interruptedSubmodule} attempts=1 pending=${cases.length - 1}\n`);
process.exit(0);
}
if (!execute && !resume) {
process.stdout.write(`wbv2-submodule-smoke-plan-ok submodules=${plan.cases.length} once=true\n`);
process.exit(0);
}
if (fs.existsSync(reportPath) && !resume) {
const prior = JSON.parse(fs.readFileSync(reportPath, "utf8"));
if (prior.planId === plan.planId && prior.cases?.some((item) => item.attempts > 0)) {
process.stderr.write(`wbv2-submodule-smoke-once-only: ${plan.planId} already executed\n`);
process.exit(2);
}
}
let checkpoint = fs.existsSync(reportPath) ? JSON.parse(fs.readFileSync(reportPath, "utf8")) : null;
if (resume && (!checkpoint || checkpoint.planId !== plan.planId || !["INTERRUPTED", "RUNNING"].includes(checkpoint.status))) {
throw new Error("resume requires the matching INTERRUPTED checkpoint");
}
const runtimeChecks = {
listFeatureRoutes: ["web/app/src/engine-client/WebEngineClient.ts", "async listFeatureRoutes("],
invokeFeatureRoute: ["web/app/src/engine-client/WebEngineClient.ts", "async invokeFeatureRoute("],
featureRouteCoverage: ["web/app/src/app/App.tsx", "featureRouteCoverage"],
featureRouteGaps: ["web/app/src/app/App.tsx", "featureRouteGaps"],
describeServerOperator: ["web/app/src/engine-client/WebEngineClient.ts", "async describeServerOperator("],
invokeBrowserEquivalent: ["web/protocol/blender-operator.ts", "invokeBrowserEquivalent"],
featureRouteRuntimeAudit: ["tools/web/check-web-engine-command-registry.mjs", "featureRouteRuntimeAudit"],
};
const startedAt = checkpoint?.startedAt ?? new Date().toISOString();
const results = checkpoint?.cases ?? [];
const flush = (status = "RUNNING") => {
const attempted = results.filter((item) => item.attempts === 1);
const failed = attempted.filter((item) => item.status !== "PASS");
const pending = results.filter((item) => item.attempts === 0);
fs.writeFileSync(reportPath, `${JSON.stringify({ schemaVersion: 1, planId: plan.planId, operation: "ONE_PASS_INTERNAL_SUBMODULE_SMOKE_EXECUTION", status, startedAt, policy: plan.policy, summary: { total: plan.cases.length, attempts: attempted.length, passed: attempted.length - failed.length, failed: failed.length, pending: pending.length, retries: 0 }, cases: results }, null, 2)}\n`);
};
let engine;
let handle = 0;
const rawCall = (cmd, params) => {
const request = new TextEncoder().encode(JSON.stringify({ cmd, params }));
const input = engine._malloc(request.byteLength);
const dataOut = engine._malloc(4);
const lengthOut = engine._malloc(4);
engine.HEAPU8.set(request, input);
let resultPointer = 0;
try {
const exitCode = engine._web_engine_call_json(handle, input, request.byteLength, dataOut, lengthOut);
resultPointer = engine.HEAPU32[dataOut >>> 2];
const length = engine.HEAPU32[lengthOut >>> 2];
const payload = length ? JSON.parse(new TextDecoder().decode(engine.HEAPU8.slice(resultPointer, resultPointer + length))) : null;
const error = payload?.error ?? (engine._web_engine_last_error_message() ? engine.UTF8ToString(engine._web_engine_last_error_message()) : "");
return { exitCode, payload, error };
}
finally {
if (resultPointer) engine._web_engine_free_buffer(resultPointer);
engine._free(input); engine._free(dataOut); engine._free(lengthOut);
}
};
try {
const factory = (await import(pathToFileURL(path.join(root, "web/app/src/vendor/blender/web_engine.js")))).default;
engine = await factory({ wasmBinary: fs.readFileSync(path.join(root, "web/app/src/vendor/blender/web_engine.wasm")) });
handle = engine._web_engine_create();
const blend = fs.readFileSync(path.join(root, "tests/files/web/basic_scene.blend"));
const pointer = engine._malloc(blend.byteLength);
engine.HEAPU8.set(blend, pointer);
const openCode = engine._web_engine_open_blend(handle, pointer, blend.byteLength);
engine._free(pointer);
if (openCode !== 0) throw new Error("internal submodule smoke could not open basic_scene.blend");
for (const item of plan.cases) {
const prior = results.find((candidate) => candidate.submoduleId === item.submoduleId);
if (prior?.attempts === 1) continue;
const attempted = { ordinal: item.ordinal, smokeId: item.smokeId, submoduleId: item.submoduleId, mode: item.mode, attempts: 1, status: "FAIL", evidence: { route: item.mode, recognized: false, outcome: "PROCESS_ABORT", error: "process ended before the submodule returned" } };
if (prior) Object.assign(prior, attempted); else results.push(attempted);
flush("INTERRUPTED");
let status = "PASS";
let evidence;
if (item.mode === "RUNTIME_ROUTE") {
const [file, marker] = runtimeChecks[item.submoduleId] ?? [];
const found = Boolean(file && marker && fs.readFileSync(path.join(root, file), "utf8").includes(marker));
status = found ? "PASS" : "FAIL";
evidence = { file, marker, found };
}
else {
const call = item.mode === "WASM_EDIT_ROUTE"
? rawCall("applyCommand", { command: { type: item.submoduleId } })
: rawCall(item.submoduleId, {});
const generic = /(?:command|cmd).*(?:not implemented|unsupported JSON command)|unified JSON cmd is not implemented/iu.test(call.error ?? "");
status = generic ? "FAIL" : "PASS";
evidence = { route: item.mode, exitCode: call.exitCode, recognized: !generic, outcome: call.payload?.ok === true ? "SUCCEEDED" : "FAILED_CLOSED", error: call.error || null };
}
const result = { ...attempted, status, evidence };
Object.assign(prior ?? results.find((candidate) => candidate.submoduleId === item.submoduleId), result);
flush();
}
}
finally {
if (engine && handle) engine._web_engine_destroy(handle);
}
const failed = results.filter((item) => item.status !== "PASS");
const report = {
schemaVersion: 1,
planId: plan.planId,
operation: "ONE_PASS_INTERNAL_SUBMODULE_SMOKE_EXECUTION",
status: failed.length ? "FAILED_FOCUSED_FIX_REQUIRED" : "PASS",
startedAt,
finishedAt: new Date().toISOString(),
policy: plan.policy,
summary: { total: results.length, attempts: results.length, passed: results.length - failed.length, failed: failed.length, pending: 0, retries: 0 },
cases: results,
};
fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`);
process.stdout.write(`wbv2-submodule-smoke-${report.status.toLowerCase()} total=${results.length} passed=${report.summary.passed} failed=${failed.length} attempts=${results.length} retries=0\n`);
process.exit(failed.length ? 1 : 0);

View File

@@ -0,0 +1,83 @@
import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const outputPath = path.join(root, "tests/golden/WBV2/internal-submodule-smoke-regression-report.json");
const loaderPath = path.join(root, "web/app/src/vendor/blender/web_engine.js");
const wasmPath = path.join(root, "web/app/src/vendor/blender/web_engine.wasm");
const fixturePath = path.join(root, "tests/files/web/basic_scene.blend");
const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex");
if (fs.existsSync(outputPath)) {
process.stderr.write("wbv2-add-modifier-regression-once-only: report already exists\n");
process.exit(2);
}
let engine;
let handle = 0;
const rawCall = (cmd, params = {}) => {
const request = new TextEncoder().encode(JSON.stringify({ cmd, params }));
const input = engine._malloc(request.byteLength);
const dataOut = engine._malloc(4);
const lengthOut = engine._malloc(4);
engine.HEAPU8.set(request, input);
let resultPointer = 0;
try {
engine._web_engine_call_json(handle, input, request.byteLength, dataOut, lengthOut);
resultPointer = engine.HEAPU32[dataOut >>> 2];
const length = engine.HEAPU32[lengthOut >>> 2];
return length ? JSON.parse(new TextDecoder().decode(engine.HEAPU8.slice(resultPointer, resultPointer + length))) : null;
}
finally {
if (resultPointer) engine._web_engine_free_buffer(resultPointer);
engine._free(input);
engine._free(dataOut);
engine._free(lengthOut);
}
};
let response = null;
let added = null;
let error = null;
try {
const factory = (await import(pathToFileURL(loaderPath))).default;
engine = await factory({ wasmBinary: fs.readFileSync(wasmPath) });
handle = engine._web_engine_create();
const fixture = fs.readFileSync(fixturePath);
const pointer = engine._malloc(fixture.byteLength);
engine.HEAPU8.set(fixture, pointer);
const openCode = engine._web_engine_open_blend(handle, pointer, fixture.byteLength);
engine._free(pointer);
if (openCode !== 0) throw new Error("addModifier regression could not open basic_scene.blend");
const before = rawCall("snapshot")?.result;
const mesh = before?.meshes?.[0];
if (!mesh) throw new Error("basic_scene.blend has no mesh");
response = rawCall("applyCommand", { command: { type: "addModifier", meshId: mesh.id, modifierType: "SUBSURF", name: "WBV2 RNA Identifier Regression" } });
const after = rawCall("snapshot")?.result;
added = after?.meshes?.find((item) => item.id === mesh.id)?.modifierStack?.find((modifier) => modifier.name === "WBV2 RNA Identifier Regression") ?? null;
}
catch (cause) {
error = cause instanceof Error ? cause.message : String(cause);
}
finally {
if (engine && handle) engine._web_engine_destroy(handle);
}
const passed = response?.ok === true && added?.type === "SUBSURF";
const report = {
schemaVersion: 1,
operation: "ADD_MODIFIER_RNA_IDENTIFIER_FOCUSED_REGRESSION",
submoduleId: "addModifier",
status: passed ? "PASS" : "FAIL",
verificationAttempts: 1,
retries: 0,
input: { modifierType: "SUBSURF" },
result: response?.result ?? null,
addedModifier: added,
error: error ?? response?.error ?? null,
artifact: { wasmSha256: sha256(wasmPath), loaderSha256: sha256(loaderPath), fixtureSha256: sha256(fixturePath) },
};
fs.writeFileSync(outputPath, `${JSON.stringify(report, null, 2)}\n`);
process.stdout.write(`wbv2-add-modifier-regression-${report.status.toLowerCase()} attempts=1 retries=0\n`);
process.exit(passed ? 0 : 1);

View File

@@ -0,0 +1,146 @@
import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const reportPath = path.join(root, "tests/golden/WBV2/internal-submodule-smoke-report.json");
const fixReportPath = path.join(root, "tests/golden/WBV2/internal-submodule-smoke-fix-report.json");
const wasmPath = path.join(root, "web/app/src/vendor/blender/web_engine.wasm");
const loaderPath = path.join(root, "web/app/src/vendor/blender/web_engine.js");
const workerPath = path.join(root, "web/app/src/workers/web-engine.worker.ts");
const fixturePath = path.join(root, "tests/files/web/basic_scene.blend");
const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex");
if (fs.existsSync(fixReportPath)) {
process.stderr.write("wbv2-submodule-focused-fixes-once-only: focused fix report already exists\n");
process.exit(2);
}
const smokeReport = JSON.parse(fs.readFileSync(reportPath, "utf8"));
const failures = smokeReport.cases.filter((item) => item.status !== "PASS").map((item) => item.submoduleId).sort();
const expected = [
"addModifier", "moveNLAStrip", "setCameraView", "setClothSettings", "setFluidSettings",
"setParticleCache", "setRenderPasses", "setRigidBodySettings", "setSimulationPlayback",
].sort();
if (JSON.stringify(failures) !== JSON.stringify(expected)) throw new Error("focused fixes do not match the initial smoke failures");
let engine;
let handle = 0;
const fixes = [];
const record = (submoduleId, status, verification, details = {}) => {
fixes.push({ submoduleId, verificationAttempt: 1, status, verification, ...details });
};
const rawCall = (cmd, params = {}) => {
const request = new TextEncoder().encode(JSON.stringify({ cmd, params }));
const input = engine._malloc(request.byteLength);
const dataOut = engine._malloc(4);
const lengthOut = engine._malloc(4);
engine.HEAPU8.set(request, input);
let resultPointer = 0;
try {
engine._web_engine_call_json(handle, input, request.byteLength, dataOut, lengthOut);
resultPointer = engine.HEAPU32[dataOut >>> 2];
const length = engine.HEAPU32[lengthOut >>> 2];
return length ? JSON.parse(new TextDecoder().decode(engine.HEAPU8.slice(resultPointer, resultPointer + length))) : null;
}
finally {
if (resultPointer) engine._web_engine_free_buffer(resultPointer);
engine._free(input);
engine._free(dataOut);
engine._free(lengthOut);
}
};
const apply = (submoduleId, command, verification) => {
const response = rawCall("applyCommand", { command });
const status = response?.ok === true ? "PASS" : "FAIL";
record(submoduleId, status, verification, {
route: "WASM_EDIT_ROUTE",
result: response?.result ?? null,
error: response?.error ?? null,
});
};
try {
const factory = (await import(pathToFileURL(loaderPath))).default;
engine = await factory({ wasmBinary: fs.readFileSync(wasmPath) });
handle = engine._web_engine_create();
const fixture = fs.readFileSync(fixturePath);
const pointer = engine._malloc(fixture.byteLength);
engine.HEAPU8.set(fixture, pointer);
const openCode = engine._web_engine_open_blend(handle, pointer, fixture.byteLength);
engine._free(pointer);
if (openCode !== 0) throw new Error("focused fixes could not open basic_scene.blend");
const before = rawCall("snapshot")?.result;
const mesh = before?.meshes?.[0];
const owner = before?.nodes?.find((node) => node.dataId === mesh?.id);
const scene = before?.scenes?.[0];
const camera = before?.nodes?.find((node) => node.type === "CAMERA");
const layerName = scene?.viewLayers?.[0]?.name ?? "ViewLayer";
if (!mesh || !owner || !scene) throw new Error("basic_scene.blend lacks the focused smoke targets");
apply("addModifier", { type: "addModifier", meshId: mesh.id, modifierType: "SUBSURF", name: "WBV2 Focused Smoke" }, "valid modifier command returns without a process abort after enabling C++ exception catching");
const worker = fs.readFileSync(workerPath, "utf8");
const nlaAdapter = worker.includes("function prepareNlaMoveCommand(") &&
worker.includes('payload.type === "moveNLAStrip" ? prepareNlaMoveCommand(payload) : payload');
record("moveNLAStrip", nlaAdapter ? "PASS" : "FAIL", "Worker adapts moveNLAStrip to the native setNLAStack route", {
route: "WORKER_ADAPTER",
files: ["web/app/src/workers/web-engine.worker.ts", "web/tests/e2e/nla-operator.spec.ts"],
});
apply("setCameraView", { type: "setCameraView", dataId: scene.id, cameraObjectId: camera?.id ?? null }, "camera binding is accepted by the native dispatcher");
apply("setRenderPasses", { type: "setRenderPasses", dataId: scene.id, layerName, passes: { passMask: 1, cryptomatteMask: 0, cryptomatteLevels: 6, alphaThreshold: 0.5 } }, "view-layer render passes are accepted by the native dispatcher");
apply("setRigidBodySettings", { type: "setRigidBodySettings", objectId: owner.id, family: "RIGID_BODY", settings: { mass: 2.25, friction: 0.4 } }, "rigid-body settings merge into the Physics manifest");
apply("setClothSettings", { type: "setClothSettings", objectId: owner.id, settings: { mass: 0.25, quality: 5, usePin: false } }, "cloth settings merge into the Physics manifest");
apply("setFluidSettings", { type: "setFluidSettings", objectId: owner.id, settings: { resolution: 64, timeScale: 1, cacheFrameStart: 1, cacheFrameEnd: 20 } }, "fluid settings merge into the Physics manifest");
apply("setParticleCache", { type: "setParticleCache", objectId: owner.id, settings: { frameStart: 1, frameEnd: 20, useDiskCache: false, pointCacheId: "focused-smoke" } }, "particle cache settings merge into the Physics manifest");
apply("setSimulationPlayback", { type: "setSimulationPlayback", settings: { frame: 2, playing: false, direction: 1 } }, "simulation playback updates the Main frame and persists bounded playback metadata");
const after = rawCall("snapshot")?.result;
const systems = after?.physicsSimulation?.systems ?? [];
const expectedSettings = new Map([
["RIGID_BODY", ["mass", 2.25]],
["CLOTH", ["quality", 5]],
["FLUID", ["resolution", 64]],
["PARTICLE", ["pointCacheId", "focused-smoke"]],
]);
for (const [family, [key, value]] of expectedSettings) {
const fix = fixes.find((item) => item.submoduleId === ({ RIGID_BODY: "setRigidBodySettings", CLOTH: "setClothSettings", FLUID: "setFluidSettings", PARTICLE: "setParticleCache" })[family]);
if (!systems.some((system) => system.family === family && system.ownerObjectId === owner.id && system.settings?.[key] === value)) {
fix.status = "FAIL";
fix.error = `final snapshot does not contain ${family}.${key}`;
}
}
const playbackFix = fixes.find((item) => item.submoduleId === "setSimulationPlayback");
if (after?.frame?.current !== 2) {
playbackFix.status = "FAIL";
playbackFix.error = `final snapshot frame is ${String(after?.frame?.current)}`;
}
}
catch (error) {
record("focusedVerifier", "FAIL", "focused verifier infrastructure", { error: error instanceof Error ? error.message : String(error) });
}
finally {
if (engine && handle) engine._web_engine_destroy(handle);
}
const failed = fixes.filter((item) => item.status !== "PASS");
const report = {
schemaVersion: 1,
planId: smokeReport.planId,
operation: "ONE_PASS_INTERNAL_SUBMODULE_SMOKE_FOCUSED_FIX_VERIFICATION",
policy: "verify only the initial smoke failures; never rerun the complete submodule smoke set",
status: failed.length === 0 && fixes.length === expected.length ? "PASS" : "FAIL",
artifact: {
wasmSha256: sha256(wasmPath),
loaderSha256: sha256(loaderPath),
fixtureSha256: sha256(fixturePath),
},
summary: { fixes: fixes.length, passed: fixes.length - failed.length, failed: failed.length, verificationAttempts: fixes.length, retries: 0 },
fixes,
};
fs.writeFileSync(fixReportPath, `${JSON.stringify(report, null, 2)}\n`);
process.stdout.write(`wbv2-submodule-focused-fixes-${report.status.toLowerCase()} fixes=${fixes.length} passed=${report.summary.passed} failed=${failed.length} retries=0\n`);
process.exit(report.status === "PASS" ? 0 : 1);