83 lines
4.2 KiB
JavaScript
83 lines
4.2 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFile } from "node:fs/promises";
|
|
import createModule from "../../build-wasm/linuxcnc-sim-core.js";
|
|
import { ProgramRunner } from "./program-runner.mjs";
|
|
import { SimSession, CONTEXT_ID } from "./sim-session.mjs";
|
|
import { compareEndpoints, createEndpoint } from "./endpoint-replay.mjs";
|
|
|
|
const sourcePath = "linuxcnc/tests/interp/do-while-break/test.ngc";
|
|
const source = await readFile(new URL(`../../${sourcePath}`, import.meta.url), "utf8");
|
|
const wasmBinary = await readFile(new URL("../../build-wasm/linuxcnc-sim-core.wasm", import.meta.url));
|
|
|
|
async function complete(core, pauseAt = null) {
|
|
const runner = new ProgramRunner(core);
|
|
assert.equal(runner.load(source, sourcePath).status, "verified");
|
|
assert.equal(runner.start().status, "verified");
|
|
let steps = 0;
|
|
while (runner.status === "running" && steps < 1000) {
|
|
runner.step();
|
|
steps += 1;
|
|
if (pauseAt !== null && steps === pauseAt) {
|
|
assert.equal(runner.pause().status, "verified");
|
|
const paused = runner.snapshot();
|
|
assert.equal(runner.resume().status, "verified");
|
|
assert.equal(paused.program_status, "paused");
|
|
}
|
|
}
|
|
assert.equal(runner.status, "completed");
|
|
assert.ok(steps < 1000);
|
|
assert.equal(runner.namedParameters.get("one_pass"), 1);
|
|
assert.equal(runner.namedParameters.get("two_pass"), 1);
|
|
assert.equal(runner.namedParameters.get("three_pass"), 1);
|
|
const snapshot = runner.snapshot();
|
|
runner.destroy();
|
|
return { snapshot, steps };
|
|
}
|
|
|
|
const core = await createModule({ wasmBinary });
|
|
const normal = await complete(core);
|
|
const paused = await complete(core, 5);
|
|
assert.equal(normal.snapshot.interpreter_state_hash, paused.snapshot.interpreter_state_hash);
|
|
|
|
const malformed = new ProgramRunner(core);
|
|
assert.equal(malformed.load("O100 ENDDO\nM2\n", "malformed.ngc").code, "program_enddo_without_do");
|
|
malformed.destroy();
|
|
const tooDeep = new ProgramRunner(core);
|
|
const nested = `${Array.from({ length: 33 }, (_, index) => `O${index + 1} DO`).join("\n")}\nM2\n${Array.from({ length: 33 }, (_, index) => `O${33 - index} WHILE [1]`).join("\n")}`;
|
|
assert.equal(tooDeep.load(nested, "depth.ngc").code, "program_control_depth");
|
|
tooDeep.destroy();
|
|
|
|
const sessionCore = await createModule({ wasmBinary });
|
|
const session = new SimSession(sessionCore, { sessionId: "s7-do-recovery" });
|
|
const base = { schema_version: 1, context_id: CONTEXT_ID, session_id: "s7-do-recovery" };
|
|
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", `${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, path: sourcePath }); send("PROGRAM_RUN", "run");
|
|
send("SIM_STEP", "step-a", { cycles: 5 });
|
|
const checkpoint = session.createCheckpoint();
|
|
send("SIM_STEP", "step-b", { cycles: 10 });
|
|
const continuous = createEndpoint({ session });
|
|
assert.equal(session.restoreCheckpoint(checkpoint, session.timeline.export(), (operation) => operation).status, "recovered");
|
|
const recovered = createEndpoint({ session });
|
|
const parity = compareEndpoints(continuous, recovered);
|
|
assert.equal(parity.equal, true, JSON.stringify(parity));
|
|
|
|
console.log(JSON.stringify({
|
|
schema: "linuxcnc-source-do-s7-01-sample.v1",
|
|
status: "verified",
|
|
context_id: CONTEXT_ID,
|
|
source: { path: sourcePath, source_hash: normal.snapshot.source_hash },
|
|
normal: { status: normal.snapshot.program_status, steps: normal.steps, named_parameters: { one_pass: 1, two_pass: 1, three_pass: 1 }, interpreter_state_hash: normal.snapshot.interpreter_state_hash },
|
|
pause_resume: { status: paused.snapshot.program_status, steps: paused.steps, hash_equal: normal.snapshot.interpreter_state_hash === paused.snapshot.interpreter_state_hash },
|
|
errors: { malformed_enddo: "program_enddo_without_do", depth: "program_control_depth" },
|
|
recovery: { equal: parity.equal, difference: parity.difference, checkpoint_hash: checkpoint.checkpoint_hash },
|
|
boundaries: ["LinuxCNC canonical expected output remains SOURCE_BLOCKED", "G64/G61 remains S7 follow-up", "host/Python/real hardware paths unchanged"],
|
|
}));
|