96 lines
5.1 KiB
JavaScript
96 lines
5.1 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFile } from "node:fs/promises";
|
|
import createModule from "../../build-wasm/linuxcnc-sim-core.js";
|
|
import { SimSession, CONTEXT_ID } from "./sim-session.mjs";
|
|
import { compareEndpoints, createEndpoint, endpointHash, firstDifference } from "./endpoint-replay.mjs";
|
|
|
|
const wasmBinary = await readFile(new URL("../../build-wasm/linuxcnc-sim-core.wasm", import.meta.url));
|
|
|
|
async function sessionWithPlan(id, mode) {
|
|
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 seq = 0;
|
|
const send = (kind, operation_id, payload = {}) => {
|
|
seq += 1;
|
|
const response = session.accept({ ...base, kind, seq, operation_id, 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("HAL_COMMAND", "thread", { action: "thread", name: "servo-thread", period_ticks: 1 });
|
|
send("HAL_COMMAND", "component", { action: "component", instance: "gate", type: "and2", thread: "servo-thread" });
|
|
send("HAL_SET", "in0", { target: "pin", name: "gate.in0", value: 1 });
|
|
send("HAL_SET", "in1", { target: "pin", name: "gate.in1", value: 1 });
|
|
send("PLC_CONFIG", "plc", { rungs: [{ op: "SET", target: "ready", value: 1 }, { op: "ADD", target: "count", source: "ready" }] });
|
|
send("PROGRAM_LOAD", "program", { source: "G0 X1\nG1 X2\n", path: "replay.ngc" });
|
|
send("PROGRAM_RUN", "run");
|
|
if (mode === "single_step") {
|
|
send("PROGRAM_STEP", "program-step");
|
|
} else if (mode === "1x") {
|
|
for (let index = 0; index < 4; index += 1) send("SIM_STEP", `step-${index}`, { cycles: 1 });
|
|
} else {
|
|
send("SIM_STEP", "step-fast", { cycles: 4 });
|
|
}
|
|
return { session, send };
|
|
}
|
|
|
|
const oneX = await sessionWithPlan("replay-1x", "1x");
|
|
const fast = await sessionWithPlan("replay-fast", "fast");
|
|
const oneEndpoint = createEndpoint({ session: oneX.session });
|
|
const fastEndpoint = createEndpoint({ session: fast.session });
|
|
const modeParity = compareEndpoints(oneEndpoint, fastEndpoint);
|
|
assert.equal(modeParity.equal, true);
|
|
assert.equal(modeParity.difference, null);
|
|
|
|
const single = await sessionWithPlan("replay-single", "single_step");
|
|
const singleEndpoint = createEndpoint({ session: single.session });
|
|
assert.equal(singleEndpoint.task.program_status, "paused");
|
|
|
|
const replay = await sessionWithPlan("replay-duplicate", "1x");
|
|
const beforeDuplicate = createEndpoint({ session: replay.session });
|
|
const duplicateSeq = replay.session.timeline.events.cursor + 1;
|
|
const duplicate = replay.session.accept({ schema_version: 1, context_id: CONTEXT_ID, session_id: "replay-duplicate", seq: duplicateSeq, kind: "SIM_STEP", operation_id: "step-3", payload: { cycles: 1 } });
|
|
assert.equal(duplicate.replayed, true);
|
|
const afterDuplicate = createEndpoint({ session: replay.session });
|
|
const duplicateParity = compareEndpoints(beforeDuplicate, afterDuplicate);
|
|
assert.equal(duplicateParity.equal, true);
|
|
|
|
const recoveredSource = await sessionWithPlan("replay-recover", "1x");
|
|
const checkpointResponse = recoveredSource.send("CHECKPOINT", "checkpoint");
|
|
assert.equal(checkpointResponse.kind, "CHECKPOINT_READY");
|
|
const checkpoint = checkpointResponse.payload.checkpoint;
|
|
recoveredSource.send("SIM_STEP", "post-checkpoint", { cycles: 2 });
|
|
const continuousAfterCheckpoint = createEndpoint({ session: recoveredSource.session });
|
|
const recovery = recoveredSource.session.restoreCheckpoint(checkpoint, recoveredSource.session.timeline.export(), (operation) => operation);
|
|
assert.equal(recovery.status, "recovered");
|
|
const recoveredEndpoint = createEndpoint({ session: recoveredSource.session });
|
|
const recoveryParity = compareEndpoints(continuousAfterCheckpoint, recoveredEndpoint);
|
|
assert.equal(recoveryParity.equal, true);
|
|
|
|
const trace = recoveredSource.session.exportTrace();
|
|
const traceCore = await createModule({ wasmBinary });
|
|
const traceSession = new SimSession(traceCore, { sessionId: "replay-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 expected = { nested: [{ value: 1 }, { value: 2 }] };
|
|
const actual = { nested: [{ value: 1 }, { value: 3 }] };
|
|
assert.deepEqual(firstDifference(expected, actual), { path: "$.nested[1].value", expected: 2, actual: 3 });
|
|
|
|
const report = {
|
|
schema: "endpoint-replay-s4-sample.v1",
|
|
status: "verified",
|
|
context_id: CONTEXT_ID,
|
|
modes: { one_x: endpointHash(oneEndpoint), fast: endpointHash(fastEndpoint), single_step: endpointHash(singleEndpoint) },
|
|
parity: { mode_equal: modeParity, duplicate_operation_equal: duplicateParity, recovery_equal: recoveryParity, trace_equal: traceParity },
|
|
first_divergence: null,
|
|
checkpoint_hash: checkpoint.checkpoint_hash,
|
|
trace_hash: trace.trace_hash,
|
|
};
|
|
console.log(JSON.stringify(report));
|