225 lines
11 KiB
C
225 lines
11 KiB
C
#include "sim_interpreter.h"
|
|
|
|
#include <math.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
/*
|
|
* 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;
|
|
}
|