Files
linuxcncWebWASM/web/worker/test_xyzac_pause_resume_consistency.mjs
wangdequan bade560372 Align AXIS control flow and fix pause and program rendering
Document source UI gates, explicit native live-plot clearing, and remaining parity boundaries. Fix pause/resume transport continuity, repeated stepping, coordinate previews, and spindle override state. Optimize large program snapshots and rendering, and cover the control flows with native Tcl, WASM, and browser checks.

Validation: 22 native tests and source/browser checks pass. Worker/App regression: 113 of 123 pass; 10 pre-existing failures remain documented with no new failures.
2026-09-08 10:46:27 +08:00

72 lines
5.0 KiB
JavaScript

import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import createModule from "../../build-wasm/linuxcnc-sim-core.js";
import { XyzacSession, CONTEXT_ID } from "./xyzac-session.mjs";
import { loadSimulationCase, toWorkerConfiguration } from "../workspace/sim-case-loader.mjs";
const bundle = await loadSimulationCase({ path: "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini", fetchImpl: async (url) => {
try { const body = await readFile(new URL(`../../${String(url).replace(/^\//, "")}`, import.meta.url), "utf8"); return { ok: true, text: async () => body }; }
catch { return { ok: false, text: async () => "" }; }
} });
const core = await createModule({ wasmBinary: await readFile(new URL("../../build-wasm/linuxcnc-sim-core.wasm", import.meta.url)) });
const session = new XyzacSession(core, { sessionId: "pause-consistency" });
let seq = 0;
const send = (kind, payload = {}, accepted = true) => {
const result = session.accept({ schema_version: 1, context_id: CONTEXT_ID, session_id: session.sessionId, kind, payload, seq: ++seq, operation_id: `pause-consistency-${seq}` });
assert.equal(result.kind === "ERROR", !accepted, `${kind}: ${result.payload.gate_reason ?? result.payload.message_key ?? result.payload.code}`);
assert.equal(core._sim_protocol_last_seq(), seq, `${kind} stranded the transport`);
assert.equal(session.timeline.events.cursor, seq, `${kind} stranded the event log`);
if (!accepted) assert.equal(result.payload.code, "axis_gate");
return result.payload;
};
const motion = () => session.wasmMotion.read().state;
const snapshot = () => session.xyzacProgram.snapshot();
const sample = (cycles = 5) => send("SIM_STEP", { cycles, response: "motion_delta" });
send("HELLO"); send("SESSION_ATTACH"); send("LOAD_WORKSPACE", { root_path: "/workspace", configuration: toWorkerConfiguration(bundle) });
send("PROGRAM_LOAD", { path: "pause-reading.ngc", source: "G21 G90\n" + Array.from({ length: 80 }, (_, i) => `G1 X${i % 2 ? 0 : 2} F600`).join("\n") + "\nM2\n" });
send("POWER", { active: true }); send("HOME", { all: true }); send("PROGRAM_RUN"); sample();
for (let round = 0; round < 3; round++) {
send("PROGRAM_PAUSE");
const pose = motion().position, line = snapshot().current_line, queue = motion().queue_depth;
const epoch = session.livePathEpoch;
sample(); assert.deepEqual(motion().position, pose);
send("VISUAL_CLEAR"); assert.equal(session.livePathEpoch, epoch + 1);
for (const [kind, payload] of [["SET_SPINDLE_OVERRIDE", { scale: 1 }], ["SET_MAX_VELOCITY", { percent: 100 }], ["SET_JOG_SCALE", { percent: 50 }], ["SET_OVERRIDE", { feed: 1 }]]) send(kind, payload);
for (const [kind, payload] of [["PROGRAM_PAUSE", {}], ["PROGRAM_RUN", {}], ["PROGRAM_LOAD", { path: "bad.ngc", source: "M2\n" }], ["MDI_EXECUTE", { block: "G0 X100" }], ["HOME", { all: true }], ["JOG_START", { axis: "x", direction: 1, increment: 1 }]]) send(kind, payload, false);
assert.equal(snapshot().current_line, line);
assert.equal(snapshot().program_status, "paused");
assert.deepEqual(motion().position, pose);
assert.equal(motion().queue_depth, queue);
send("PROGRAM_RESUME");
for (let n = 0; n < 3; n++) sample();
assert.notDeepEqual(motion().position, pose, "Resume did not advance the preserved TP");
send("PROGRAM_RESUME", {}, false);
sample();
}
send("PROGRAM_PAUSE");
const beforeStep = motion().position;
send("PROGRAM_STEP");
for (let tick = 0; tick < 300 && session.wasmMotion.isStepping(); tick++) sample(1);
assert.notDeepEqual(motion().position, beforeStep, "Step while paused did not move");
assert.equal(snapshot().program_status, "paused");
send("VISUAL_CLEAR"); send("PROGRAM_RESUME"); sample(); send("PROGRAM_ABORT");
assert.equal(motion().queue_depth, 0);
send("PROGRAM_LOAD", { path: "pause-waiting.ngc", source: "G21 G90\nG1 X30 F600\nM2\n" });
send("PROGRAM_RUN");
for (let tick = 0; tick < 30 && snapshot().status !== 4; tick++) sample(1);
assert.equal(snapshot().status, 4); assert.ok(motion().queue_depth > 0);
send("PROGRAM_PAUSE"); send("VISUAL_CLEAR"); send("PROGRAM_RESUME");
assert.equal(snapshot().status, 4, "WAITING resume must not rewind or re-read");
for (let tick = 0; tick < 1000 && motion().queue_depth; tick++) sample();
assert.ok(Math.abs(motion().position.x - 30) < 1e-8);
send("PROGRAM_LOAD", { path: "pause-mdi.ngc", source: "G21 G90\nM2\n" });
send("MDI_EXECUTE", { block: "G1 X20 F600" }); sample();
send("PROGRAM_PAUSE");
const mdiPose = motion().position;
send("SET_SPINDLE_OVERRIDE", { scale: .8 }); send("VISUAL_CLEAR"); send("PROGRAM_RESUME"); sample();
assert.notDeepEqual(motion().position, mdiPose);
assert.equal(session.task.state.mode, "MDI");
send("PROGRAM_ABORT");
console.log(JSON.stringify({ schema: "xyzac-pause-consistency.v1", status: "verified", commands: seq, transport_seq: core._sim_protocol_last_seq(), checks: ["reading_repeated_pause", "clear_and_overrides_while_paused", "rejected_commands_keep_sequence", "paused_step_then_resume", "waiting_resume_to_completion", "mdi_pause_resume", "abort_after_resume"] }));