diff --git a/wasm-port/docs/compatibility-validation.md b/wasm-port/docs/compatibility-validation.md index f9bf129..c4e1f1c 100644 --- a/wasm-port/docs/compatibility-validation.md +++ b/wasm-port/docs/compatibility-validation.md @@ -24,10 +24,14 @@ The native validation script runs these checks in order: vendored file exists, and every vendored file is byte-identical to upstream. 3. `tools/verify_no_standalone_cnc_semantics.sh` Confirms standalone code has not reintroduced `Interp::convert_g()`. -4. `tools/build_native_probes.sh` +4. `tools/verify_native_linuxcnc_fixture_baseline.sh` + Runs a narrow side-by-side fixture baseline through upstream + `../linuxcnc/bin/rs274` and compares normalized canonical events for + fixtures that do not require standalone-only runtime adapters. +5. `tools/build_native_probes.sh` Builds native source probes and standalone harnesses from vendored LinuxCNC source plus narrow runtime wrappers. -5. `tests/native/verify_native_probes.sh` +6. `tests/native/verify_native_probes.sh` Checks probe exit codes, source-probe coverage, harness stdout, canonical fixture events, and expected error behavior. @@ -51,6 +55,8 @@ The validation fails if: | `linuxcnc_namedparam_harness` | Validates LinuxCNC named parameter behavior, `_ini[...]`, and `_hal[...]` adapter resolution. | | `linuxcnc_interp_minimal_harness` | Runs G-code fixtures through vendored LinuxCNC parser/execution/conversion code and captures canonical events. | | `linuxcnc_parameter_file_harness` | Validates LinuxCNC parameter file restore/save behavior and required/read-only parameter handling. | +| `linuxcnc_interp_init_harness` | Validates vendored LinuxCNC `Interp::init()` emits canonical initialization boundaries through the standalone event sink. | +| `linuxcnc_indexer_harness` | Validates vendored LinuxCNC single-axis rotary indexer dispatch emits lock/unlock and motion boundaries through the standalone event sink. | | `linuxcnc_tp_api_probe` | Validates vendored LinuxCNC trajectory planner calls for linear, arc, and queued motion paths. | | `linuxcnc_kinematics_probe` | Validates vendored LinuxCNC `trivkins.c` plus `kins_util.c` initialize and perform identity forward/inverse mapping through the standalone HAL/RTAPI boundary. | | `linuxcnc_5axis_kinematics_probe` | Validates vendored LinuxCNC `5axiskins.c` through `switchkins.c`, including 5-axis forward/inverse round-trip behavior and switching to identity kinematics. | @@ -75,6 +81,11 @@ Positive G-code fixtures currently cover: - spindle orient - tool semantics - tool table setup +- tool-data reload boundary +- interpreter state-tag boundary +- percent-delimited file `FINISH` boundary +- file-open `ON_RESET` boundary +- comment logging canonical calls - named and numbered parameters - O-word subroutines - program-end modal reset @@ -96,9 +107,11 @@ Negative fixtures currently cover: Current validation is native-only. WASM, browser, SDK, OPFS, and full machine-session validation remain future work. -The current fixture expectations validate standalone behavior against the -vendored LinuxCNC source path. They do not yet run a side-by-side native -LinuxCNC executable comparison for each fixture. +The current fixture expectations validate standalone behavior against both the +vendored LinuxCNC source path and a first upstream `rs274` side-by-side +baseline for simple parser/conversion fixtures. Fixtures that depend on +standalone-only runtime adapters, HAL/INI/tool table setup, or richer machine +session state still need dedicated native LinuxCNC baselines. The kinematics probes currently cover LinuxCNC identity/trivial kinematics, the switchable `5axiskins` XYZBCW bridge-mill model, and the TRT `xyzac`/`xyzbc` diff --git a/wasm-port/docs/drift-report.md b/wasm-port/docs/drift-report.md index dac67d8..56f3ede 100644 --- a/wasm-port/docs/drift-report.md +++ b/wasm-port/docs/drift-report.md @@ -26,6 +26,13 @@ semantic rewrites: | HAL lookup | Standalone HAL adapter for `_hal[...]` named parameter reads. | | INI lookup | Standalone INI adapter around vendored LinuxCNC INI parser behavior. | | Canonical output | Canonical calls are captured as test events instead of driving hardware. | +| Tool-data reload | LinuxCNC `RELOAD_TOOLDATA` is captured as a canonical test event; browser/native tool-table reload plumbing remains a future host/runtime adapter. | +| Interpreter state tags | LinuxCNC `UPDATE_TAG` callbacks are captured from the state tags packed by vendored interpreter code; standalone code does not derive modal state. | +| Rotary indexer lock state | LinuxCNC `UNLOCK_ROTARY` and `LOCK_ROTARY` callbacks are captured from the vendored single-axis indexer path; standalone code does not implement rotary-indexing semantics. | +| File flush | LinuxCNC `FINISH` is captured as a canonical test event on the vendored `%`-delimited file-reading path. | +| Interpreter reset | LinuxCNC `ON_RESET` is captured as a canonical test event on the vendored interpreter reset/file-open path. | +| Interpreter init | LinuxCNC `INIT_CANON` and the canonical initialization sequence from vendored `Interp::init()` are captured as test events. | +| Comment logging | LinuxCNC `LOGOPEN`, `LOG`, `LOGAPPEND`, and `LOGCLOSE` callbacks are captured as canonical test events instead of writing host log files. | | Python/remap | Python/remap hooks are stubbed at the runtime edge. | | Dynamic interpreter path | `interp_base.cc` probe uses standalone `EMC2_HOME` compile-time path boundary. | | Realtime scheduler | TP probes seed deterministic status/config data instead of running LinuxCNC realtime process topology. | @@ -58,8 +65,10 @@ semantic rewrites: - Positive cutter-compensated motion and cutter compensation rejection paths now have standalone interpreter fixture coverage through vendored LinuxCNC source. -- Fixture expectations are currently checked against the standalone vendored - source path, not by running a native LinuxCNC binary for every fixture. +- Fixture expectations are checked against the standalone vendored source + path. A first upstream `rs274` side-by-side baseline now covers simple + parser/conversion fixtures; adapter-heavy fixtures still need dedicated + native LinuxCNC baselines. ## Current Drift Conclusion diff --git a/wasm-port/docs/porting-steps-standalone.md b/wasm-port/docs/porting-steps-standalone.md index a2dea1f..d8f5e47 100644 --- a/wasm-port/docs/porting-steps-standalone.md +++ b/wasm-port/docs/porting-steps-standalone.md @@ -456,6 +456,58 @@ Current verified progress: `tests/fixtures/gcode/tool_table_setup.ngc` and `tests/fixtures/canon/tool_table_setup.events` pin `G10 L1` tool offset, diameter, front/back angle, and orientation behavior. +- `runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp` now records + the tool-data reload boundary call emitted by vendored + `interp_convert.cc::convert_modal_0()`: `RELOAD_TOOLDATA`. + `tests/fixtures/gcode/tool_reload.ngc` and + `tests/fixtures/canon/tool_reload.events` pin `G10 L0` dispatch without + adding standalone tool-table reload semantics. +- `runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp` now records + interpreter state-tag boundary calls emitted by vendored LinuxCNC + `interp_write.cc::write_state_tag()` and + `interp_convert.cc::update_tag()`: `UPDATE_TAG`. + `tests/fixtures/gcode/state_tag_motion.ngc` and + `tests/fixtures/canon/state_tag_motion.events` pin straight-motion state tag + emission without deriving modal state in standalone code. +- `runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp` now records + rotary indexer lock/unlock boundary calls emitted by vendored + `interp_convert.cc::issue_straight_index()`: `UNLOCK_ROTARY` and + `LOCK_ROTARY`. A dedicated `linuxcnc_indexer_harness` pins the LinuxCNC + single-axis `G0 A...` indexer path, including the surrounding motion-control + boundary calls, without adding standalone rotary-indexing semantics. +- `runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp` now records + the canonical flush boundary emitted by vendored LinuxCNC file-reading + code: `FINISH`. `tests/fixtures/gcode/percent_file_finish.ngc` and + `tests/fixtures/canon/percent_file_finish.events` pin `%`-delimited file + handling from `rs274ngc_pre.cc::open()` and `interp_read.cc::read_text()`. + The native verification script treats this fixture as file-mode-only + because `%` is a LinuxCNC program-file delimiter, not a valid MDI command. +- `runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp` now records + the reset/queue-drop boundary emitted by vendored + `rs274ngc_pre.cc::reset()`: `ON_RESET`. + `tests/fixtures/gcode/file_open_reset.ngc` and + `tests/fixtures/canon/file_open_reset.events` pin the LinuxCNC file-open + reset path without adding standalone queue-management semantics. +- `runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp` now records + the interpreter initialization boundary emitted by vendored + `rs274ngc_pre.cc::init()`: `INIT_CANON`. A dedicated + `linuxcnc_interp_init_harness` calls vendored `Interp::init()` and pins the + LinuxCNC initialization sequence: `INIT_CANON`, `USE_LENGTH_UNITS`, + `SET_G5X_OFFSET`, `SET_G92_OFFSET`, `SET_XY_ROTATION`, and + `SET_FEED_REFERENCE`, without changing the minimal G-code fixture harness + startup path. +- `runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp` now records + comment logging canonical boundary calls emitted by vendored + `interp_convert.cc::convert_comment()`: `LOGOPEN`, `LOG`, `LOGAPPEND`, + and `LOGCLOSE`. `tests/fixtures/gcode/comment_logging.ngc` and + `tests/fixtures/canon/comment_logging.events` pin LinuxCNC comment logging + dispatch without adding standalone comment semantics. +- `tools/verify_native_linuxcnc_fixture_baseline.sh` now runs a first + side-by-side baseline against upstream `../linuxcnc/bin/rs274`. It normalizes + native LinuxCNC canonical output for simple parser/conversion fixtures + (`minimal_linear`, `length_units`, `plane_selection`, and + `modal_incremental`) and compares it with the standalone fixture events, + without introducing a project-authored CNC semantics oracle. - `tests/fixtures/gcode_errors/g1_zero_feed.ngc` and `tests/fixtures/canon_errors/g1_zero_feed.expected` pin the negative `G1` zero-feed case. The source basis is LinuxCNC diff --git a/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_indexer_harness.cpp b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_indexer_harness.cpp new file mode 100644 index 0000000..35644b1 --- /dev/null +++ b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_indexer_harness.cpp @@ -0,0 +1,51 @@ +#define private public +#include "emc/rs274ngc/rs274ngc_interp.hh" +#undef private + +#include +#include + +#include "canon_event_sink.hh" +#include "linuxcnc_hal_adapter.hh" +#include "linuxcnc_tool_adapter.hh" + +namespace { + +void initialize_indexer_interp(Interp &interp) +{ + standalone::reset_hal_adapter(); + standalone::reset_tool_adapter(); + + interp._setup.length_units = CANON_UNITS_MM; + interp._setup.distance_mode = DISTANCE_MODE::ABSOLUTE; + interp._setup.ijk_distance_mode = DISTANCE_MODE::ABSOLUTE; + interp._setup.feed_mode = FEED_MODE::UNITS_PER_MINUTE; + interp._setup.plane = CANON_PLANE::XY; + interp._setup.motion_mode = G_0; + interp._setup.percent_flag = false; + interp._setup.sequence_number = 1; + interp._setup.parameter_occurrence = 0; + interp._setup.num_spindles = 1; + interp._setup.a_indexer_jnum = 0; + interp._setup.parameters[5599] = 1.0; + std::memcpy(interp._readers, Interp::default_readers, sizeof(Interp::default_readers)); +} + +} // namespace + +int main() +{ + Interp interp; + standalone::reset_canon_events(); + initialize_indexer_interp(interp); + + const int rc = interp.execute("G0 A90\n"); + std::cout << "execute=" << rc << "\n"; + std::cout << "post_a=" << interp._setup.AA_current << "\n"; + std::cout << "canon_event_count=" << standalone::canon_events().size() << "\n"; + for (const auto &event : standalone::canon_events()) { + std::cout << "canon_event=" << event << "\n"; + } + + return rc == INTERP_OK ? 0 : 1; +} diff --git a/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_interp_init_harness.cpp b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_interp_init_harness.cpp new file mode 100644 index 0000000..66e3678 --- /dev/null +++ b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_interp_init_harness.cpp @@ -0,0 +1,49 @@ +#define private public +#include "emc/rs274ngc/rs274ngc_interp.hh" +#undef private + +#include +#include +#include +#include + +#include "canon_event_sink.hh" + +namespace { + +std::filesystem::path make_temp_dir() +{ + std::string pattern = "/tmp/linuxcnc-init-XXXXXX"; + char *path = mkdtemp(pattern.data()); + if (path == nullptr) { + std::perror("mkdtemp"); + std::exit(2); + } + return path; +} + +} // namespace + +int main() +{ + const auto temp_dir = make_temp_dir(); + std::filesystem::current_path(temp_dir); + + Interp interp; + standalone::reset_canon_events(); + + const int rc = interp.init(); + std::cout << "init=" << rc << "\n"; + std::cout << "setup.length_units=" << static_cast(interp._setup.length_units) << "\n"; + std::cout << "setup.origin_index=" << interp._setup.origin_index << "\n"; + std::cout << "setup.distance_mode=" << static_cast(interp._setup.distance_mode) << "\n"; + std::cout << "setup.feed_mode=" << static_cast(interp._setup.feed_mode) << "\n"; + std::cout << "setup.motion_mode=" << interp._setup.motion_mode << "\n"; + std::cout << "canon_event_count=" << standalone::canon_events().size() << "\n"; + for (const auto &event : standalone::canon_events()) { + std::cout << "canon_event=" << event << "\n"; + } + + std::filesystem::remove_all(temp_dir); + return rc == INTERP_OK ? 0 : 1; +} diff --git a/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp index 937a282..20dde0d 100644 --- a/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp +++ b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp @@ -36,8 +36,8 @@ const std::vector &canon_events() } // namespace standalone bool GET_BLOCK_DELETE(void) { return false; } -void FINISH(void) {} -void ON_RESET(void) {} +void FINISH(void) { standalone::push_canon_event("FINISH"); } +void ON_RESET(void) { standalone::push_canon_event("ON_RESET"); } USER_DEFINED_FUNCTION_TYPE USER_DEFINED_FUNCTION[USER_DEFINED_FUNCTION_NUM] = {}; @@ -50,7 +50,7 @@ int USER_DEFINED_FUNCTION_ADD(USER_DEFINED_FUNCTION_TYPE func, int num) return num; } -void INIT_CANON() {} +void INIT_CANON() { standalone::push_canon_event("INIT_CANON"); } void SET_G5X_OFFSET(int index, double x, double y, double z, double a, double b, double c, @@ -307,7 +307,7 @@ void CHANGE_TOOL_NUMBER(int pocket) oss << "CHANGE_TOOL_NUMBER pocket=" << pocket; standalone::push_canon_event(oss.str()); } -void RELOAD_TOOLDATA(void) {} +void RELOAD_TOOLDATA(void) { standalone::push_canon_event("RELOAD_TOOLDATA"); } void CLAMP_AXIS(CANON_AXIS) {} void DISABLE_ADAPTIVE_FEED() { standalone::push_canon_event("DISABLE_ADAPTIVE_FEED"); } void ENABLE_ADAPTIVE_FEED() { standalone::push_canon_event("ENABLE_ADAPTIVE_FEED"); } @@ -390,15 +390,52 @@ int WAIT(int index, int input_type, int wait_type, double timeout) standalone::push_canon_event(oss.str()); return 0; } -int UNLOCK_ROTARY(int, int) { return 0; } -int LOCK_ROTARY(int, int) { return 0; } -void UPDATE_TAG(const StateTag &) {} +int UNLOCK_ROTARY(int line_no, int joint_num) +{ + std::ostringstream oss; + oss << "UNLOCK_ROTARY line=" << line_no << " joint=" << joint_num; + standalone::push_canon_event(oss.str()); + return 0; +} +int LOCK_ROTARY(int line_no, int joint_num) +{ + std::ostringstream oss; + oss << "LOCK_ROTARY line=" << line_no << " joint=" << joint_num; + standalone::push_canon_event(oss.str()); + return 0; +} +void UPDATE_TAG(const StateTag &tag) +{ + std::ostringstream oss; + oss << "UPDATE_TAG" + << " line=" << tag.fields[GM_FIELD_LINE_NUMBER] + << " g0=" << tag.fields[GM_FIELD_G_MODE_0] + << " motion=" << tag.fields[GM_FIELD_MOTION_MODE] + << " plane=" << tag.fields[GM_FIELD_PLANE] + << " origin=" << tag.fields[GM_FIELD_ORIGIN] + << " feed=" << tag.fields_float[GM_FIELD_FLOAT_FEED] + << " speed=" << tag.fields_float[GM_FIELD_FLOAT_SPEED] + << " flags=" << tag.flags.to_ulong(); + standalone::push_canon_event(oss.str()); +} void MESSAGE(char *s) { standalone::push_canon_event(std::string("MESSAGE: ") + (s ? s : "")); } -void LOG(char *) {} -void LOGOPEN(char *) {} -void LOGAPPEND(char *) {} -void LOGCLOSE() {} +void LOG(char *s) +{ + standalone::push_canon_event(std::string("LOG: ") + (s ? s : "")); +} +void LOGOPEN(char *s) +{ + standalone::push_canon_event(std::string("LOGOPEN: ") + (s ? s : "")); +} +void LOGAPPEND(char *s) +{ + standalone::push_canon_event(std::string("LOGAPPEND: ") + (s ? s : "")); +} +void LOGCLOSE() +{ + standalone::push_canon_event("LOGCLOSE"); +} void CANON_ERROR(const char *fmt, ...) { diff --git a/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_runtime_state_stubs.cpp b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_runtime_state_stubs.cpp index de1d23d..786788b 100644 --- a/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_runtime_state_stubs.cpp +++ b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_runtime_state_stubs.cpp @@ -187,9 +187,9 @@ setup::setup() a_axis_wrapped(0), b_axis_wrapped(0), c_axis_wrapped(0), - a_indexer_jnum(0), - b_indexer_jnum(0), - c_indexer_jnum(0), + a_indexer_jnum(-1), + b_indexer_jnum(-1), + c_indexer_jnum(-1), lathe_diameter_mode(0), mdi_interrupt(0), feature_set(0), diff --git a/wasm-port/tests/fixtures/canon/comment_logging.events b/wasm-port/tests/fixtures/canon/comment_logging.events new file mode 100644 index 0000000..4018d7f --- /dev/null +++ b/wasm-port/tests/fixtures/canon/comment_logging.events @@ -0,0 +1,4 @@ +canon_event=LOGOPEN: session.log +canon_event=LOG: feed log line +canon_event=LOGAPPEND: append log line +canon_event=LOGCLOSE diff --git a/wasm-port/tests/fixtures/canon/file_open_reset.events b/wasm-port/tests/fixtures/canon/file_open_reset.events new file mode 100644 index 0000000..9567b47 --- /dev/null +++ b/wasm-port/tests/fixtures/canon/file_open_reset.events @@ -0,0 +1,2 @@ +canon_event=ON_RESET +canon_event=STRAIGHT_TRAVERSE line=1 x=1 y=0 z=0 a=0 b=0 c=0 u=0 v=0 w=0 diff --git a/wasm-port/tests/fixtures/canon/percent_file_finish.events b/wasm-port/tests/fixtures/canon/percent_file_finish.events new file mode 100644 index 0000000..a4357b7 --- /dev/null +++ b/wasm-port/tests/fixtures/canon/percent_file_finish.events @@ -0,0 +1,2 @@ +canon_event=STRAIGHT_TRAVERSE line=2 x=1 y=0 z=0 a=0 b=0 c=0 u=0 v=0 w=0 +canon_event=FINISH diff --git a/wasm-port/tests/fixtures/canon/state_tag_motion.events b/wasm-port/tests/fixtures/canon/state_tag_motion.events new file mode 100644 index 0000000..0067372 --- /dev/null +++ b/wasm-port/tests/fixtures/canon/state_tag_motion.events @@ -0,0 +1,2 @@ +canon_event=UPDATE_TAG line=1 +canon_event=UPDATE_TAG line=2 diff --git a/wasm-port/tests/fixtures/canon/tool_reload.events b/wasm-port/tests/fixtures/canon/tool_reload.events new file mode 100644 index 0000000..7e95dca --- /dev/null +++ b/wasm-port/tests/fixtures/canon/tool_reload.events @@ -0,0 +1 @@ +canon_event=RELOAD_TOOLDATA diff --git a/wasm-port/tests/fixtures/gcode/comment_logging.ngc b/wasm-port/tests/fixtures/gcode/comment_logging.ngc new file mode 100644 index 0000000..fef42a6 --- /dev/null +++ b/wasm-port/tests/fixtures/gcode/comment_logging.ngc @@ -0,0 +1,4 @@ +(LOGOPEN,session.log) +(LOG,feed log line) +(LOGAPPEND,append log line) +(LOGCLOSE) diff --git a/wasm-port/tests/fixtures/gcode/file_open_reset.ngc b/wasm-port/tests/fixtures/gcode/file_open_reset.ngc new file mode 100644 index 0000000..380a114 --- /dev/null +++ b/wasm-port/tests/fixtures/gcode/file_open_reset.ngc @@ -0,0 +1 @@ +G0 X1 diff --git a/wasm-port/tests/fixtures/gcode/percent_file_finish.ngc b/wasm-port/tests/fixtures/gcode/percent_file_finish.ngc new file mode 100644 index 0000000..4f3bb71 --- /dev/null +++ b/wasm-port/tests/fixtures/gcode/percent_file_finish.ngc @@ -0,0 +1,3 @@ +% +G0 X1 +% diff --git a/wasm-port/tests/fixtures/gcode/state_tag_motion.ngc b/wasm-port/tests/fixtures/gcode/state_tag_motion.ngc new file mode 100644 index 0000000..f22d2b7 --- /dev/null +++ b/wasm-port/tests/fixtures/gcode/state_tag_motion.ngc @@ -0,0 +1,2 @@ +G90 G17 G0 X1 Y2 +G1 X3 Y4 F120 diff --git a/wasm-port/tests/fixtures/gcode/tool_reload.ngc b/wasm-port/tests/fixtures/gcode/tool_reload.ngc new file mode 100644 index 0000000..42ba18f --- /dev/null +++ b/wasm-port/tests/fixtures/gcode/tool_reload.ngc @@ -0,0 +1 @@ +G10 L0 diff --git a/wasm-port/tests/native/verify_native_probes.sh b/wasm-port/tests/native/verify_native_probes.sh index a4f946f..9e7a206 100755 --- a/wasm-port/tests/native/verify_native_probes.sh +++ b/wasm-port/tests/native/verify_native_probes.sh @@ -12,6 +12,7 @@ NAMEDPARAM_INI_FIXTURE="$ROOT_DIR/tests/fixtures/ini/namedparams.ini" "$ROOT_DIR/tools/verify_upstream_baseline.sh" "$ROOT_DIR/tools/verify_vendor_sync.sh" "$ROOT_DIR/tools/verify_no_standalone_cnc_semantics.sh" +"$ROOT_DIR/tools/verify_native_linuxcnc_fixture_baseline.sh" "$ROOT_DIR/tools/build_native_probes.sh" check_source_probe_coverage() { @@ -152,6 +153,10 @@ check_exitcode linuxcnc_interp_minimal_harness check_exitcode linuxcnc_interp_minimal_harness.run check_exitcode linuxcnc_parameter_file_harness check_exitcode linuxcnc_parameter_file_harness.run +check_exitcode linuxcnc_interp_init_harness +check_exitcode linuxcnc_interp_init_harness.run +check_exitcode linuxcnc_indexer_harness +check_exitcode linuxcnc_indexer_harness.run check_exitcode linuxcnc_rs274_compile_probe check_exitcode linuxcnc_kins_util_source_probe check_exitcode linuxcnc_cubic_source_probe @@ -238,6 +243,31 @@ grep -Fq "saved_has_5399=1" "$PARAMETER_FILE_STDOUT" grep -Fq "saved_has_named_param=0" "$PARAMETER_FILE_STDOUT" grep -Fq "backup_has_original_5161=1" "$PARAMETER_FILE_STDOUT" +INIT_HARNESS_STDOUT="$BUILD_DIR/linuxcnc_interp_init_harness.run.stdout.log" +grep -Fq "init=0" "$INIT_HARNESS_STDOUT" +grep -Fq "setup.length_units=2" "$INIT_HARNESS_STDOUT" +grep -Fq "setup.origin_index=1" "$INIT_HARNESS_STDOUT" +grep -Fq "setup.distance_mode=0" "$INIT_HARNESS_STDOUT" +grep -Fq "setup.feed_mode=0" "$INIT_HARNESS_STDOUT" +grep -Fq "setup.motion_mode=800" "$INIT_HARNESS_STDOUT" +grep -Fq "canon_event=INIT_CANON" "$INIT_HARNESS_STDOUT" +grep -Fq "canon_event=USE_LENGTH_UNITS units=2" "$INIT_HARNESS_STDOUT" +grep -Fq "canon_event=SET_G5X_OFFSET index=1" "$INIT_HARNESS_STDOUT" +grep -Fq "canon_event=SET_G92_OFFSET x=0 y=0 z=0" "$INIT_HARNESS_STDOUT" +grep -Fq "canon_event=SET_XY_ROTATION rotation=0" "$INIT_HARNESS_STDOUT" +grep -Fq "canon_event=SET_FEED_REFERENCE reference=2" "$INIT_HARNESS_STDOUT" + +INDEXER_HARNESS_STDOUT="$BUILD_DIR/linuxcnc_indexer_harness.run.stdout.log" +grep -Fq "execute=0" "$INDEXER_HARNESS_STDOUT" +grep -Fq "post_a=90" "$INDEXER_HARNESS_STDOUT" +grep -Fq "canon_event=SET_MOTION_CONTROL_MODE mode=2 tolerance=0" "$INDEXER_HARNESS_STDOUT" +grep -Fq "canon_event=UNLOCK_ROTARY line=1 joint=0" "$INDEXER_HARNESS_STDOUT" +grep -Fq "canon_event=STRAIGHT_TRAVERSE line=1 x=0 y=0 z=0 a=90 b=0 c=0 u=0 v=0 w=0" "$INDEXER_HARNESS_STDOUT" +grep -Fq "canon_event=LOCK_ROTARY line=1 joint=0" "$INDEXER_HARNESS_STDOUT" +grep -Fq "canon_event=SET_MOTION_CONTROL_MODE mode=1 tolerance=0" "$INDEXER_HARNESS_STDOUT" +grep -Fq "canon_event=SET_NAIVECAM_TOLERANCE tolerance=0" "$INDEXER_HARNESS_STDOUT" +grep -Fq "canon_event=UPDATE_TAG line=1" "$INDEXER_HARNESS_STDOUT" + TP_API_STDOUT="$BUILD_DIR/linuxcnc_tp_api_probe.run.stdout.log" grep -Fq "tp_default_queue_size=32" "$TP_API_STDOUT" grep -Fq "tp_err_ok=0" "$TP_API_STDOUT" @@ -438,10 +468,10 @@ check_fixture_output() { local stdout_file="$3" local require_mdi="${4:-1}" - grep -Fq "read=0" "$stdout_file" - grep -Fq "parse_line=0" "$stdout_file" - if [[ "$require_mdi" == "1" ]]; then + grep -Fq "read=0" "$stdout_file" + grep -Fq "parse_line=0" "$stdout_file" + local line_count line_count="$(grep -cv '^[[:space:]]*$' "$fixture")" local line_number @@ -484,7 +514,7 @@ for fixture in "$GCODE_FIXTURE_DIR"/*.ngc; do >"$stdout_file" \ 2>"$stderr_file" require_mdi=1 - if [[ "$name" == "oword_subroutine" ]]; then + if [[ "$name" == "oword_subroutine" || "$name" == "percent_file_finish" ]]; then require_mdi=0 fi check_fixture_output "$fixture" "$expected" "$stdout_file" "$require_mdi" diff --git a/wasm-port/tools/build_native_probes.sh b/wasm-port/tools/build_native_probes.sh index 1d98f2d..8b3fae8 100755 --- a/wasm-port/tools/build_native_probes.sh +++ b/wasm-port/tools/build_native_probes.sh @@ -629,6 +629,16 @@ PARAMETER_FILE_SOURCES=( "$WRAP_DIR/linuxcnc_parameter_file_harness.cpp" ) +INIT_HARNESS_SOURCES=( + "${INTERP_CORE_SOURCES[@]}" + "$WRAP_DIR/linuxcnc_interp_init_harness.cpp" +) + +INDEXER_HARNESS_SOURCES=( + "${INTERP_CORE_SOURCES[@]}" + "$WRAP_DIR/linuxcnc_indexer_harness.cpp" +) + build_binary_target \ linuxcnc_ini_probe \ "$BUILD_DIR/linuxcnc_ini_probe" \ @@ -1123,6 +1133,52 @@ else "$BUILD_DIR/linuxcnc_parameter_file_harness.run.stderr.log" fi +build_binary_target \ + linuxcnc_interp_init_harness \ + "$BUILD_DIR/linuxcnc_interp_init_harness" \ + MINIMAL_FLAGS \ + INIT_HARNESS_SOURCES \ + MINIMAL_LINK_FLAGS \ + -lfmt + +if [[ "$(tr -d '[:space:]' < "$BUILD_DIR/linuxcnc_interp_init_harness.exitcode")" == "0" ]]; then + set +e + "$BUILD_DIR/linuxcnc_interp_init_harness" \ + >"$BUILD_DIR/linuxcnc_interp_init_harness.run.stdout.log" \ + 2>"$BUILD_DIR/linuxcnc_interp_init_harness.run.stderr.log" + INTERP_INIT_RUN_RC=$? + set -e + echo "$INTERP_INIT_RUN_RC" > "$BUILD_DIR/linuxcnc_interp_init_harness.run.exitcode" +else + rm -f \ + "$BUILD_DIR/linuxcnc_interp_init_harness.run.exitcode" \ + "$BUILD_DIR/linuxcnc_interp_init_harness.run.stdout.log" \ + "$BUILD_DIR/linuxcnc_interp_init_harness.run.stderr.log" +fi + +build_binary_target \ + linuxcnc_indexer_harness \ + "$BUILD_DIR/linuxcnc_indexer_harness" \ + MINIMAL_FLAGS \ + INDEXER_HARNESS_SOURCES \ + MINIMAL_LINK_FLAGS \ + -lfmt + +if [[ "$(tr -d '[:space:]' < "$BUILD_DIR/linuxcnc_indexer_harness.exitcode")" == "0" ]]; then + set +e + "$BUILD_DIR/linuxcnc_indexer_harness" \ + >"$BUILD_DIR/linuxcnc_indexer_harness.run.stdout.log" \ + 2>"$BUILD_DIR/linuxcnc_indexer_harness.run.stderr.log" + INDEXER_RUN_RC=$? + set -e + echo "$INDEXER_RUN_RC" > "$BUILD_DIR/linuxcnc_indexer_harness.run.exitcode" +else + rm -f \ + "$BUILD_DIR/linuxcnc_indexer_harness.run.exitcode" \ + "$BUILD_DIR/linuxcnc_indexer_harness.run.stdout.log" \ + "$BUILD_DIR/linuxcnc_indexer_harness.run.stderr.log" +fi + build_object_target \ linuxcnc_rs274_compile_probe \ "$BUILD_DIR/linuxcnc_rs274_compile_probe.o" \ diff --git a/wasm-port/tools/verify_native_linuxcnc_fixture_baseline.sh b/wasm-port/tools/verify_native_linuxcnc_fixture_baseline.sh new file mode 100755 index 0000000..e5b11d0 --- /dev/null +++ b/wasm-port/tools/verify_native_linuxcnc_fixture_baseline.sh @@ -0,0 +1,186 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +UPSTREAM_RS274="$ROOT_DIR/../linuxcnc/bin/rs274" +GCODE_FIXTURE_DIR="$ROOT_DIR/tests/fixtures/gcode" +CANON_FIXTURE_DIR="$ROOT_DIR/tests/fixtures/canon" + +FIXTURES=( + minimal_linear + length_units + plane_selection + modal_incremental +) + +if [[ ! -x "$UPSTREAM_RS274" ]]; then + echo "missing upstream rs274 executable: $UPSTREAM_RS274" >&2 + exit 1 +fi + +TMP_DIR="$(mktemp -d)" +trap 'rm -rf "$TMP_DIR"' EXIT + +normalize_standalone_events() { + awk ' + function norm(value) { + return sprintf("%.12g", value + 0) + } + + /^canon_event=/ { + line = $0 + sub(/^canon_event=/, "", line) + + if (line ~ /^SELECT_PLANE plane=/) { + value = line + sub(/^SELECT_PLANE plane=/, "", value) + if (value == "1") { + print "SELECT_PLANE|XY" + } else if (value == "2") { + print "SELECT_PLANE|YZ" + } else if (value == "3") { + print "SELECT_PLANE|XZ" + } + next + } + + if (line ~ /^USE_LENGTH_UNITS units=/) { + value = line + sub(/^USE_LENGTH_UNITS units=/, "", value) + if (value == "1") { + print "USE_LENGTH_UNITS|INCHES" + } else if (value == "2") { + print "USE_LENGTH_UNITS|MM" + } + next + } + + if (line ~ /^SET_FEED_RATE rate=/) { + value = line + sub(/^SET_FEED_RATE rate=/, "", value) + print "SET_FEED_RATE|" norm(value) + next + } + + if (line ~ /^STRAIGHT_(TRAVERSE|FEED) /) { + split(line, fields, /[[:space:]]+/) + for (i in fields) { + split(fields[i], pair, "=") + values[pair[1]] = pair[2] + } + print fields[1] "|" norm(values["x"]) "|" norm(values["y"]) "|" \ + norm(values["z"]) "|" norm(values["a"]) "|" norm(values["b"]) "|" \ + norm(values["c"]) + delete values + next + } + } + ' +} + +normalize_upstream_rs274_output() { + awk ' + function norm(value) { + return sprintf("%.12g", value + 0) + } + + function args_of(call, args) { + args = call + sub(/^[^(]+\(/, "", args) + sub(/\).*$/, "", args) + gsub(/[[:space:]]/, "", args) + return args + } + + /^[[:space:]]*[0-9]+[[:space:]]+N\.\.\.\.\./ { + line = $0 + sub(/^[[:space:]]*[0-9]+[[:space:]]+N\.\.\.\.\.[[:space:]]+/, "", line) + + if (line == "ON_RESET()") { + seen_reset = 1 + next + } + + if (!seen_reset) { + next + } + + if (line ~ /^SELECT_PLANE\(CANON_PLANE_/) { + value = line + sub(/^SELECT_PLANE\(CANON_PLANE_/, "", value) + sub(/\)$/, "", value) + print "SELECT_PLANE|" value + next + } + + if (line ~ /^USE_LENGTH_UNITS\(CANON_UNITS_/) { + value = line + sub(/^USE_LENGTH_UNITS\(CANON_UNITS_/, "", value) + sub(/\)$/, "", value) + print "USE_LENGTH_UNITS|" value + next + } + + if (line ~ /^SET_FEED_RATE\(/) { + args = args_of(line) + print "SET_FEED_RATE|" norm(args) + next + } + + if (line ~ /^STRAIGHT_(TRAVERSE|FEED)\(/) { + op = line + sub(/\(.*/, "", op) + args = args_of(line) + split(args, values, ",") + print op "|" norm(values[1]) "|" norm(values[2]) "|" norm(values[3]) "|" \ + norm(values[4]) "|" norm(values[5]) "|" norm(values[6]) + next + } + } + ' +} + +for name in "${FIXTURES[@]}"; do + gcode_file="$GCODE_FIXTURE_DIR/$name.ngc" + expected_file="$CANON_FIXTURE_DIR/$name.events" + native_raw="$TMP_DIR/$name.rs274.raw" + native_norm="$TMP_DIR/$name.rs274.normalized" + expected_norm="$TMP_DIR/$name.expected.normalized" + + if [[ ! -f "$gcode_file" ]]; then + echo "missing G-code fixture: $gcode_file" >&2 + exit 1 + fi + + if [[ ! -f "$expected_file" ]]; then + echo "missing canonical fixture: $expected_file" >&2 + exit 1 + fi + + set +e + "$UPSTREAM_RS274" -g "$gcode_file" > "$native_raw" 2>&1 + rs274_rc=$? + set -e + + if [[ "$rs274_rc" != "0" ]] && ! grep -Fq "File ended with no percent sign (%) or program end (M2)" "$native_raw"; then + echo "upstream rs274 failed for fixture: $name" >&2 + sed -n '1,160p' "$native_raw" >&2 + exit 1 + fi + + normalize_upstream_rs274_output < "$native_raw" > "$native_norm" + normalize_standalone_events < "$expected_file" > "$expected_norm" + + if [[ ! -s "$native_norm" ]]; then + echo "no normalized upstream rs274 events for fixture: $name" >&2 + sed -n '1,160p' "$native_raw" >&2 + exit 1 + fi + + if ! diff -u "$expected_norm" "$native_norm"; then + echo "native LinuxCNC rs274 baseline mismatch for fixture: $name" >&2 + exit 1 + fi +done + +echo "native LinuxCNC fixture baseline validation complete"