73 lines
4.9 KiB
JavaScript
73 lines
4.9 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 { fnv1a64 } from "./replay.mjs";
|
|
|
|
const wasmBinary = await readFile(new URL("../../build-wasm/linuxcnc-sim-core.wasm", import.meta.url));
|
|
|
|
function base(sessionId) {
|
|
return { schema_version: 1, context_id: CONTEXT_ID, session_id: sessionId };
|
|
}
|
|
|
|
async function createSession(sessionId) {
|
|
const core = await createModule({ wasmBinary });
|
|
const session = new SimSession(core, { sessionId });
|
|
const prefix = base(sessionId);
|
|
assert.equal(session.accept({ ...prefix, kind: "HELLO", seq: 1, payload: {} }).kind, "CAPABILITIES");
|
|
const ready = session.accept({ ...prefix, kind: "SESSION_ATTACH", seq: 2, operation_id: `${sessionId}-attach`, payload: {} });
|
|
assert.equal(ready.kind, "SESSION_READY");
|
|
assert.equal(ready.payload.task.schema, "sim_task.v1");
|
|
assert.equal(ready.payload.task.state, "READY");
|
|
assert.equal(ready.payload.task.seq, 2);
|
|
return { core, session, prefix };
|
|
}
|
|
|
|
const first = await createSession("task-a");
|
|
const { session, prefix } = first;
|
|
let response = session.accept({ ...prefix, kind: "LOAD_WORKSPACE", seq: 3, operation_id: "load", payload: { root_path: "/workspace" } });
|
|
assert.equal(response.payload.task.state, "READY");
|
|
response = session.accept({ ...prefix, kind: "PROGRAM_LOAD", seq: 4, operation_id: "program-load", payload: { path: "task.ngc", source: "G21 G90\nG1 X2 F60\nM2\n" } });
|
|
assert.equal(response.payload.task.program_status, "loaded");
|
|
response = session.accept({ ...prefix, kind: "PROGRAM_RUN", seq: 5, operation_id: "program-run", payload: {} });
|
|
assert.equal(response.payload.task.state, "RUNNING");
|
|
response = session.accept({ ...prefix, kind: "SIM_STEP", seq: 6, operation_id: "tick-1", payload: { cycles: 1 } });
|
|
assert.equal(response.payload.task.simulation_tick, 1);
|
|
const firstTickHash = response.payload.task.state_hash;
|
|
response = session.accept({ ...prefix, kind: "PROGRAM_PAUSE", seq: 7, operation_id: "pause", payload: {} });
|
|
assert.equal(response.payload.task.state, "PAUSED");
|
|
response = session.accept({ ...prefix, kind: "PROGRAM_RESUME", seq: 8, operation_id: "resume", payload: {} });
|
|
assert.equal(response.payload.task.state, "RUNNING");
|
|
response = session.accept({ ...prefix, kind: "PROGRAM_ABORT", seq: 9, operation_id: "abort", payload: {} });
|
|
assert.equal(response.payload.task.state, "STOPPED");
|
|
assert.equal(response.payload.task.program_status, "aborted");
|
|
|
|
const checkpointResponse = session.accept({ ...prefix, kind: "CHECKPOINT", seq: 10, operation_id: "checkpoint", payload: {} });
|
|
assert.equal(checkpointResponse.kind, "CHECKPOINT_READY");
|
|
assert.equal(checkpointResponse.payload.checkpoint.state.task.schema, "sim_task.v1");
|
|
const checkpoint = checkpointResponse.payload.checkpoint;
|
|
const recovered = session.restoreCheckpoint(checkpoint, null, (operation) => operation);
|
|
assert.equal(recovered.status, "recovered");
|
|
assert.equal(recovered.state.task.state, "STOPPED");
|
|
assert.equal(recovered.state.task.state_hash, checkpoint.state.task.state_hash);
|
|
|
|
const faultSession = await createSession("task-fault");
|
|
const faultBase = faultSession.prefix;
|
|
faultSession.session.accept({ ...faultBase, kind: "LOAD_WORKSPACE", seq: 3, operation_id: "fault-load", payload: { root_path: "/workspace" } });
|
|
faultSession.session.accept({ ...faultBase, kind: "PROGRAM_LOAD", seq: 4, operation_id: "fault-program", payload: { source: "G21\nG1 X1\n" } });
|
|
faultSession.session.accept({ ...faultBase, kind: "PROGRAM_RUN", seq: 5, operation_id: "fault-run", payload: {} });
|
|
const estop = faultSession.session.accept({ ...faultBase, kind: "ESTOP", seq: 6, operation_id: "estop", payload: { active: true } });
|
|
assert.equal(estop.payload.task.state, "ESTOP");
|
|
faultSession.session.accept({ ...faultBase, kind: "ESTOP", seq: 7, operation_id: "estop-clear", payload: { active: false } });
|
|
const recoveredIo = faultSession.session.accept({ ...faultBase, kind: "IO_RECOVER", seq: 8, operation_id: "io-recover", payload: {} });
|
|
assert.equal(recoveredIo.payload.task.state, "READY");
|
|
|
|
const second = await createSession("task-a");
|
|
second.session.accept({ ...second.prefix, kind: "LOAD_WORKSPACE", seq: 3, operation_id: "load", payload: { root_path: "/workspace" } });
|
|
second.session.accept({ ...second.prefix, kind: "PROGRAM_LOAD", seq: 4, operation_id: "program-load", payload: { path: "task.ngc", source: "G21 G90\nG1 X2 F60\nM2\n" } });
|
|
second.session.accept({ ...second.prefix, kind: "PROGRAM_RUN", seq: 5, operation_id: "program-run", payload: {} });
|
|
const secondTick = second.session.accept({ ...second.prefix, kind: "SIM_STEP", seq: 6, operation_id: "tick-1", payload: { cycles: 1 } });
|
|
assert.equal(secondTick.payload.task.state_hash, firstTickHash);
|
|
|
|
console.log(`sim_task=passed state=${recovered.state.task.state} tick_hash=${firstTickHash} recovery_hash=${recovered.state.task.state_hash}`);
|