import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; const root = new URL("../", import.meta.url); const read = (path) => readFile(new URL(path, root), "utf8"); const [axisPy, axisTcl, taskSource, contract, app, worker, programCore, runtime] = await Promise.all([ read("linuxcnc/src/emc/usr_intf/axis/scripts/axis.py"), read("linuxcnc/share/axis/tcl/axis.tcl"), read("linuxcnc/src/emc/task/emctaskmain.cc"), read("docs/linuxcnc-axis-web-control-panel-source-logic.md"), read("web/react/src/App.tsx"), read("web/worker/sim-session.mjs"), read("wasm-core/src/sim_program.c"), read("web/worker/wasm-program-runtime.mjs"), ]); for (const symbol of ["manual_ok", "ensure_mode", "open_file_guts", "task_run", "task_step", "task_pause", "task_resume", "task_stop", "send_mdi_command", "home_all_joints", "home_joint", "touch_off_system", "touch_off_tool", "spindle_forward_toggle", "spindle_backward_toggle", "toggle_override_limits", "cycle_view"]) assert.match(axisPy, new RegExp(`(?:def|class) ${symbol}`), `missing source symbol ${symbol}`); for (const binding of ["machine_estop", "machine_power", "file_open", "reload", "program_run", "program_step", "program_pause", "program_stop", "program_blockdelete", "program_optpause", "view_zoomin", "view_zoomout", "clear_plot", "jogminus", "jogplus", "zerohome.home", "zerohome.zero", "zerohome.tooltouch", "spindlef.ccw", "spindlef.stop", "spindlef.cw"]) assert.match(axisTcl, new RegExp(`(?:toolbar\\.${binding}|manual\\.jogf|manual\\.spindlef|manual\\.jogf\\.zerohome)`), `missing source binding ${binding}`); for (const taskTerm of ["EMC_TASK_PLAN_RUN_TYPE", "emcTaskPlanOpen", "emcTaskPlanClose", "programStartLine = run_msg->line"]) assert.match(taskSource, new RegExp(taskTerm.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")), `missing task source ${taskTerm}`); for (const term of ["manual_ok", "ensure_mode", "open_file_guts", "EMC_TASK_PLAN_RUN", "PROGRAM_LOAD", "PROGRAM_RUN", "PROGRAM_STEP", "PROGRAM_PAUSE", "PROGRAM_RESUME", "PROGRAM_ABORT", "JOG_START", "HOME", "TOUCH_OFF", "Run after completion or Stop", "Toolpath display lifecycle", "live_path_epoch", "positionlogger", "blocked_by_abi_tool_offsets", "Button Dependency Matrix"]) assert.match(contract, new RegExp(term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")), `contract missing ${term}`); assert.match(app, /Begin executing current file \[R\]/); assert.match(app, /Reload current G-code \[Ctrl-R\]/); assert.match(app, /Rapid Override/); assert.doesNotMatch(app, /Reset machine/); assert.doesNotMatch(app, /Run Startup Sequence/); assert.doesNotMatch(app, /G92\s+\$\{selectedAxis/); assert.doesNotMatch(app, /cachedProgram/); assert.doesNotMatch(app, /program:\s*\{\s*path:\s*programPath/); assert.match(contract, /exactly one PROGRAM_LOAD for the configured OPEN_FILE/); assert.match(worker, /blocked_by_abi_tool_offsets/); assert.doesNotMatch(worker, /xyzac_program_aborted_requires_restart/); assert.match(worker, /reopened_taskfile/); assert.match(worker, /livePathEpoch/); assert.doesNotMatch(worker.match(/const visualClearResponse[\s\S]*?switch \(input\.kind\)/)?.[0] ?? "", /previewState\s*=\s*null/); assert.match(programCore, /SIM_PROGRAM_COMPLETED \|\| program->status == SIM_PROGRAM_ABORTED/); assert.match(programCore, /sim_program_set_position/); assert.match(runtime, /synchronizePosition/); assert.match(worker, /xyzac_motion_active/); console.log(JSON.stringify({ schema: "axis-control-source-contract.v1", status: "verified", source: "linuxcnc-axis-web-control-panel-source-logic.md", checked: { axis_py_symbols: 17, axis_tcl_bindings: 21, task_source_edges: 4, app_source_controls: 3, worker_abi_boundaries: 1, direct_rerun_edges: 2 } }));