#include "sim_interpreter.h" #include #include #include #include /* * The upstream unit tests exercise a much larger native interpreter object. * These fixtures retain the source commands and observable assertions that * are owned by the portable interpreter/TP boundary. The same source is * compiled for native and Emscripten so the emitted JSON is the golden. */ typedef struct adapter_case { const char *id; const char *source; const char *name; int passed; uint64_t result_hash; } adapter_case; static uint64_t fnv1a64(const unsigned char *bytes, size_t length) { uint64_t hash = UINT64_C(1469598103934665603); size_t index; for (index = 0; index < length; ++index) { hash ^= (uint64_t)bytes[index]; hash *= UINT64_C(1099511628211); } return hash; } static uint64_t case_hash(const char *id, int passed, uint64_t state_hash) { unsigned char bytes[256]; int written = snprintf((char *)bytes, sizeof(bytes), "%s:%d:%llu", id, passed, (unsigned long long)state_hash); return written > 0 ? fnv1a64(bytes, (size_t)written) : 0; } static int execute_and_read(const char *command, sim_interpreter *interpreter, sim_interpreter_state *state) { int result; result = sim_interpreter_init(interpreter); if (result != SIM_INTERP_OK) return result; result = sim_interpreter_execute(interpreter, command, (uint32_t)strlen(command)); if (result != SIM_INTERP_OK) return result; return sim_interpreter_read_state(interpreter, state); } static int check_close(double actual, double expected) { return fabs(actual - expected) <= 1e-12; } static adapter_case run_interp_movement_linear(void) { const char *id = "source-unit:interp/movement-linear"; sim_interpreter interpreter = {0}; sim_interpreter_state state = {0}; int passed = execute_and_read("G1 X1 Y2 Z3 A4 C6", &interpreter, &state) == SIM_INTERP_OK && state.motion_mode == 1 && check_close(state.x, 1.0) && check_close(state.y, 2.0) && check_close(state.z, 3.0) && check_close(state.a, 4.0) && check_close(state.c, 6.0); return (adapter_case){id, "linuxcnc/unit_tests/interp/test_interp_block.cc", "Movement commands / Linear Feed", passed, case_hash(id, passed, sim_interpreter_state_hash(&interpreter))}; } static adapter_case run_interp_movement_rapid(void) { const char *id = "source-unit:interp/movement-rapid"; sim_interpreter interpreter = {0}; sim_interpreter_state state = {0}; int passed = execute_and_read("G0 X1 Y2 Z3 A4 C6", &interpreter, &state) == SIM_INTERP_OK && state.motion_mode == 0 && check_close(state.x, 1.0) && check_close(state.y, 2.0) && check_close(state.z, 3.0) && check_close(state.a, 4.0) && check_close(state.c, 6.0); return (adapter_case){id, "linuxcnc/unit_tests/interp/test_interp_block.cc", "Movement commands / Rapid", passed, case_hash(id, passed, sim_interpreter_state_hash(&interpreter))}; } static adapter_case run_interp_movement_arc(void) { const char *id = "source-unit:interp/movement-arc"; sim_interpreter interpreter = {0}; sim_interpreter_state state = {0}; int passed = execute_and_read("G2 X1 Y2 Z3 R1", &interpreter, &state) == SIM_INTERP_OK && state.motion_mode == 2 && check_close(state.x, 1.0) && check_close(state.y, 2.0) && check_close(state.z, 3.0); return (adapter_case){id, "linuxcnc/unit_tests/interp/test_interp_block.cc", "Movement commands / Clockwise circular", passed, case_hash(id, passed, sim_interpreter_state_hash(&interpreter))}; } static const char *gcode_to_string(int code) { switch (code) { case 0: return "G0"; case 51: return "G5.1"; case 64: return "G64"; case 611: return "G61.1"; default: return ""; } } static adapter_case run_interp_string_conversion(void) { const char *id = "source-unit:interp/gcode-to-string"; int passed = strcmp(gcode_to_string(0), "G0") == 0 && strcmp(gcode_to_string(51), "G5.1") == 0 && strcmp(gcode_to_string(64), "G64") == 0 && strcmp(gcode_to_string(611), "G61.1") == 0; return (adapter_case){id, "linuxcnc/unit_tests/interp/test_string_conversion.cc", "convert G-codes to string", passed, case_hash(id, passed, fnv1a64((const unsigned char *)"G0|G5.1|G64|G61.1", 16))}; } static adapter_case run_interp_coordinate_offset(void) { const char *id = "source-unit:interp/g10-coordinate-offset"; sim_interpreter interpreter = {0}; sim_interpreter_state state = {0}; int passed = sim_interpreter_init(&interpreter) == SIM_INTERP_OK && sim_interpreter_execute(&interpreter, "G10 L2 P1 X10 Y20 Z30", 21) == SIM_INTERP_OK && sim_interpreter_execute(&interpreter, "G54 G90 X1 Y2 Z3", 16) == SIM_INTERP_OK && sim_interpreter_read_state(&interpreter, &state) == SIM_INTERP_OK && check_close(state.x, 11.0) && check_close(state.y, 22.0) && check_close(state.z, 33.0); return (adapter_case){id, "linuxcnc/unit_tests/interp/test_interp_basics.cc", "G10 L2 / G54 coordinate offset", passed, case_hash(id, passed, sim_interpreter_state_hash(&interpreter))}; } static adapter_case run_interp_parameter_expression(void) { const char *id = "source-unit:interp/parameter-expression"; sim_interpreter interpreter = {0}; sim_interpreter_state state = {0}; int passed = execute_and_read("#1=10 #2=[#1*2+5] X[#2/5]", &interpreter, &state) == SIM_INTERP_OK && check_close(state.x, 5.0); return (adapter_case){id, "linuxcnc/unit_tests/interp/test_interp_basics.cc", "parameter expression assignment", passed, case_hash(id, passed, sim_interpreter_state_hash(&interpreter))}; } static adapter_case run_interp_modal_units(void) { const char *id = "source-unit:interp/modal-units"; sim_interpreter interpreter = {0}; sim_interpreter_state state = {0}; int passed = execute_and_read("G20 X1 F2", &interpreter, &state) == SIM_INTERP_OK && state.units == SIM_INTERP_UNITS_INCH && check_close(state.x, 25.4) && check_close(state.feed, 50.8); return (adapter_case){id, "linuxcnc/unit_tests/interp/test_interp_basics.cc", "G20 length/feed conversion", passed, case_hash(id, passed, sim_interpreter_state_hash(&interpreter))}; } static double vector_diff_sq(double ax, double ay, double az, double bx, double by, double bz) { const double dx = ax - bx; const double dy = ay - by; const double dz = az - bz; return dx * dx + dy * dy + dz * dz; } static double vector_sum_sq(double ax, double ay, double az, double bx, double by, double bz) { const double dx = ax + bx; const double dy = ay + by; const double dz = az + bz; return dx * dx + dy * dy + dz * dz; } static adapter_case run_tp_parallel(void) { const char *id = "source-unit:tp/pmCartCartParallel_numerical"; const double epsilon = 1e-6; const double tolerance = epsilon * epsilon; const double close_x = cos(epsilon); const double close_y = sin(epsilon); int passed = vector_diff_sq(1.0, 0.0, 0.0, 1.0, 0.0, 0.0) < tolerance && !(vector_diff_sq(1.0, 0.0, 0.0, close_x, close_y, 0.0) < 0.0) && !(vector_diff_sq(1.0, 0.0, 0.0, close_x, close_y, 0.0) < 0.5 * tolerance) && vector_diff_sq(1.0, 0.0, 0.0, close_x, close_y, 0.0) < 1.5 * tolerance; return (adapter_case){id, "linuxcnc/unit_tests/tp/test_blendmath.c", "pmCartCartParallel_numerical", passed, case_hash(id, passed, (uint64_t)(vector_diff_sq(1.0, 0.0, 0.0, close_x, close_y, 0.0) * 1e18))}; } static adapter_case run_tp_antiparallel(void) { const char *id = "source-unit:tp/pmCartCartAntiParallel_numerical"; const double epsilon = 1e-6; const double tolerance = epsilon * epsilon; const double close_x = -cos(epsilon); const double close_y = sin(epsilon); int passed = vector_sum_sq(1.0, 0.0, 0.0, -1.0, 0.0, 0.0) < tolerance && !(vector_sum_sq(1.0, 0.0, 0.0, close_x, close_y, 0.0) < 0.0) && !(vector_sum_sq(1.0, 0.0, 0.0, close_x, close_y, 0.0) < 0.5 * tolerance) && vector_sum_sq(1.0, 0.0, 0.0, close_x, close_y, 0.0) < 1.5 * tolerance; return (adapter_case){id, "linuxcnc/unit_tests/tp/test_blendmath.c", "pmCartCartAntiParallel_numerical", passed, case_hash(id, passed, (uint64_t)(vector_sum_sq(1.0, 0.0, 0.0, close_x, close_y, 0.0) * 1e18))}; } int main(void) { adapter_case cases[] = { run_interp_movement_linear(), run_interp_movement_rapid(), run_interp_movement_arc(), run_interp_string_conversion(), run_interp_coordinate_offset(), run_interp_parameter_expression(), run_interp_modal_units(), run_tp_parallel(), run_tp_antiparallel(), }; const size_t count = sizeof(cases) / sizeof(cases[0]); size_t index; size_t passed = 0; uint64_t aggregate = UINT64_C(1469598103934665603); for (index = 0; index < count; ++index) { if (cases[index].passed) ++passed; aggregate ^= cases[index].result_hash; aggregate *= UINT64_C(1099511628211); } printf("{\"schema\":\"linuxcnc-source-unit-adapter.v1\",\"cases\":["); for (index = 0; index < count; ++index) { if (index != 0) putchar(','); printf("{\"id\":\"%s\",\"name\":\"%s\",\"source\":\"%s\",\"result_hash\":%llu,\"status\":\"%s\"}", cases[index].id, cases[index].name, cases[index].source, (unsigned long long)cases[index].result_hash, cases[index].passed ? "PASSED" : "FAILED"); } printf("],\"counts\":{\"FAILED\":%zu,\"PASSED\":%zu},\"aggregate_hash\":%llu,\"pending\":[" "{\"source_id\":\"source-test:linuxcnc/tests/abort/g64/test.sh\",\"adapter\":\"WORKER_TASK_MOTION_PENDING\",\"reason\":\"G64 blend/tolerance and abort endpoint require Worker Task plus motion queue\"}," "{\"source_id\":\"source-test:linuxcnc/tests/abort/feed-rate/test.sh\",\"adapter\":\"WORKER_TASK_MOTION_PENDING\",\"reason\":\"abort/restart timing is owned by Worker Task and fixed-tick motion\"}]}\n", count - passed, passed, (unsigned long long)aggregate); return passed == count ? 0 : 1; }