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

144 lines
6.9 KiB
JavaScript

#!/usr/bin/env node
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { pathToFileURL } from "node:url";
const facadePath = process.argv[2];
const buildDirectory = path.resolve(process.argv[3] || "freecad-source/build-web/cloud-configure-r2/bin");
if (!facadePath) throw new Error("compiled cloud-facade module path is required");
const facade = await import(facadePath);
const envelope = await import(path.resolve(path.dirname(facadePath), "facade-envelope.js"));
const {
CLOUD_ACTION_SCHEMA,
CLOUD_COMMAND,
CLOUD_FIXTURE,
createCloudRequest,
handleCloudConfigure,
isCloudRequest,
} = facade;
const { FacadeSerialQueue, createCancel, isFacadeResponse } = envelope;
globalThis.__dirname = `${buildDirectory}${path.sep}`;
globalThis.require = (await import("node:module")).createRequire(import.meta.url);
const loaderUrl = pathToFileURL(path.join(buildDirectory, "FreeCADWebCloudKernelProbe.js")).href;
const loader = await import(loaderUrl);
const kernel = await loader.default({
wasmBinary: fs.readFileSync(path.join(buildDirectory, "FreeCADWebCloudKernelProbe.wasm")),
});
const root = path.resolve(path.dirname(new URL(import.meta.url).pathname), "..");
const outputPath = path.join(root, "docs", "p6", "cloud-facade.json");
const schemaPath = path.join(root, "docs", "p6", "cloud-facade.schema.json");
const logPath = path.join(root, "docs", "freecad-p6-cloud-e-worker.log");
const queue = new FacadeSerialQueue();
const trace = [];
let active = 0;
let maxActive = 0;
async function dispatch(request) {
trace.push({ direction: "request", requestId: request.requestId, command: request.command });
const response = await queue.dispatch(request, async (payload, signal) => {
active += 1;
maxActive = Math.max(maxActive, active);
trace.push({ direction: "handler-start", requestId: request.requestId });
try {
return await handleCloudConfigure(payload, signal, kernel);
} finally {
active -= 1;
trace.push({ direction: "handler-end", requestId: request.requestId });
}
});
trace.push({ direction: "response", requestId: response.requestId, kind: response.kind, code: response.kind === "error" ? response.error.code : null });
return response;
}
const success = await dispatch(createCloudRequest("cloud-success"));
const expectedError = await dispatch(createCloudRequest("cloud-error", { ...CLOUD_FIXTURE, url: "" }));
const duplicate = await dispatch(createCloudRequest("cloud-success"));
const cancelRequest = createCloudRequest("cloud-cancel");
const cancelPromise = dispatch(cancelRequest);
trace.push({ direction: "cancel", requestId: "cloud-cancel-message", cancelOf: cancelRequest.requestId });
const cancelAccepted = queue.cancel(createCancel("cloud-cancel-message", cancelRequest.requestId, "cloud-config"));
const cancelled = await cancelPromise;
const afterCancel = await dispatch(createCloudRequest("cloud-after-cancel"));
assert.equal(CLOUD_COMMAND, "cloud.configureRecompute");
assert.equal(isCloudRequest(createCloudRequest("schema-test")), true);
assert.equal(isCloudRequest({ ...createCloudRequest("schema-test"), command: "other" }), false);
assert.equal(success.kind, "result");
assert.equal(success.result.schema, CLOUD_ACTION_SCHEMA);
assert.equal(success.result.configuration.setterCount, 6);
assert.equal(success.result.recompute.count, 1);
assert.equal(success.result.recompute.hasError, 0);
assert.equal(success.result.recompute.summary, Object.values(CLOUD_FIXTURE).slice(2).join("|"));
assert.equal(expectedError.kind, "error");
assert.equal(expectedError.error.code, "invalid_argument");
assert.equal(expectedError.error.diagnostic.field, "url");
assert.equal(duplicate.kind, "error");
assert.equal(duplicate.error.code, "duplicate_request");
assert.equal(cancelAccepted, true);
assert.equal(cancelled.kind, "error");
assert.equal(cancelled.error.code, "cancelled");
assert.equal(afterCancel.kind, "result");
assert.deepEqual(afterCancel.result, success.result);
assert.equal(maxActive, 1);
assert.equal(active, 0);
for (const response of [success, expectedError, duplicate, cancelled, afterCancel]) assert.equal(isFacadeResponse(response), true);
const schema = JSON.parse(fs.readFileSync(schemaPath, "utf8"));
assert.equal(schema.$id, "https://freecad.org/schemas/freecad-web-cloud-configure-recompute-v1.json");
assert.equal(schema.$defs.request.properties.command.const, CLOUD_COMMAND);
assert.equal(schema.$defs.typedResult.properties.schema.const, CLOUD_ACTION_SCHEMA);
assert.equal(schema.$defs.payload.additionalProperties, false);
const inputSha256 = crypto.createHash("sha256").update(JSON.stringify(CLOUD_FIXTURE)).digest("hex");
const resultSha256 = crypto.createHash("sha256").update(JSON.stringify(success.result)).digest("hex");
const report = {
schema: "freecad-web-p6-cloud-facade/v1",
task_id: "FW-P6-CLD-E",
module: "Cloud",
action: CLOUD_COMMAND,
status: "pass",
source_revision: "b5a086fda59b0325fe0996147e6e9b344414f9bc",
input_sha256: inputSha256,
result_sha256: resultSha256,
responses: { success, expectedError, duplicate, cancelled, afterCancel },
trace,
sequencing: { maxActive, activeAfter: active, requestCount: 5, responseCount: 5, cancelAccepted },
state: {
beforeCancel: success.result.recompute.summary,
afterCancel: afterCancel.result.recompute.summary,
unchangedAfterCancel: success.result.recompute.summary === afterCancel.result.recompute.summary,
},
schemaChecks: {
request: true,
result: true,
expectedError: true,
duplicate: true,
cancel: true,
noNativePointers: !JSON.stringify({ responses: { success, expectedError, duplicate, cancelled, afterCancel }, trace }).match(/pointer|native/i),
},
commands: [
"npx tsc --target ES2022 --module ESNext --moduleResolution Bundler --strict --skipLibCheck --outDir /tmp/freecad-p6-cloud-facade --declaration false --noEmit false src/facade-envelope.ts src/cloud-facade.ts",
"node tools/run_p6_cloud_facade_smoke.mjs /tmp/freecad-p6-cloud-facade/cloud-facade.js freecad-source/build-web/cloud-configure-r2/bin"
],
artifacts: {
schema: path.relative(root, schemaPath),
workerLog: path.relative(root, logPath),
wasm: "freecad-source/build-web/cloud-configure-r2/bin/FreeCADWebCloudKernelProbe.wasm",
},
boundaries: [
"TypeScript validates and sequences envelope data; all Cloud configuration and recompute result fields come from the WebAssembly C ABI.",
"The Facade and Worker never expose a native pointer.",
"Only the locked configure/recompute action is included."
],
generated_at: new Date().toISOString(),
};
fs.writeFileSync(outputPath, `${JSON.stringify(report, null, 2)}\n`);
fs.writeFileSync(logPath, `${trace.map((event, index) => `${String(index + 1).padStart(2, "0")} ${JSON.stringify(event)}`).join("\n")}\nresult=pass\nmaxActive=${maxActive}\n`);
console.log(JSON.stringify({ result: report.status, requests: report.sequencing.requestCount, responses: report.sequencing.responseCount, maxActive }));