import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import { spawnSync } from "node:child_process"; import { axisSourceUi } from "../web/worker/axis-source-ui.mjs"; const source = await readFile(new URL("../linuxcnc/share/axis/tcl/axis.tcl", import.meta.url), "utf8"); const start = source.indexOf("proc update_state {args} {"); const end = source.indexOf("\nproc set_mode_from_tab", start); assert.ok(start >= 0 && end > start); const formulas = source.slice(start, end); const scenarios = []; for (const task_state of ["STATE_ESTOP", "STATE_ESTOP_RESET", "STATE_ON"]) for (const interp_state of ["INTERP_IDLE", "INTERP_READING", "INTERP_PAUSED", "INTERP_WAITING"]) for (const taskfile of [false, true]) for (const queued_mdi_commands of [0, 10]) for (const motion_mode of ["FREE", "TELEOP"]) for (const kinematics_type of ["BOTH", "IDENTITY"]) for (const spindle_direction of [0, -1, 1]) scenarios.push({ task_state, interp_state, taskfile, queued_mdi_commands, motion_mode, kinematics_type, spindle_direction, tool: taskfile ? 1 : 0, paused: interp_state === "INTERP_PAUSED" }); const widgetMap = { power: ".toolbar.machine_power", load: ".toolbar.file_open", reload: ".toolbar.reload", run: ".toolbar.program_run", step: ".toolbar.program_step", pauseResume: ".toolbar.program_pause", abort: ".toolbar.program_stop", manual: "manualgroup", mdi: "mdigroup", spindleAdjust: ".manual.spindlef.spindleminus", touchOff: ".manual.jogf.zerohome.zero", toolTouchOff: ".manual.jogf.zerohome.tooltouch", overrideLimits: ".manual.jogf.override" }; const prelude = ` proc _ {x} { return $x } proc relief {condition args} {} proc state {condition args} { foreach widget $args { set ::result($widget) [uplevel #0 [list expr $condition]] } } proc enable_group {group} { set ::result($group) 1 } proc disable_group {group} { set ::result($group) 0 } proc set_mode_from_tab {} {} proc unknown {name args} { if {[lindex $args 0] eq "configure"} { set ::result($name) [expr {[lindex $args 2] eq "normal"}] }; return "" } foreach name {STATE_ESTOP STATE_ESTOP_RESET STATE_ON INTERP_IDLE INTERP_READING INTERP_PAUSED INTERP_WAITING TRAJ_MODE_FREE TRAJ_MODE_TELEOP KINEMATICS_IDENTITY KINEMATICS_BOTH TASK_MODE_AUTO} { set ::$name $name } set ::_tabs_manual .manual set ::manualgroup manualgroup set ::mdigroup mdigroup set ::has_editor 1 set ::has_ladder 0 set ::highlight_line -1 set ::task_mode MANUAL set ::coord_type 0 set ::display_type 0 set ::block_delete 0 set ::optional_stop 0 set ::last_interp_state INTERP_IDLE set ::last_task_state STATE_ON set ::on_any_limit 0 set ::max_queued_mdi_commands 10 ${formulas} `; const script = prelude + scenarios.map((s) => ` set ::task_state ${s.task_state} set ::interp_state ${s.interp_state} set ::taskfile ${s.taskfile ? "program.ngc" : '""'} set ::queued_mdi_commands ${s.queued_mdi_commands} set ::motion_mode TRAJ_MODE_${s.motion_mode} set ::kinematics_type KINEMATICS_${s.kinematics_type} set ::spindledir ${s.spindle_direction} set ::tool ${s.tool || '"No tool"'} set ::interp_pause ${Number(s.paused)} update_state puts [list ${Object.values(widgetMap).map((key) => `$::result(${key})`).join(" ")}] `).join("\n"); const result = spawnSync("tclsh", [], { input: script, encoding: "utf8", maxBuffer: 4 * 1024 * 1024 }); assert.equal(result.status, 0, result.stderr); assert.equal(result.stderr, ""); const rows = result.stdout.trim().split("\n"); assert.equal(rows.length, scenarios.length); for (const [index, row] of rows.entries()) { const expected = row.split(" ").map(Number); const actual = axisSourceUi(scenarios[index]).gates; Object.keys(widgetMap).forEach((name, column) => assert.equal(Number(actual[name].enabled), expected[column], `${name}: ${JSON.stringify(scenarios[index])}`)); } console.log(JSON.stringify({ schema: "axis-source-ui-oracle.v1", status: "verified", source: "axis.tcl::update_state", scenarios: scenarios.length, predicates: Object.keys(widgetMap).length, assertions: scenarios.length * Object.keys(widgetMap).length }));