Implement AXIS parity UI and execution baseline
This commit is contained in:
82
web/worker/test_source_do_s7_01.mjs
Normal file
82
web/worker/test_source_do_s7_01.mjs
Normal file
@@ -0,0 +1,82 @@
|
||||
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"],
|
||||
}));
|
||||
Reference in New Issue
Block a user