Complete sim config boundary coverage
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
|
||||
#include "emc/ini/inifile.hh"
|
||||
|
||||
#ifndef LINUXCNC_SOURCE_CONFIG_DIR
|
||||
#error "LINUXCNC_SOURCE_CONFIG_DIR must point at the LinuxCNC configs/sim tree"
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
struct UserMCase {
|
||||
const char *code;
|
||||
const char *remap_code;
|
||||
const char *script_rel;
|
||||
const char *remap_rel;
|
||||
const char *state_mode;
|
||||
const char *guard_pin;
|
||||
const char *switchkins_target;
|
||||
const char *active_g5x;
|
||||
const char *work_offset_pocket;
|
||||
const char *work_offset_words;
|
||||
const char *min_limit_key;
|
||||
const char *max_limit_key;
|
||||
};
|
||||
|
||||
std::string source_config_path(const std::string &rel)
|
||||
{
|
||||
return std::string(LINUXCNC_SOURCE_CONFIG_DIR) + "/" + rel;
|
||||
}
|
||||
|
||||
std::string read_text(const std::string &path)
|
||||
{
|
||||
std::ifstream input(path);
|
||||
return std::string(
|
||||
std::istreambuf_iterator<char>(input),
|
||||
std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
bool contains(const std::string &text, const std::string &needle)
|
||||
{
|
||||
return text.find(needle) != std::string::npos;
|
||||
}
|
||||
|
||||
void print_bool(const std::string &name, bool value)
|
||||
{
|
||||
std::cout << name << "=" << (value ? 1 : 0) << "\n";
|
||||
}
|
||||
|
||||
bool check_ini_value(
|
||||
const linuxcnc::IniFile &ini,
|
||||
const std::string &label,
|
||||
const std::string §ion,
|
||||
const std::string &tag,
|
||||
const std::string &expected)
|
||||
{
|
||||
const auto actual = ini.findString(tag, section);
|
||||
std::cout << label << "_" << section << "." << tag << "="
|
||||
<< (actual ? *actual : "<missing>") << "\n";
|
||||
const bool ok = actual && *actual == expected;
|
||||
print_bool(label + "_" + section + "." + tag + "_ok", ok);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool check_axis_targets(
|
||||
const linuxcnc::IniFile &ini,
|
||||
const UserMCase &user_m,
|
||||
const char *axis,
|
||||
const char *expected_min,
|
||||
const char *expected_max,
|
||||
const char *expected_velocity,
|
||||
const char *expected_acceleration)
|
||||
{
|
||||
const std::string section = std::string("AXIS_") + axis;
|
||||
const std::string label = std::string(user_m.code) + "_" + axis;
|
||||
bool ok = true;
|
||||
ok &= check_ini_value(ini, label, section, user_m.min_limit_key, expected_min);
|
||||
ok &= check_ini_value(ini, label, section, user_m.max_limit_key, expected_max);
|
||||
ok &= check_ini_value(ini, label, section, "MAX_VELOCITY", expected_velocity);
|
||||
ok &= check_ini_value(ini, label, section, "MAX_ACCELERATION", expected_acceleration);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool check_script(const UserMCase &user_m)
|
||||
{
|
||||
const std::string text = read_text(source_config_path(user_m.script_rel));
|
||||
bool ok = true;
|
||||
ok &= contains(text, "#!/usr/bin/tclsh");
|
||||
ok &= contains(text, "package require Linuxcnc");
|
||||
ok &= contains(text, "package require Hal");
|
||||
ok &= contains(text, "parse_ini $::env(INI_FILE_NAME)");
|
||||
ok &= contains(text, std::string("hal getp ") + user_m.guard_pin);
|
||||
ok &= contains(text, "hal setp ini.$l.min_limit");
|
||||
ok &= contains(text, "hal setp ini.$l.max_limit");
|
||||
ok &= contains(text, "hal setp ini.$l.min_velocity");
|
||||
ok &= contains(text, "hal setp ini.$l.max_acceleration");
|
||||
ok &= contains(text, user_m.min_limit_key);
|
||||
ok &= contains(text, user_m.max_limit_key);
|
||||
print_bool(std::string(user_m.code) + "_script_tcl_hal_runtime", ok);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool check_remap_caller(const UserMCase &user_m)
|
||||
{
|
||||
const std::string text = read_text(source_config_path(user_m.remap_rel));
|
||||
const std::string remap_code(user_m.remap_code);
|
||||
const bool ok =
|
||||
contains(text, user_m.code) &&
|
||||
contains(text, "#<SWITCHKINS_PIN> = 3") &&
|
||||
contains(text, std::string("#<kinstype> = ") + user_m.switchkins_target) &&
|
||||
contains(text, "M68") &&
|
||||
contains(text, "M68 E#<SWITCHKINS_PIN> Q#<kinstype>") &&
|
||||
contains(text, "M66") &&
|
||||
contains(text, "M66 E0 L0") &&
|
||||
contains(text, std::string("G10 L2 ") + user_m.work_offset_pocket + " " + user_m.work_offset_words) &&
|
||||
contains(text, user_m.active_g5x) &&
|
||||
contains(text, "#<_hal[motion.switchkins-type]>") &&
|
||||
contains(text, std::string("[#<_hal[motion.switchkins-type]> NE ") + user_m.switchkins_target + "]");
|
||||
std::cout << remap_code << "_switchkins_output_pin=motion.analog-out-03\n";
|
||||
std::cout << remap_code << "_switchkins_target=" << user_m.switchkins_target << "\n";
|
||||
std::cout << remap_code << "_work_offset_pocket=" << user_m.work_offset_pocket << "\n";
|
||||
std::cout << remap_code << "_work_offset_words=" << user_m.work_offset_words << "\n";
|
||||
std::cout << remap_code << "_active_g5x=" << user_m.active_g5x << "\n";
|
||||
print_bool(remap_code + "_calls_" + user_m.code, contains(text, user_m.code));
|
||||
print_bool(remap_code + "_source_transition_from_linuxcnc", ok);
|
||||
print_bool(std::string(user_m.code) + "_remap_calls_user_m_process", ok);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool check_user_m_case(const linuxcnc::IniFile &ini, const UserMCase &user_m)
|
||||
{
|
||||
bool ok = true;
|
||||
ok &= check_script(user_m);
|
||||
ok &= check_remap_caller(user_m);
|
||||
if (std::string(user_m.code) == "M128") {
|
||||
ok &= check_axis_targets(ini, user_m, "X", "-300", "300", "60.0", "400.0");
|
||||
ok &= check_axis_targets(ini, user_m, "Y", "-100", "100", "60.0", "400.0");
|
||||
ok &= check_axis_targets(ini, user_m, "Z", "-240", "0", "60.0", "400.0");
|
||||
} else {
|
||||
ok &= check_axis_targets(ini, user_m, "X", "-240", "0", "60.0", "400.0");
|
||||
ok &= check_axis_targets(ini, user_m, "Y", "-100", "100", "60.0", "400.0");
|
||||
ok &= check_axis_targets(ini, user_m, "Z", "-300", "300", "60.0", "400.0");
|
||||
}
|
||||
print_bool(std::string(user_m.code) + "_source_state_targets_from_linuxcnc", ok);
|
||||
return ok;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
const std::string ini_path = source_config_path("axis/vismach/millturn/millturn.ini");
|
||||
linuxcnc::IniFile ini(ini_path);
|
||||
const bool opened = static_cast<bool>(ini);
|
||||
print_bool("millturn_ini_open", opened);
|
||||
if (!opened) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
ok &= check_ini_value(ini, "millturn", "RS274NGC", "USER_M_PATH", "./mcodes");
|
||||
ok &= check_ini_value(ini, "millturn", "RS274NGC", "SUBROUTINE_PATH", "./remap_subs");
|
||||
ok &= check_ini_value(ini, "millturn", "DISPLAY", "PYVCP", "millturn.xml");
|
||||
ok &= check_ini_value(ini, "millturn", "HAL", "HALUI", "halui");
|
||||
ok &= check_ini_value(
|
||||
ini,
|
||||
"millturn",
|
||||
"HAL",
|
||||
"HALCMD",
|
||||
"net :kinstype-select <= motion.analog-out-03 => motion.switchkins-type");
|
||||
|
||||
const UserMCase cases[] = {
|
||||
{
|
||||
"M128",
|
||||
"M428",
|
||||
"axis/vismach/millturn/mcodes/M128",
|
||||
"axis/vismach/millturn/remap_subs/428remap.ngc",
|
||||
"mill",
|
||||
"kinstype.is-0",
|
||||
"0",
|
||||
"G59.1",
|
||||
"P7",
|
||||
"X-290 Y0 Z-160 A0",
|
||||
"MIN_LIMIT",
|
||||
"MAX_LIMIT",
|
||||
},
|
||||
{
|
||||
"M129",
|
||||
"M429",
|
||||
"axis/vismach/millturn/mcodes/M129",
|
||||
"axis/vismach/millturn/remap_subs/429remap.ngc",
|
||||
"turn",
|
||||
"kinstype.is-1",
|
||||
"1",
|
||||
"G59.2",
|
||||
"P8",
|
||||
"X-160 Y0 Z-290 A0",
|
||||
"MIN_LIMIT_TURN",
|
||||
"MAX_LIMIT_TURN",
|
||||
},
|
||||
};
|
||||
|
||||
for (const auto &user_m : cases) {
|
||||
std::cout << user_m.code << "_state_mode=" << user_m.state_mode << "\n";
|
||||
std::cout << user_m.code << "_guard_pin=" << user_m.guard_pin << "\n";
|
||||
ok &= check_user_m_case(ini, user_m);
|
||||
}
|
||||
|
||||
print_bool("millturn_user_m_native_source_state_proof", ok);
|
||||
print_bool("millturn_user_m_execution_enabled", false);
|
||||
print_bool("millturn_user_m_promotion_allowed", false);
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
|
||||
#ifndef LINUXCNC_SOURCE_DIR
|
||||
#error "LINUXCNC_SOURCE_DIR must point at the LinuxCNC source tree"
|
||||
#endif
|
||||
|
||||
#ifndef LINUXCNC_SOURCE_CONFIG_DIR
|
||||
#error "LINUXCNC_SOURCE_CONFIG_DIR must point at the LinuxCNC configs/sim tree"
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
std::string source_path(const std::string &rel)
|
||||
{
|
||||
return std::string(LINUXCNC_SOURCE_DIR) + "/" + rel;
|
||||
}
|
||||
|
||||
std::string source_config_path(const std::string &rel)
|
||||
{
|
||||
return std::string(LINUXCNC_SOURCE_CONFIG_DIR) + "/" + rel;
|
||||
}
|
||||
|
||||
std::string read_text(const std::string &path)
|
||||
{
|
||||
std::ifstream input(path);
|
||||
return std::string(
|
||||
std::istreambuf_iterator<char>(input),
|
||||
std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
bool contains(const std::string &text, const std::string &needle)
|
||||
{
|
||||
return text.find(needle) != std::string::npos;
|
||||
}
|
||||
|
||||
void print_bool(const std::string &name, bool value)
|
||||
{
|
||||
std::cout << name << "=" << (value ? 1 : 0) << "\n";
|
||||
}
|
||||
|
||||
bool check_python_runtime_owner()
|
||||
{
|
||||
const std::string interp_python = read_text(source_path("src/emc/rs274ngc/interp_python.cc"));
|
||||
const std::string python_plugin = read_text(source_path("src/emc/pythonplugin/python_plugin.cc"));
|
||||
|
||||
const bool pycall_dispatch =
|
||||
contains(interp_python, "PythonPlugin *python_plugin") &&
|
||||
contains(interp_python, "Interp::pycall") &&
|
||||
contains(interp_python, "python_plugin->call(module,funcname");
|
||||
const bool remap_phases =
|
||||
contains(interp_python, "PY_PROLOG") &&
|
||||
contains(interp_python, "PY_BODY") &&
|
||||
contains(interp_python, "PY_EPILOG");
|
||||
const bool generator_finish =
|
||||
contains(interp_python, "PyGen_Check(retval.ptr())") &&
|
||||
contains(interp_python, "PY_FINISH_PROLOG") &&
|
||||
contains(interp_python, "PY_FINISH_EPILOG");
|
||||
const bool callable_lookup =
|
||||
contains(interp_python, "Interp::is_pycallable") &&
|
||||
contains(interp_python, "python_plugin->is_callable(module,funcname)");
|
||||
const bool execute_runtime =
|
||||
contains(interp_python, "Interp::py_execute") &&
|
||||
contains(interp_python, "python_plugin->run_string(cmd, retval, as_file)");
|
||||
|
||||
const bool initializes_python =
|
||||
contains(python_plugin, "PyConfig_InitPythonConfig(&config)") &&
|
||||
contains(python_plugin, "Py_InitializeFromConfig(&config)");
|
||||
const bool toplevel_exec =
|
||||
contains(python_plugin, "findString(\"TOPLEVEL\", section)") &&
|
||||
contains(python_plugin, "bp::exec_file(abs_path, main_namespace, main_namespace)");
|
||||
const bool ini_python_path =
|
||||
contains(python_plugin, "findString(n, \"PATH_PREPEND\", \"PYTHON\")") &&
|
||||
contains(python_plugin, "findString(n, \"PATH_APPEND\", \"PYTHON\")") &&
|
||||
contains(python_plugin, "PyRun_SimpleString(pycmd)");
|
||||
const bool callable_invoke =
|
||||
contains(python_plugin, "PythonPlugin::call") &&
|
||||
contains(python_plugin, "PyObject_Call(function.ptr(), tupleargs.ptr(), kwargs.ptr())");
|
||||
const bool reload_on_change =
|
||||
contains(python_plugin, "PythonPlugin::reload") &&
|
||||
contains(python_plugin, "reload_on_change");
|
||||
|
||||
print_bool("python_runtime_pycall_dispatch", pycall_dispatch);
|
||||
print_bool("python_runtime_remap_phases", remap_phases);
|
||||
print_bool("python_runtime_generator_finish", generator_finish);
|
||||
print_bool("python_runtime_callable_lookup", callable_lookup);
|
||||
print_bool("python_runtime_execute_runtime", execute_runtime);
|
||||
print_bool("python_plugin_initializes_python", initializes_python);
|
||||
print_bool("python_plugin_toplevel_exec_file", toplevel_exec);
|
||||
print_bool("python_plugin_ini_python_path", ini_python_path);
|
||||
print_bool("python_plugin_callable_invoke", callable_invoke);
|
||||
print_bool("python_plugin_reload_on_change", reload_on_change);
|
||||
|
||||
return pycall_dispatch && remap_phases && generator_finish && callable_lookup &&
|
||||
execute_runtime && initializes_python && toplevel_exec && ini_python_path &&
|
||||
callable_invoke && reload_on_change;
|
||||
}
|
||||
|
||||
bool check_axis_laser_family()
|
||||
{
|
||||
const std::string ini = read_text(source_config_path("axis/laser/laser.ini"));
|
||||
const std::string remap = read_text(source_config_path("axis/laser/python/remap.py"));
|
||||
const bool ok =
|
||||
contains(ini, "REMAP= M10 modalgroup=10 python=rasterStop") &&
|
||||
contains(ini, "REMAP= M11 modalgroup=10 python=rasterBegin") &&
|
||||
contains(ini, "REMAP= M12 modalgroup=10 python=rasterData") &&
|
||||
contains(ini, "REMAP= M13 modalgroup=10 python=rasterStart") &&
|
||||
contains(ini, "TOPLEVEL=python/toplevel.py") &&
|
||||
contains(ini, "PATH_APPEND=python") &&
|
||||
contains(remap, "def rasterBegin(self, **words):") &&
|
||||
contains(remap, "def rasterData(self, **words):") &&
|
||||
contains(remap, "def rasterStart(self, **words):") &&
|
||||
contains(remap, "def rasterStop(self, **words):");
|
||||
print_bool("python_family_axis_laser_inventory", ok);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool check_twp_nutating_family()
|
||||
{
|
||||
const std::string ini = read_text(source_config_path(
|
||||
"axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzacb-trsrn_twp/xyzacb-trsrn.ini"));
|
||||
const std::string remap = read_text(source_config_path(
|
||||
"axis/vismach/5axis/table-rotary_spindle-rotary-nutating/python/remap.py"));
|
||||
const bool ok =
|
||||
contains(ini, "REMAP = G68.2") &&
|
||||
contains(ini, "python=g682") &&
|
||||
contains(ini, "REMAP = G68.3") &&
|
||||
contains(ini, "python=g683") &&
|
||||
contains(ini, "REMAP = G68.4") &&
|
||||
contains(ini, "python=g684") &&
|
||||
contains(ini, "python=g53x_core") &&
|
||||
contains(ini, "python=g69_core") &&
|
||||
contains(ini, "PATH_APPEND = ../python") &&
|
||||
contains(ini, "TOPLEVEL = ../python/toplevel.py") &&
|
||||
contains(remap, "def g53x_core(self):") &&
|
||||
contains(remap, "def g69_core(self):") &&
|
||||
contains(remap, "def g682(self, **words):") &&
|
||||
contains(remap, "def g683(self, **words):") &&
|
||||
contains(remap, "def g684(self, **words):");
|
||||
print_bool("python_family_twp_nutating_inventory", ok);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool check_cycle_and_stdglue_families()
|
||||
{
|
||||
const std::string cycle_ini = read_text(source_config_path("axis/remap/cycle/cycle.ini"));
|
||||
const std::string cycle_remap = read_text(source_config_path("axis/remap/cycle/python/remap.py"));
|
||||
const std::string stdglue = read_text(source_config_path(
|
||||
"axis/nc_files/remap_lib/python-stdglue/stdglue.py"));
|
||||
const std::string gmoccapy_stdglue = read_text(source_config_path("gmoccapy/python/stdglue.py"));
|
||||
|
||||
const bool cycle_ok =
|
||||
contains(cycle_ini, "python=g842") &&
|
||||
contains(cycle_ini, "prolog=cycle_prolog") &&
|
||||
contains(cycle_ini, "epilog=cycle_epilog") &&
|
||||
contains(cycle_ini, "PATH_PREPEND=./python") &&
|
||||
contains(cycle_ini, "PATH_APPEND=../../nc_files/remap_lib/python-stdglue") &&
|
||||
contains(cycle_remap, "def g842(self,**words):") &&
|
||||
contains(stdglue, "def cycle_prolog(self,**words):") &&
|
||||
contains(stdglue, "def cycle_epilog(self,**words):");
|
||||
|
||||
const bool gmoccapy_ok =
|
||||
contains(gmoccapy_stdglue, "def settool_prolog(self,**words):") &&
|
||||
contains(gmoccapy_stdglue, "def settool_epilog(self,**words):") &&
|
||||
contains(gmoccapy_stdglue, "def change_prolog(self, **words):") &&
|
||||
contains(gmoccapy_stdglue, "def change_epilog(self, **words):");
|
||||
|
||||
print_bool("python_family_axis_remap_cycle_inventory", cycle_ok);
|
||||
print_bool("python_family_gmoccapy_stdglue_inventory", gmoccapy_ok);
|
||||
return cycle_ok && gmoccapy_ok;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
bool ok = true;
|
||||
ok &= check_python_runtime_owner();
|
||||
ok &= check_axis_laser_family();
|
||||
ok &= check_twp_nutating_family();
|
||||
ok &= check_cycle_and_stdglue_families();
|
||||
|
||||
print_bool("python_remap_native_source_inventory_proof", ok);
|
||||
print_bool("python_remap_execution_enabled", false);
|
||||
print_bool("python_remap_promotion_allowed", false);
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
|
||||
#include "emc/ini/inifile.hh"
|
||||
|
||||
#ifndef LINUXCNC_SOURCE_DIR
|
||||
#error "LINUXCNC_SOURCE_DIR must point at the LinuxCNC source tree"
|
||||
#endif
|
||||
|
||||
#ifndef LINUXCNC_SOURCE_CONFIG_DIR
|
||||
#error "LINUXCNC_SOURCE_CONFIG_DIR must point at the LinuxCNC configs/sim tree"
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
std::string source_path(const std::string &rel)
|
||||
{
|
||||
return std::string(LINUXCNC_SOURCE_DIR) + "/" + rel;
|
||||
}
|
||||
|
||||
std::string source_config_path(const std::string &rel)
|
||||
{
|
||||
return std::string(LINUXCNC_SOURCE_CONFIG_DIR) + "/" + rel;
|
||||
}
|
||||
|
||||
std::string read_text(const std::string &path)
|
||||
{
|
||||
std::ifstream input(path);
|
||||
return std::string(
|
||||
std::istreambuf_iterator<char>(input),
|
||||
std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
bool contains(const std::string &text, const std::string &needle)
|
||||
{
|
||||
return text.find(needle) != std::string::npos;
|
||||
}
|
||||
|
||||
void print_bool(const std::string &name, bool value)
|
||||
{
|
||||
std::cout << name << "=" << (value ? 1 : 0) << "\n";
|
||||
}
|
||||
|
||||
bool check_ini_value(
|
||||
const linuxcnc::IniFile &ini,
|
||||
const std::string &label,
|
||||
const std::string §ion,
|
||||
const std::string &tag,
|
||||
const std::string &expected)
|
||||
{
|
||||
const auto actual = ini.findString(tag, section);
|
||||
std::cout << label << "_" << section << "." << tag << "="
|
||||
<< (actual ? *actual : "<missing>") << "\n";
|
||||
const bool ok = actual && *actual == expected;
|
||||
print_bool(label + "_" + section + "." + tag + "_ok", ok);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool check_task_owner()
|
||||
{
|
||||
const std::string text = read_text(source_path("src/emc/task/taskclass.cc"));
|
||||
const bool db_init =
|
||||
contains(text, "findString(\"DB_PROGRAM\", \"EMCIO\")") &&
|
||||
contains(text, "tooldata_set_db(db_mode)") &&
|
||||
contains(text, "tooldata_db_init(db_program, random_toolchanger)");
|
||||
const bool tbl_ignored = contains(text, "DB_PROGRAM active: IGNORING tool table file");
|
||||
const bool load_unload_notify =
|
||||
contains(text, "tooldata_db_notify(SPINDLE_UNLOAD") &&
|
||||
contains(text, "tooldata_db_notify(SPINDLE_LOAD");
|
||||
|
||||
print_bool("tool_db_task_db_mode_init", db_init);
|
||||
print_bool("tool_db_task_ignores_tool_table_with_db_program", tbl_ignored);
|
||||
print_bool("tool_db_task_load_unload_notify", load_unload_notify);
|
||||
return db_init && tbl_ignored && load_unload_notify;
|
||||
}
|
||||
|
||||
bool check_tooldata_db_owner()
|
||||
{
|
||||
const std::string text = read_text(source_path("src/emc/tooldata/tooldata_db.cc"));
|
||||
const bool child_process =
|
||||
contains(text, "pipe2(pipes[PARENT_READ_PIPE]") &&
|
||||
contains(text, "pipe2(pipes[PARENT_WRITE_PIPE]") &&
|
||||
contains(text, "fork()") &&
|
||||
contains(text, "execv(myargv[0], myargv)");
|
||||
const bool executable_check =
|
||||
contains(text, "access(child_argv[0],X_OK)") &&
|
||||
contains(text, "not executable");
|
||||
const bool version_handshake =
|
||||
contains(text, "#define DB_VERSION \"v2.1\"") &&
|
||||
contains(text, "read_reply(reply,sizeof(reply))") &&
|
||||
contains(text, "strncmp(reply,DB_VERSION");
|
||||
const bool getall =
|
||||
contains(text, "send_request((char*)\"g\\n\")") &&
|
||||
contains(text, "tooldata_reset()") &&
|
||||
contains(text, "strstr(reply,\"FINI\")") &&
|
||||
contains(text, "tooldata_read_entry(reply)");
|
||||
const bool notify =
|
||||
contains(text, "snprintf(msg,sizeof(msg),\"l %s\\n\",buffer)") &&
|
||||
contains(text, "snprintf(msg,sizeof(msg),\"u %s\\n\",buffer)") &&
|
||||
contains(text, "snprintf(msg,sizeof(msg),\"p %s\\n\",buffer)") &&
|
||||
contains(text, "send_and_verify(msg)");
|
||||
|
||||
print_bool("tool_db_child_process_boundary", child_process);
|
||||
print_bool("tool_db_program_executable_check", executable_check);
|
||||
print_bool("tool_db_v2_1_handshake", version_handshake);
|
||||
print_bool("tool_db_getall_g_until_fini", getall);
|
||||
print_bool("tool_db_notify_l_u_p_protocol", notify);
|
||||
return child_process && executable_check && version_handshake && getall && notify;
|
||||
}
|
||||
|
||||
bool check_db_program_owner()
|
||||
{
|
||||
const std::string text = read_text(source_config_path("axis/db_demo/db.py"));
|
||||
const bool imports =
|
||||
contains(text, "from tooldb import tooldb_callbacks") &&
|
||||
contains(text, "from tooldb import tooldb_tools") &&
|
||||
contains(text, "from tooldb import tooldb_loop");
|
||||
const bool callbacks =
|
||||
contains(text, "def user_get_tool(tno):") &&
|
||||
contains(text, "def user_put_tool(tno,params):") &&
|
||||
contains(text, "def user_load_spindle_nonran_tc(tno,params):") &&
|
||||
contains(text, "def user_unload_spindle_nonran_tc(tno,params):") &&
|
||||
contains(text, "tooldb_callbacks(user_get_tool") &&
|
||||
contains(text, "tooldb_tools(toollist)") &&
|
||||
contains(text, "tooldb_loop()");
|
||||
const bool state_targets =
|
||||
contains(text, "db_nonran_savefile = \"/tmp/db_nonran_file\"") &&
|
||||
contains(text, "toolno_min = 10") &&
|
||||
contains(text, "toolno_max = 19") &&
|
||||
contains(text, "pocket_offset = 100") &&
|
||||
contains(text, "return tno+pocket_offset") &&
|
||||
contains(text, "D['P'] = \"0\"") &&
|
||||
contains(text, "nonran_restore_pocket(spindle_tool)") &&
|
||||
contains(text, "linuxcnc.command().load_tool_table") &&
|
||||
contains(text, "save_tools_to_file(db_savefile");
|
||||
const bool reload_rules =
|
||||
contains(text, "apply_db_rules()") &&
|
||||
contains(text, "G10L0");
|
||||
|
||||
print_bool("tool_db_program_tooldb_module", imports);
|
||||
print_bool("tool_db_program_nonran_callbacks", callbacks);
|
||||
print_bool("tool_db_program_nonran_state_targets", state_targets);
|
||||
print_bool("tool_db_program_reload_rules", reload_rules);
|
||||
return imports && callbacks && state_targets && reload_rules;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
const std::string ini_path = source_config_path("axis/db_demo/db_nonran.ini");
|
||||
const std::string ini_text = read_text(ini_path);
|
||||
linuxcnc::IniFile ini(ini_path);
|
||||
const bool opened = static_cast<bool>(ini);
|
||||
print_bool("db_nonran_ini_open", opened);
|
||||
if (!opened) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
ok &= check_ini_value(ini, "db_nonran", "EMCIO", "RANDOM_TOOLCHANGER", "0");
|
||||
ok &= check_ini_value(ini, "db_nonran", "EMCIO", "DB_PROGRAM", "./db_nonran.py");
|
||||
const bool tool_table_ignored = contains(ini_text, "TOOL_TABLE= is not used with DB_PROGRAM");
|
||||
print_bool("db_nonran_tool_table_ignored_with_db_program", tool_table_ignored);
|
||||
ok &= tool_table_ignored;
|
||||
|
||||
ok &= check_task_owner();
|
||||
ok &= check_tooldata_db_owner();
|
||||
ok &= check_db_program_owner();
|
||||
|
||||
print_bool("tool_db_native_source_protocol_proof", ok);
|
||||
print_bool("tool_db_execution_enabled", false);
|
||||
print_bool("tool_db_promotion_allowed", false);
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
@@ -23,6 +23,7 @@ Use `src/index.js` for stable imports:
|
||||
|
||||
```js
|
||||
import {
|
||||
analyzeIniRuntimeBoundaries,
|
||||
createLinuxCncIniSdk,
|
||||
createLinuxCncInterpSdk,
|
||||
planIniFileContextStaging,
|
||||
@@ -104,6 +105,20 @@ source manifest text, machine relative path, INI file name, and INI text. It
|
||||
uses `planIniFileContextStaging()` with a `configs/sim/<machine>` source root
|
||||
and `configs/sim` upward-search boundary.
|
||||
|
||||
`analyzeIniRuntimeBoundaries()` is a host-boundary classifier for LinuxCNC
|
||||
INI-driven runs. It reads INI text plus optional execution text and manifest
|
||||
text, then reports declared HAL, UI, HALUI MDI, Python, tool-database, and
|
||||
external user-M process dependencies. User-M accounting is per execution code:
|
||||
`executionCodes` lists `M100..M199` codes seen in the supplied execution text,
|
||||
while `unstagedExecutionCodes` lists the subset not backed by vendored
|
||||
`USER_M_PATH` files. Python accounting keeps UI/DB references separate from
|
||||
Python remap runtime references, so a UI handler or DB program does not imply
|
||||
Python-remap coverage. It also returns the currently recommended Layer 4
|
||||
blocked kind for hard process boundaries such as `L4-TOOL-DB` and
|
||||
`L4-USER-M-PROCESS`. The classifier is policy/accounting only: it does not
|
||||
execute HAL, task, UI, Python, user-M, or tool-database behavior and does not
|
||||
change interpreter semantics.
|
||||
|
||||
`runFileWithIniContinueOnError()` uses the same LinuxCNC-backed file execution
|
||||
path but keeps the runner loop going after LinuxCNC reports an error, matching
|
||||
upstream `rs274 -n 0` regression tests such as `tests/interp/oword-unwind`.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export { createLinuxCncIniSdk } from "./linuxcnc-ini.js";
|
||||
export { createLinuxCncInterpSdk } from "./linuxcnc-interp.js";
|
||||
export {
|
||||
analyzeIniRuntimeBoundaries,
|
||||
planIniFileContextStaging,
|
||||
planSimConfigStaging,
|
||||
} from "./sim-config-staging.js";
|
||||
|
||||
@@ -172,6 +172,197 @@ function remapNgcNames(iniValues) {
|
||||
return names;
|
||||
}
|
||||
|
||||
function sectionValues(iniValues, section) {
|
||||
const prefix = `${section.toUpperCase()}.`;
|
||||
const values = [];
|
||||
for (const [key, entries] of iniValues.entries()) {
|
||||
if (!key.startsWith(prefix)) {
|
||||
continue;
|
||||
}
|
||||
for (const value of entries) {
|
||||
values.push({ key: key.slice(prefix.length), value });
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
function valuesMatching(iniValues, section, keys) {
|
||||
const wanted = new Set(keys.map((key) => key.toUpperCase()));
|
||||
return sectionValues(iniValues, section)
|
||||
.filter((entry) => wanted.has(entry.key))
|
||||
.map((entry) => entry.value);
|
||||
}
|
||||
|
||||
function looksLikePythonReference(value) {
|
||||
return /(^|\s|=|:)["']?[^"'\s]*\.py(["'\s]|$)/i.test(value);
|
||||
}
|
||||
|
||||
function userMCodesInText(text) {
|
||||
const codes = new Set();
|
||||
for (const match of text.matchAll(/(?<![A-Za-z0-9_])M\s*(1\d\d)(?![0-9])/gi)) {
|
||||
codes.add(`M${match[1]}`);
|
||||
}
|
||||
return [...codes].sort();
|
||||
}
|
||||
|
||||
function vendoredUserMCodes({
|
||||
manifestEntries,
|
||||
sourceDir,
|
||||
normalizedSearchRoot,
|
||||
userMPathValues,
|
||||
}) {
|
||||
const manifestSet = new Set(manifestEntries);
|
||||
const executableCodes = new Set();
|
||||
for (const dirEntry of userMPathValues.flatMap(splitSearchPath)) {
|
||||
if (dirEntry.startsWith("/")) {
|
||||
continue;
|
||||
}
|
||||
const sourceUserMDir = normalizeRel(`${sourceDir}/${dirEntry}`);
|
||||
const prefix = sourceUserMDir ? `${sourceUserMDir}/` : "";
|
||||
for (const sourceRel of manifestEntries) {
|
||||
if (
|
||||
sourceRel.startsWith(prefix) &&
|
||||
!sourceRel.slice(prefix.length).includes("/") &&
|
||||
isUserMCodePath(sourceRel)
|
||||
) {
|
||||
executableCodes.add(basename(sourceRel).toUpperCase());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const dirEntry of userMPathValues.flatMap(splitSearchPath)) {
|
||||
if (dirEntry.startsWith("/")) {
|
||||
continue;
|
||||
}
|
||||
const sourceUserMDir = normalizeRel(`${sourceDir}/${dirEntry}`);
|
||||
for (let code = 100; code <= 199; code += 1) {
|
||||
const candidate = findUpwardByBasename(
|
||||
manifestSet,
|
||||
sourceUserMDir,
|
||||
`M${code}`,
|
||||
normalizedSearchRoot,
|
||||
);
|
||||
if (candidate) {
|
||||
executableCodes.add(`M${code}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [...executableCodes].sort();
|
||||
}
|
||||
|
||||
export function analyzeIniRuntimeBoundaries({
|
||||
manifestText = "",
|
||||
sourceRootRel,
|
||||
sourceSearchRootRel = sourceRootRel,
|
||||
iniFile = "",
|
||||
iniText,
|
||||
executionTexts = [],
|
||||
}) {
|
||||
const manifestEntries = cleanManifest(manifestText);
|
||||
const iniValues = parseIni(iniText);
|
||||
const normalizedSourceRoot = normalizeRel(sourceRootRel);
|
||||
const normalizedSearchRoot = normalizeRel(sourceSearchRootRel);
|
||||
const sourceDir = normalizeRel(`${normalizedSourceRoot}/${dirname(iniFile)}`);
|
||||
const userMPathValues = allIniValues(iniValues, "RS274NGC", "USER_M_PATH");
|
||||
const halValues = valuesMatching(iniValues, "HAL", [
|
||||
"HALFILE",
|
||||
"HALCMD",
|
||||
"POSTGUI_HALFILE",
|
||||
"HALUI",
|
||||
]);
|
||||
const displayValues = valuesMatching(iniValues, "DISPLAY", [
|
||||
"DISPLAY",
|
||||
"PYVCP",
|
||||
"GLADEVCP",
|
||||
"EMBED_TAB_COMMAND",
|
||||
]);
|
||||
const halUiMdiCommands = allIniValues(iniValues, "HALUI", "MDI_COMMAND");
|
||||
const dbProgram = firstIniValue(iniValues, "EMCIO", "DB_PROGRAM");
|
||||
const remapValues = allIniValues(iniValues, "RS274NGC", "REMAP");
|
||||
const pythonRemapReferences = [
|
||||
...sectionValues(iniValues, "PYTHON").map((entry) => entry.value),
|
||||
...remapValues.filter((value) => /(?:^|\s)python=/i.test(value)),
|
||||
];
|
||||
const pythonUiReferences = [
|
||||
...displayValues.filter(looksLikePythonReference),
|
||||
...(dbProgram && looksLikePythonReference(dbProgram) ? [dbProgram] : []),
|
||||
];
|
||||
const pythonReferences = [...pythonRemapReferences, ...pythonUiReferences];
|
||||
const vendoredUserMCodeList = vendoredUserMCodes({
|
||||
manifestEntries,
|
||||
sourceDir,
|
||||
normalizedSearchRoot,
|
||||
userMPathValues,
|
||||
});
|
||||
const vendoredUserMCodeSet = new Set(vendoredUserMCodeList);
|
||||
const hasUserMPath = userMPathValues.length > 0;
|
||||
const executionUserMCodes = [...new Set(executionTexts.flatMap(userMCodesInText))].sort();
|
||||
const unstagedExecutionUserMCodes = executionUserMCodes
|
||||
.filter((code) => !vendoredUserMCodeSet.has(code));
|
||||
const hasExternalUserMUse = hasUserMPath && unstagedExecutionUserMCodes.length > 0;
|
||||
|
||||
const dependencies = [];
|
||||
if (dbProgram) {
|
||||
dependencies.push("tool_database_process");
|
||||
}
|
||||
if (halValues.length > 0) {
|
||||
dependencies.push("hal_process");
|
||||
}
|
||||
if (displayValues.some((value) => !/^axis$/i.test(value))) {
|
||||
dependencies.push("ui_process");
|
||||
}
|
||||
if (halUiMdiCommands.length > 0) {
|
||||
dependencies.push("halui_mdi_process");
|
||||
}
|
||||
if (pythonReferences.length > 0) {
|
||||
dependencies.push("python_runtime");
|
||||
}
|
||||
if (hasExternalUserMUse) {
|
||||
dependencies.push("external_user_m_process");
|
||||
}
|
||||
|
||||
let recommendedBlockedKind = "-";
|
||||
if (dbProgram) {
|
||||
recommendedBlockedKind = "L4-TOOL-DB";
|
||||
} else if (hasExternalUserMUse) {
|
||||
recommendedBlockedKind = "L4-USER-M-PROCESS";
|
||||
} else if (pythonRemapReferences.length > 0) {
|
||||
recommendedBlockedKind = "L4-PYTHON-REMAP";
|
||||
}
|
||||
|
||||
return {
|
||||
dependencies: [...new Set(dependencies)].sort(),
|
||||
recommendedBlockedKind,
|
||||
toolDatabaseProgram: dbProgram ?? "",
|
||||
halRuntime: {
|
||||
values: halValues,
|
||||
requiresProcess: halValues.length > 0,
|
||||
},
|
||||
uiRuntime: {
|
||||
values: displayValues,
|
||||
requiresProcess: displayValues.some((value) => !/^axis$/i.test(value)),
|
||||
},
|
||||
haluiRuntime: {
|
||||
mdiCommands: halUiMdiCommands,
|
||||
requiresProcess: halUiMdiCommands.length > 0,
|
||||
},
|
||||
userMRuntime: {
|
||||
paths: userMPathValues,
|
||||
vendoredExecutableCount: vendoredUserMCodeList.length,
|
||||
executionCodes: executionUserMCodes,
|
||||
unstagedExecutionCodes: unstagedExecutionUserMCodes,
|
||||
requiresExternalProcess: hasExternalUserMUse,
|
||||
},
|
||||
pythonRuntime: {
|
||||
references: pythonReferences,
|
||||
remapReferences: pythonRemapReferences,
|
||||
uiReferences: pythonUiReferences,
|
||||
requiresProcess: pythonReferences.length > 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function planSimConfigStaging({
|
||||
manifestText,
|
||||
machineRel,
|
||||
|
||||
Reference in New Issue
Block a user