Files
linuxcncWebWASM/web/worker/test_source_io_s5.mjs

132 lines
7.5 KiB
JavaScript

import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";
import createModule from "../../build-wasm/linuxcnc-sim-core.js";
import { DeviceIoRuntime } from "./device-io.mjs";
import { IoAckTracker } from "./io-ack.mjs";
import { MotionState } from "./motion-state.mjs";
import { SimSession, CONTEXT_ID } from "./sim-session.mjs";
import { fnv1a64 } from "./replay.mjs";
const ROOT = new URL("../../", import.meta.url);
const SESSION_ID = "source-io-s5";
async function read(path) { return readFile(new URL(path, ROOT)); }
async function fixture(paths) {
const files = [];
for (const path of paths) {
const value = await read(path);
files.push({ path, bytes: value.byteLength, sha256: createHash("sha256").update(value).digest("hex") });
}
return files;
}
function resultHash(value) { return fnv1a64(value); }
async function hardLimitResult() {
const motion = new MotionState();
assert.equal(motion.home().status, "verified");
assert.equal(motion.startJog({ axis: "x", direction: 1, speed: 10 }).status, "verified");
motion.step(1);
assert.equal(motion.state.axes.x, 0.01);
assert.equal(motion.setLimit("x", true).state.fault, "limit");
const result = { tick: motion.state.tick, axis: motion.state.axes.x, limit: motion.state.limits.x, fault: motion.state.fault, state_hash: motion.hash() };
assert.equal(result.limit, true);
return result;
}
async function limit3Result() {
const motion = new MotionState();
assert.equal(motion.home().status, "verified");
assert.equal(motion.startJog({ axis: "x", direction: 1, speed: 500 }).status, "verified");
motion.step(1);
assert.equal(motion.setFollowingError(0.02).state.fault, "following_error");
return { tick: motion.state.tick, axis: motion.state.axes.x, following_error: motion.state.following_error, fault: motion.state.fault, state_hash: motion.hash() };
}
async function toolAckResult() {
const tracker = new IoAckTracker();
assert.equal(tracker.issue("tool-change-7", "TOOL_CHANGE", 0, 2).status, "verified");
assert.equal(tracker.step(1).status, "verified");
const ack = tracker.ack("tool-change-7", 1);
assert.equal(ack.command.status, "acked");
assert.equal(ack.command.side_effect_count, 1);
return { command: ack.command, state_hash: tracker.hash() };
}
async function abortResult(source) {
const core = await createModule({ wasmBinary: await read("build-wasm/linuxcnc-sim-core.wasm") });
const session = new SimSession(core, { sessionId: SESSION_ID });
const base = { schema_version: 1, context_id: CONTEXT_ID, session_id: SESSION_ID };
const send = (kind, seq, operation_id, payload = {}) => session.accept({ ...base, kind, seq, operation_id, payload });
send("HELLO", 1); send("SESSION_ATTACH", 2, "attach"); send("LOAD_WORKSPACE", 3, "workspace", { root_path: "/workspace" });
const loaded = send("PROGRAM_LOAD", 4, "load", { source, path: "startup-abort.ngc" });
assert.equal(loaded.kind, "COMMAND_RESULT");
send("PROGRAM_RUN", 5, "run");
const aborted = send("PROGRAM_ABORT", 6, "abort");
assert.equal(aborted.payload.task.program_status, "aborted");
return { task_program_status: aborted.payload.task.program_status, task_hash: aborted.payload.task.state_hash, session_tick: session.tick };
}
async function main() {
const cases = [];
const add = async (id, adapter, paths, result, status = "PASSED", reason = null) => cases.push({ id, adapter, status, ...(reason ? { reason } : {}), fixture: await fixture(paths), result, result_hash: resultHash(result) });
await add("source-test:linuxcnc/tests/hard-limits/test.sh", "WORKER_SIM_ADAPTER", ["linuxcnc/tests/hard-limits/test.sh", "linuxcnc/tests/hard-limits/hard-limits.ini", "linuxcnc/tests/hard-limits/hard-limits.hal", "linuxcnc/tests/hard-limits/checkresult"], await hardLimitResult());
await add("source-test:linuxcnc/tests/limit3/limit-max-velocity/test.hal", "WORKER_SIM_ADAPTER", ["linuxcnc/tests/limit3/limit-max-velocity/test.hal", "linuxcnc/tests/limit3/limit-max-velocity/expected"], await limit3Result());
await add("source-test:linuxcnc/tests/toolchanger/m61/test.sh", "WORKER_SIM_ADAPTER", ["linuxcnc/tests/toolchanger/m61/test.sh", "linuxcnc/tests/toolchanger/m61/checkresult", "linuxcnc/tests/toolchanger/m61/m61-test.ini", "linuxcnc/tests/toolchanger/m61/mod_core_sim.hal", "linuxcnc/tests/toolchanger/m61/test-ui.py"], await toolAckResult());
for (const [id, subdir, expectedTool] of [
["source-test:linuxcnc/tests/io-startup/nonrandom/no-tool-in-p0/test.sh", "no-tool-in-P0", 0],
["source-test:linuxcnc/tests/io-startup/nonrandom/tool-in-p0/test.sh", "tool-in-P0", 0],
]) {
const expected = (await read(`linuxcnc/tests/io-startup/nonrandom/${subdir}/expected-startup-tool-number`)).toString().trim();
assert.equal(Number(expected), expectedTool);
await add(id, "SOURCE_BLOCKED", [
`linuxcnc/tests/io-startup/nonrandom/${subdir}/expected-startup-tool-number`,
`linuxcnc/tests/io-startup/nonrandom/${subdir}/tool.tbl.original`,
"linuxcnc/tests/io-startup/nonrandom/test.ini",
"linuxcnc/tests/io-startup/shared-test.sh",
"linuxcnc/tests/io-startup/checkresult",
], { expected_startup_tool: Number(expected), reason: "startup tool table requires full Task/configuration runtime" }, "SOURCE_BLOCKED", "startup tool table requires full Task/configuration runtime");
}
const abortSource = (await read("linuxcnc/tests/motion-logger/startup-gcode-abort/nc_files/init.ngc")).toString("utf8");
await add("source-test:linuxcnc/tests/motion-logger/startup-gcode-abort/test.sh", "WORKER_TASK_MOTION_PENDING", [
"linuxcnc/tests/motion-logger/startup-gcode-abort/test.sh",
"linuxcnc/tests/motion-logger/startup-gcode-abort/test.ini",
"linuxcnc/tests/motion-logger/startup-gcode-abort/test-ui.py",
"linuxcnc/tests/motion-logger/startup-gcode-abort/nc_files/init.ngc",
"linuxcnc/tests/motion-logger/startup-gcode-abort/checkresult",
"linuxcnc/tests/motion-logger/startup-gcode-abort/expected.motion-logger.in",
], await abortResult(abortSource), "WORKER_TASK_MOTION_PENDING", "motion logger and startup abort endpoint require Task/motion adapter");
await add("source-test:linuxcnc/tests/classicladder/estop/test.sh", "HOST_EXCLUDED", [
"linuxcnc/tests/classicladder/estop/test.sh",
"linuxcnc/tests/classicladder/estop/classicladder-estop.ini",
"linuxcnc/tests/classicladder/estop/classicladder-estop.hal",
"linuxcnc/tests/classicladder/estop/classicladder-estop.clp",
"linuxcnc/tests/classicladder/estop/classicladder-estop-ui.py",
"linuxcnc/tests/classicladder/estop/checkresult",
], { reason: "ClassicLadder GUI/runtime is host excluded" }, "HOST_EXCLUDED", "ClassicLadder GUI/runtime is host excluded");
const counts = Object.fromEntries(["PASSED", "SOURCE_BLOCKED", "HOST_EXCLUDED", "WORKER_TASK_MOTION_PENDING"].map((status) => [status, cases.filter((item) => item.status === status).length]));
const output = {
schema: "linuxcnc-source-io-adapter.v1",
context_id: CONTEXT_ID,
cases,
counts,
fixture_file_count: new Set(cases.flatMap((item) => item.fixture.map((file) => file.path))).size,
boundaries: [
"startup tool table resolution remains SOURCE_BLOCKED until full Task/configuration startup is available",
"motion logger abort remains WORKER_TASK_MOTION_PENDING; no GUI or wall-clock result is promoted",
"ClassicLadder and realtime hardware paths remain HOST_EXCLUDED",
],
};
output.aggregate_hash = resultHash({ cases: output.cases, counts: output.counts });
console.log(JSON.stringify(output));
}
await main();