Files
FreeCADWasmDev/tools/run_p6_workbench_command_facade_smoke.mjs
wangdequan 9a7c4f0335 Save FreeCAD Web workspace sources and user validation evidence
IFC native binding remains incomplete; preserve accurate test results.
2026-09-15 22:01:58 +08:00

38 lines
5.5 KiB
JavaScript

#!/usr/bin/env node
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { createRequire } from "node:module";
import { execFileSync } from "node:child_process";
import { fileURLToPath, pathToFileURL } from "node:url";
import { Worker } from "node:worker_threads";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const build = path.resolve(process.argv[2] ?? path.join(root, "freecad-source/build-web/configure/bin"));
const out = fs.mkdtempSync(path.join(os.tmpdir(), "freecad-p6-workbench-facade-"));
execFileSync(path.join(root, "web/node_modules/.bin/tsc"), ["--target", "ES2022", "--module", "ESNext", "--moduleResolution", "Bundler", "--strict", "--skipLibCheck", "--outDir", out, "--declaration", "false", "--noEmit", "false", "web/src/facade-envelope.ts", "web/src/workbench-command-facade.ts"], { cwd: root, stdio: "inherit" });
const facade = await import(pathToFileURL(path.join(out, "workbench-command-facade.js")));
const envelope = await import(pathToFileURL(path.join(out, "facade-envelope.js")));
const matrix = JSON.parse(fs.readFileSync(path.join(root, "docs/p6/workbench-command-matrix.json"), "utf8"));
const workerFile = path.join(out, "worker.mjs");
fs.writeFileSync(workerFile, `import fs from 'node:fs'; import path from 'node:path'; import { createRequire } from 'node:module'; import { parentPort, workerData } from 'node:worker_threads'; import { pathToFileURL } from 'node:url'; const f=await import(workerData.f), e=await import(workerData.e); globalThis.__dirname=workerData.b+path.sep; globalThis.require=createRequire(import.meta.url); const l=await import(pathToFileURL(path.join(workerData.b,'FreeCADWebWorkbenchCommandProbe.js'))); const k=await l.default({wasmBinary:fs.readFileSync(path.join(workerData.b,'FreeCADWebWorkbenchCommandProbe.wasm')),locateFile:x=>path.join(workerData.b,x),print:()=>{},printErr:()=>{}}); let q=new e.FacadeSerialQueue(); parentPort.postMessage({kind:'ready'}); parentPort.on('message',async m=>{if(m.kind==='reset'){q=new e.FacadeSerialQueue();parentPort.postMessage({kind:'reset'});return} if(e.isFacadeCancel(m)){if(!q.cancel(m))parentPort.postMessage(e.createFailure(m.requestId,'cancel_rejected','request is not active'));return} if(f.isWorkbenchCommandRequest(m)){const source=m.requestId==='workbench-facade-cancel'?new Promise(r=>setTimeout(()=>r(k),25)):k;parentPort.postMessage(await f.dispatchWorkbenchCommands(q,m,source));return} parentPort.postMessage(e.createFailure('invalid','invalid_request','Workbench facade envelope invalid'));});`);
const worker = new Worker(workerFile, { workerData: { b: build, f: pathToFileURL(path.join(out, "workbench-command-facade.js")).href, e: pathToFileURL(path.join(out, "facade-envelope.js")).href } });
const recv = () => new Promise((resolve, reject) => { const onMessage = (value) => { worker.off("error", reject); resolve(value); }; worker.once("message", onMessage); worker.once("error", reject); });
const context = { documentId: "workbench-facade-document", selection: ["Part:desktop:Std_DlgMacroRecord"], propertyMode: "read-only" };
const records = matrix.records.slice(0, 24);
await recv();
const request = facade.createWorkbenchCommandRequest("workbench-facade-success", records, context);
worker.postMessage(request); const success = await recv();
worker.postMessage(request); const duplicate = await recv();
const invalid = facade.createWorkbenchCommandRequest("workbench-facade-invalid", records.map((record) => ({ ...record })), context); invalid.payload.records[0] = { ...invalid.payload.records[0], sourcePath: "" }; worker.postMessage(invalid); const expectedError = await recv();
const cancel = facade.createWorkbenchCommandRequest("workbench-facade-cancel", records.map((record) => ({ ...record })), context); worker.postMessage(cancel); worker.postMessage(envelope.createCancel("workbench-facade-cancel-control", cancel.requestId, cancel.documentId)); const cancelled = await recv();
await worker.terminate();
const trace = { success, duplicate, expectedError, cancelled };
const valid = success.kind === "result" && success.result.count === records.length && success.result.context.documentId === context.documentId && duplicate.kind === "error" && duplicate.error.code === "duplicate_request" && expectedError.kind === "error" && expectedError.error.code === "invalid_request" && cancelled.kind === "error" && cancelled.error.code === "cancelled";
const report = { schema: "freecad-web-p6-workbench-command-facade-smoke/v1", taskId: "FW-P6-FPA-G", result: valid ? "pass" : "fail", request: { recordCount: records.length, context, sha256: crypto.createHash("sha256").update(JSON.stringify(request)).digest("hex") }, trace, traceSha256: crypto.createHash("sha256").update(JSON.stringify(trace)).digest("hex"), sequencing: { responses: 4, maxActive: 1 }, boundaries: ["Facade validates source and command metadata before native dispatch; GUI-only records remain restricted dispositions."] };
fs.writeFileSync(path.join(root, "docs/p6/workbench-command-facade.json"), `${JSON.stringify(report, null, 2)}\n`);
fs.writeFileSync(path.join(root, "docs/freecad-p6-workbench-command-facade.log"), `${JSON.stringify({ result: report.result, records: records.length, duplicate: duplicate.error?.code, invalid: expectedError.error?.code, cancelled: cancelled.error?.code, traceSha256: report.traceSha256 })}\n`);
console.log(JSON.stringify({ result: report.result, records: records.length, duplicate: duplicate.error?.code, invalid: expectedError.error?.code, cancelled: cancelled.error?.code }));
if (!valid) process.exitCode = 1;