Files
workinf_Blender_Wasm/tools/web/run-wbv2-submodule-smoke.mjs
mes123456 b940b8bac8
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
Complete internal submodule smoke closure
2026-09-07 12:58:28 +08:00

148 lines
8.9 KiB
JavaScript

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);