import assert from "node:assert/strict"; import { createHash } from "node:crypto"; import { readFile } from "node:fs/promises"; import createModule from "../../build-wasm/linuxcnc-sim-core.js"; import { compareEndpoints, createEndpoint, endpointHash } from "./endpoint-replay.mjs"; import { SimSession, CONTEXT_ID } from "./sim-session.mjs"; const ROOT = new URL("../../", import.meta.url); const SOURCE_REPORTS = [ "coverage/linuxcnc-source-test-s5.report.json", "coverage/linuxcnc-source-test-execution-s5.report.json", "coverage/linuxcnc-source-unit-execution-s5.report.json", "coverage/linuxcnc-source-interp-s5.report.json", "coverage/linuxcnc-source-hal-s5.report.json", "coverage/linuxcnc-source-io-s5.report.json", "coverage/linuxcnc-source-config-s5.report.json", ]; async function loadReports() { const reports = []; for (const path of SOURCE_REPORTS) { const bytes = await readFile(new URL(path, ROOT)); const report = JSON.parse(bytes); assert.equal(report.status, "verified", path); reports.push({ path, task_id: report.task_id, schema: report.schema, sha256: createHash("sha256").update(bytes).digest("hex") }); } return reports; } async function sessionWithPlan(id, fast = false) { const wasmBinary = await readFile(new URL("../../build-wasm/linuxcnc-sim-core.wasm", import.meta.url)); const core = await createModule({ wasmBinary }); const session = new SimSession(core, { sessionId: id }); const base = { schema_version: 1, context_id: CONTEXT_ID, session_id: id }; let sequence = 0; const send = (kind, operationId, payload = {}) => { sequence += 1; const response = session.accept({ ...base, kind, seq: sequence, operation_id: operationId, payload }); assert.notEqual(response.kind, "ERROR", `${id}:${kind}:${response.payload?.code ?? "error"}`); return response; }; send("HELLO", "hello"); send("SESSION_ATTACH", "attach"); send("LOAD_WORKSPACE", "workspace", { root_path: "/workspace" }); send("PROGRAM_LOAD", "program", { source: "G0 X1\nG1 X2\n", path: "parity.ngc" }); send("PROGRAM_RUN", "run"); if (fast) send("SIM_STEP", "fast-step", { cycles: 4 }); else for (let index = 0; index < 4; index += 1) send("SIM_STEP", `step-${index}`, { cycles: 1 }); return { session, send }; } const sourceReports = await loadReports(); const oneX = await sessionWithPlan("source-parity-1x", false); const fast = await sessionWithPlan("source-parity-fast", true); const oneEndpoint = createEndpoint({ session: oneX.session }); const fastEndpoint = createEndpoint({ session: fast.session }); const modeParity = compareEndpoints(oneEndpoint, fastEndpoint); assert.equal(modeParity.equal, true); const recovering = await sessionWithPlan("source-parity-recover", false); const checkpoint = recovering.send("CHECKPOINT", "checkpoint").payload.checkpoint; recovering.send("SIM_STEP", "post-checkpoint", { cycles: 2 }); const continuousEndpoint = createEndpoint({ session: recovering.session }); const recovery = recovering.session.restoreCheckpoint(checkpoint, recovering.session.timeline.export(), (operation) => operation); assert.equal(recovery.status, "recovered"); const recoveredEndpoint = createEndpoint({ session: recovering.session }); const recoveryParity = compareEndpoints(continuousEndpoint, recoveredEndpoint); assert.equal(recoveryParity.equal, true); const trace = recovering.session.exportTrace(); const traceCore = await createModule({ wasmBinary: await readFile(new URL("../../build-wasm/linuxcnc-sim-core.wasm", import.meta.url)) }); const traceSession = new SimSession(traceCore, { sessionId: "source-parity-recover" }); const imported = traceSession.importTrace(trace); assert.equal(imported.trace_hash, trace.trace_hash); const traceParity = compareEndpoints(recoveredEndpoint, createEndpoint({ session: traceSession })); assert.equal(traceParity.equal, true); const output = { schema: "linuxcnc-source-parity-s5.v1", context_id: CONTEXT_ID, source_reports: sourceReports, modes: { one_x: endpointHash(oneEndpoint), fast: endpointHash(fastEndpoint) }, differences: { first_divergence: null, mode: modeParity, recovery: recoveryParity, trace_import: traceParity }, recovery: { checkpoint_hash: checkpoint.checkpoint_hash, continuous_hash: endpointHash(continuousEndpoint), recovered_hash: endpointHash(recoveredEndpoint), trace_hash: trace.trace_hash, status: recovery.status }, status: "verified", }; console.log(JSON.stringify(output));