104 lines
5.4 KiB
JavaScript
104 lines
5.4 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 root = path.resolve(path.dirname(new URL(import.meta.url).pathname), "..");
|
|
const facadePath = process.argv[2];
|
|
if (!facadePath) throw new Error("compiled Sandbox facade path is required");
|
|
const facade = await import(pathToFileURL(path.resolve(facadePath)));
|
|
const envelope = await import(pathToFileURL(path.join(path.dirname(path.resolve(facadePath)), "facade-envelope.js")));
|
|
const build = path.resolve(process.argv[3] ?? path.join(root, "freecad-source/build-web/configure/bin"));
|
|
globalThis.__dirname = `${build}${path.sep}`;
|
|
globalThis.require = (await import("node:module")).createRequire(import.meta.url);
|
|
const loader = await import(pathToFileURL(path.join(build, "FreeCADWebSandboxRuntimeProbe.js")));
|
|
const kernel = await loader.default({ wasmBinary: fs.readFileSync(path.join(build, "FreeCADWebSandboxRuntimeProbe.wasm")) });
|
|
const queue = new envelope.FacadeSerialQueue();
|
|
const trace = [];
|
|
let active = 0;
|
|
let maxActive = 0;
|
|
|
|
async function dispatch(request, source = kernel) {
|
|
trace.push({ direction: "request", requestId: request.requestId });
|
|
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 facade.handleSandbox(payload, signal, source);
|
|
} 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(facade.createSandboxRequest("sdb-e-success"));
|
|
const expectedError = await dispatch(facade.createSandboxRequest("sdb-e-error", { ...facade.SANDBOX_FIXTURE, integer: 43 }));
|
|
const duplicate = await dispatch(facade.createSandboxRequest("sdb-e-success"));
|
|
const delayed = new Promise((resolve) => setTimeout(() => resolve(kernel), 0));
|
|
const cancelRequest = facade.createSandboxRequest("sdb-e-cancel");
|
|
const cancelPromise = dispatch(cancelRequest, delayed);
|
|
const cancelAccepted = queue.cancel(envelope.createCancel("sdb-e-cancel-message", cancelRequest.requestId, "sandbox-doc"));
|
|
const cancelled = await cancelPromise;
|
|
const afterCancel = await dispatch(facade.createSandboxRequest("sdb-e-after-cancel"));
|
|
|
|
assert.equal(success.kind, "result");
|
|
assert.equal(success.result.objectType, "Sandbox::SandboxObject");
|
|
assert.equal(success.result.objectInteger, 42);
|
|
assert.equal(success.result.objectCount, 1);
|
|
assert.equal(success.result.resourcesReleased, true);
|
|
assert.equal(expectedError.error.code, "invalid_fixture");
|
|
assert.equal(duplicate.error.code, "duplicate_request");
|
|
assert.equal(cancelAccepted, true);
|
|
assert.equal(cancelled.error.code, "cancelled");
|
|
assert.deepEqual(afterCancel.result, success.result);
|
|
assert.equal(maxActive, 1);
|
|
|
|
const responses = { success, expectedError, duplicate, cancelled, afterCancel };
|
|
const report = {
|
|
schema: "freecad-web-p6-sandbox-facade/v1",
|
|
taskId: "FW-P6-SDB-E",
|
|
module: "Sandbox",
|
|
action: facade.SANDBOX_COMMAND,
|
|
result: "pass",
|
|
envelopeSchema: "freecad-web-facade/v1",
|
|
typedSchema: facade.SANDBOX_SCHEMA,
|
|
fixture: {
|
|
...facade.SANDBOX_FIXTURE,
|
|
inputSha256: crypto.createHash("sha256").update(JSON.stringify(facade.SANDBOX_FIXTURE)).digest("hex"),
|
|
},
|
|
responses,
|
|
trace,
|
|
sequencing: { maxActive, requestCount: 5, responseCount: 5, cancelAccepted },
|
|
state: { unchangedAfterCancel: true },
|
|
schemaChecks: {
|
|
request: facade.isSandboxRequest(success.kind === "result" ? facade.createSandboxRequest("schema-check") : success),
|
|
result: success.kind === "result" && success.result.schema === facade.SANDBOX_SCHEMA,
|
|
expectedError: expectedError.kind === "error" && expectedError.error.code === "invalid_fixture",
|
|
duplicate: duplicate.kind === "error" && duplicate.error.code === "duplicate_request",
|
|
cancel: cancelled.kind === "error" && cancelled.error.code === "cancelled",
|
|
noNativePointers: !JSON.stringify(responses).match(/pointer|native|handle/i),
|
|
},
|
|
artifacts: {
|
|
kernel: "docs/p6/sandbox-kernel.json",
|
|
recovery: "docs/p6/sandbox-recovery.json",
|
|
workerLog: "docs/freecad-p6-sandbox-e-facade.log",
|
|
},
|
|
commands: [
|
|
"npx tsc --target ES2022 --module ESNext --moduleResolution Bundler --strict --skipLibCheck --outDir <tmp> --declaration false --noEmit false src/facade-envelope.ts src/sandbox-facade.ts",
|
|
"node tools/run_p6_sandbox_facade_smoke.mjs <tmp>/sandbox-facade.js freecad-source/build-web/configure/bin",
|
|
],
|
|
boundaries: [
|
|
"Only the Sandbox create/recompute action is exposed; GUI actions and full worker UI are out of scope.",
|
|
"The handler returns typed JSON fields only and never exposes a native pointer or document object.",
|
|
],
|
|
};
|
|
fs.writeFileSync(path.join(root, "docs/p6/sandbox-facade.json"), `${JSON.stringify(report, null, 2)}\n`);
|
|
fs.writeFileSync(path.join(root, "docs/freecad-p6-sandbox-e-facade.log"), `${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.result, maxActive, requests: 5, cancelAccepted }));
|