按建议继续完成后续工作

结论:native 兼容性验证已接入 interpreter init、rotary indexer、canonical 边界 fixture,并新增 upstream rs274 side-by-side 基线;完整 verify_native_probes.sh 已通过。
This commit is contained in:
2026-06-07 22:37:29 +08:00
parent 59cfc8406e
commit 7a20531ae4
20 changed files with 530 additions and 25 deletions

View File

@@ -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`

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,51 @@
#define private public
#include "emc/rs274ngc/rs274ngc_interp.hh"
#undef private
#include <cstring>
#include <iostream>
#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;
}

View File

@@ -0,0 +1,49 @@
#define private public
#include "emc/rs274ngc/rs274ngc_interp.hh"
#undef private
#include <cstdlib>
#include <filesystem>
#include <iostream>
#include <unistd.h>
#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<int>(interp._setup.length_units) << "\n";
std::cout << "setup.origin_index=" << interp._setup.origin_index << "\n";
std::cout << "setup.distance_mode=" << static_cast<int>(interp._setup.distance_mode) << "\n";
std::cout << "setup.feed_mode=" << static_cast<int>(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;
}

View File

@@ -36,8 +36,8 @@ const std::vector<std::string> &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, ...)
{

View File

@@ -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),

View File

@@ -0,0 +1,4 @@
canon_event=LOGOPEN: session.log
canon_event=LOG: feed log line
canon_event=LOGAPPEND: append log line
canon_event=LOGCLOSE

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,2 @@
canon_event=UPDATE_TAG line=1
canon_event=UPDATE_TAG line=2

View File

@@ -0,0 +1 @@
canon_event=RELOAD_TOOLDATA

View File

@@ -0,0 +1,4 @@
(LOGOPEN,session.log)
(LOG,feed log line)
(LOGAPPEND,append log line)
(LOGCLOSE)

View File

@@ -0,0 +1 @@
G0 X1

View File

@@ -0,0 +1,3 @@
%
G0 X1
%

View File

@@ -0,0 +1,2 @@
G90 G17 G0 X1 Y2
G1 X3 Y4 F120

View File

@@ -0,0 +1 @@
G10 L0

View File

@@ -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}"
if [[ "$require_mdi" == "1" ]]; then
grep -Fq "read=0" "$stdout_file"
grep -Fq "parse_line=0" "$stdout_file"
if [[ "$require_mdi" == "1" ]]; then
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"

View File

@@ -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" \

View File

@@ -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"