Files
FreeCADWasmDev/tools/run_p6_robot_python_family_facade.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

85 lines
5.9 KiB
JavaScript

#!/usr/bin/env node
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { execFileSync } from "node:child_process";
import { Worker } from "node:worker_threads";
import { fileURLToPath, pathToFileURL } from "node:url";
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-robot-family-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/robot-python-family-facade.ts"], { cwd: root, stdio: "inherit" });
const facade = await import(pathToFileURL(path.join(out, "robot-python-family-facade.js")));
const envelope = await import(pathToFileURL(path.join(out, "facade-envelope.js")));
const worker = new Worker(path.join(root, "tools/robot-python-family-facade-smoke-worker.mjs"), { workerData: { buildDirectory: build, facadePath: pathToFileURL(path.join(out, "robot-python-family-facade.js")).href, envelopePath: pathToFileURL(path.join(out, "facade-envelope.js")).href } });
const receive = () => new Promise((resolve, reject) => { const onMessage = (value) => { worker.off("error", reject); resolve(value); }; worker.once("message", onMessage); worker.once("error", reject); });
const ready = await receive();
const records = [
{ apiId: "py:Robot:class:Robot6Axis", name: "Robot6Axis", family: "generic-object" },
{ apiId: "py:Robot:class:RobotWorkbench", name: "RobotWorkbench", family: "generic-object" },
{ apiId: "py:Robot:class:Trajectory", name: "Trajectory", family: "generic-object" },
{ apiId: "py:Robot:class:Waypoint", name: "Waypoint", family: "geometry-value" },
{ apiId: "py:Robot:method:RobotObject.getRobot", name: "RobotObject.getRobot", family: "generic-object" },
];
const successRequest = facade.createRobotPythonFamilyRequest("robot-family-success-1", records);
worker.postMessage(successRequest);
const success = await receive();
worker.postMessage(successRequest);
const duplicate = await receive();
const cancelRequest = facade.createRobotPythonFamilyRequest("robot-family-cancel-1", records);
const cancelledPromise = new Promise((resolve, reject) => { const onMessage = (value) => { if (value.requestId === cancelRequest.requestId) { worker.off("error", reject); resolve(value); } }; worker.on("message", onMessage); worker.once("error", reject); });
worker.postMessage(cancelRequest);
worker.postMessage(envelope.createCancel("robot-family-cancel-control", cancelRequest.requestId, "robot-python-family"));
const cancelled = await cancelledPromise;
const crossRequest = facade.createRobotPythonFamilyRequest("robot-family-cross-1", [{ ...records[0], apiId: "py:BIM:class:Robot6Axis" }]);
worker.postMessage(crossRequest);
const crossModule = await receive();
worker.postMessage({ kind: "reset", requestId: "robot-family-reset-1" });
const reset = await receive();
const afterResetRequest = facade.createRobotPythonFamilyRequest("robot-family-after-reset-1", records.slice(0, 1));
worker.postMessage(afterResetRequest);
const afterReset = await receive();
await worker.terminate();
assert.equal(ready.kind, "ready");
assert.equal(success.kind, "result");
assert.equal(success.result.schema, facade.ROBOT_PYTHON_FAMILY_SCHEMA);
assert.equal(success.result.module, "Robot");
assert.equal(success.result.tier, "tier-1-readonly");
assert.equal(success.result.records.length, 5);
assert.deepEqual(success.result.records.map((record) => record.status), ["exposed", "restricted", "exposed", "exposed", "exposed"]);
assert.equal(success.result.resourcesReleased, true);
assert.equal(duplicate.kind, "error");
assert.equal(duplicate.error.code, "duplicate_request");
assert.equal(cancelled.kind, "error");
assert.equal(cancelled.error.code, "cancelled");
assert.equal(crossModule.kind, "result");
assert.equal(crossModule.result.records[0].status, "not_implemented");
assert.equal(crossModule.result.records[0].code, "unmatched_source_record");
assert.deepEqual(reset, { kind: "reset", requestId: "robot-family-reset-1" });
assert.equal(afterReset.kind, "result");
assert.equal(afterReset.result.records[0].status, "exposed");
const trace = { ready, success, duplicate, cancelled, crossModule, reset, afterReset };
const traceSha256 = crypto.createHash("sha256").update(JSON.stringify(trace)).digest("hex");
const report = {
schema: "freecad-web-p6-robot-tier1-readonly-family-facade/v1",
taskId: "FW-P6-FPA-F1-ROBOT",
module: "Robot",
command: facade.ROBOT_PYTHON_FAMILY_COMMAND,
result: "pass",
envelopeSchema: envelope.FACADE_SCHEMA,
typedSchema: facade.ROBOT_PYTHON_FAMILY_SCHEMA,
trace,
traceSha256,
sequencing: { requestCount: 5, responseCount: 7, maxActive: 1, cancelAccepted: true, resetAccepted: true },
schemaChecks: { nativeSource: success.result.source === "freecad-source", resourcesReleased: success.result.resourcesReleased === true, terminalStatuses: success.result.records.every((record) => ["exposed", "restricted", "nonportable", "not_implemented"].includes(record.status)), noNativePointers: !JSON.stringify(trace).match(/pointer/i) },
artifacts: { native: "docs/p6/robot-tier1-readonly-family-runtime.json", worker: "web/src/robot-python.worker.ts", facade: "web/src/robot-python-family-facade.ts" },
};
fs.writeFileSync(path.join(root, "docs/p6/robot-tier1-readonly-family-facade.json"), `${JSON.stringify(report, null, 2)}\n`);
fs.writeFileSync(path.join(root, "docs/freecad-p6-robot-tier1-readonly-family-facade.log"), `${JSON.stringify({ result: report.result, traceSha256, cancel: cancelled.error.code, reset: reset.requestId })}\n`);
console.log(JSON.stringify({ result: report.result, traceSha256, cancel: cancelled.error.code, reset: reset.requestId }));