262 lines
9.4 KiB
Python
262 lines
9.4 KiB
Python
"""Tests for ConfigParser."""
|
|
import pytest
|
|
import sys
|
|
import os
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
|
|
|
from RSIPI.config_parser import ConfigParser
|
|
|
|
# Path to the real config file in the project root
|
|
CONFIG_FILE = os.path.join(os.path.dirname(__file__), '..', 'RSI_EthernetConfig.xml')
|
|
|
|
|
|
@pytest.fixture
|
|
def parser():
|
|
"""Create a ConfigParser from the real config file."""
|
|
return ConfigParser(CONFIG_FILE)
|
|
|
|
|
|
class TestParseWithoutError:
|
|
"""Verify that the config file parses successfully."""
|
|
|
|
def test_parser_initializes(self, parser):
|
|
"""ConfigParser should initialize without raising."""
|
|
assert parser is not None
|
|
|
|
def test_send_variables_is_dict(self, parser):
|
|
"""send_variables should be a dict after parsing."""
|
|
assert isinstance(parser.send_variables, dict)
|
|
|
|
def test_receive_variables_is_dict(self, parser):
|
|
"""receive_variables should be a dict after parsing."""
|
|
assert isinstance(parser.receive_variables, dict)
|
|
|
|
|
|
class TestNetworkSettings:
|
|
"""Verify network_settings extraction from <CONFIG>."""
|
|
|
|
def test_has_ip(self, parser):
|
|
"""network_settings must contain 'ip'."""
|
|
assert "ip" in parser.network_settings
|
|
assert parser.network_settings["ip"] == "10.10.10.10"
|
|
|
|
def test_has_port(self, parser):
|
|
"""network_settings must contain 'port' as int."""
|
|
assert "port" in parser.network_settings
|
|
assert parser.network_settings["port"] == 64000
|
|
assert isinstance(parser.network_settings["port"], int)
|
|
|
|
def test_has_sentype(self, parser):
|
|
"""network_settings must contain 'sentype'."""
|
|
assert "sentype" in parser.network_settings
|
|
assert parser.network_settings["sentype"] == "ImFree"
|
|
|
|
def test_has_onlysend(self, parser):
|
|
"""network_settings must contain 'onlysend' as bool."""
|
|
assert "onlysend" in parser.network_settings
|
|
assert parser.network_settings["onlysend"] is False
|
|
|
|
def test_get_network_settings_matches(self, parser):
|
|
"""get_network_settings() should return the same dict."""
|
|
assert parser.get_network_settings() == parser.network_settings
|
|
|
|
|
|
class TestSendVariables:
|
|
"""Verify send_variables contains expected keys from the config file."""
|
|
|
|
def test_contains_rist(self, parser):
|
|
"""RIst (robot actual position) should be in send_variables."""
|
|
assert "RIst" in parser.send_variables
|
|
|
|
def test_rist_has_cartesian_keys(self, parser):
|
|
"""RIst should expand to X, Y, Z, A, B, C sub-keys."""
|
|
rist = parser.send_variables["RIst"]
|
|
assert isinstance(rist, dict)
|
|
for axis in ["X", "Y", "Z", "A", "B", "C"]:
|
|
assert axis in rist
|
|
|
|
def test_contains_rsol(self, parser):
|
|
"""RSol (robot commanded position) should be in send_variables."""
|
|
assert "RSol" in parser.send_variables
|
|
|
|
def test_contains_ipoc(self, parser):
|
|
"""IPOC timestamp should always be in send_variables."""
|
|
assert "IPOC" in parser.send_variables
|
|
|
|
def test_contains_delay(self, parser):
|
|
"""Delay should be in send_variables."""
|
|
assert "Delay" in parser.send_variables
|
|
|
|
def test_contains_digout(self, parser):
|
|
"""Digout group should be parsed from dotted tags."""
|
|
assert "Digout" in parser.send_variables
|
|
|
|
def test_digout_has_channels(self, parser):
|
|
"""Digout should contain o1, o2, o3 sub-keys from the config."""
|
|
digout = parser.send_variables["Digout"]
|
|
assert isinstance(digout, dict)
|
|
for ch in ["o1", "o2", "o3"]:
|
|
assert ch in digout
|
|
|
|
def test_contains_source1(self, parser):
|
|
"""Source1 should be in send_variables as a DOUBLE default."""
|
|
assert "Source1" in parser.send_variables
|
|
assert parser.send_variables["Source1"] == 0.0
|
|
|
|
def test_contains_dil(self, parser):
|
|
"""DiL should be in send_variables as a LONG default."""
|
|
assert "DiL" in parser.send_variables
|
|
assert parser.send_variables["DiL"] == 0
|
|
|
|
|
|
class TestReceiveVariables:
|
|
"""Verify receive_variables contains expected keys."""
|
|
|
|
def test_contains_rkorr(self, parser):
|
|
"""RKorr (correction values) should be in receive_variables."""
|
|
assert "RKorr" in parser.receive_variables
|
|
|
|
def test_rkorr_has_cartesian_keys(self, parser):
|
|
"""RKorr should have X, Y, Z, A, B, C from individual tags."""
|
|
rkorr = parser.receive_variables["RKorr"]
|
|
assert isinstance(rkorr, dict)
|
|
for axis in ["X", "Y", "Z", "A", "B", "C"]:
|
|
assert axis in rkorr
|
|
|
|
def test_contains_estr(self, parser):
|
|
"""EStr should be in receive_variables."""
|
|
assert "EStr" in parser.receive_variables
|
|
|
|
def test_contains_free(self, parser):
|
|
"""FREE should be in receive_variables."""
|
|
assert "FREE" in parser.receive_variables
|
|
|
|
def test_contains_dio(self, parser):
|
|
"""DiO should be in receive_variables."""
|
|
assert "DiO" in parser.receive_variables
|
|
|
|
|
|
class TestDefPrefixStripping:
|
|
"""Verify that DEF_ prefix is stripped from tags."""
|
|
|
|
def test_rist_not_def_rist(self, parser):
|
|
"""Key should be 'RIst', not 'DEF_RIst'."""
|
|
assert "DEF_RIst" not in parser.send_variables
|
|
assert "RIst" in parser.send_variables
|
|
|
|
def test_rsol_not_def_rsol(self, parser):
|
|
"""Key should be 'RSol', not 'DEF_RSol'."""
|
|
assert "DEF_RSol" not in parser.send_variables
|
|
assert "RSol" in parser.send_variables
|
|
|
|
def test_estr_not_def_estr(self, parser):
|
|
"""Key should be 'EStr', not 'DEF_EStr'."""
|
|
assert "DEF_EStr" not in parser.receive_variables
|
|
assert "EStr" in parser.receive_variables
|
|
|
|
def test_tech_not_def_tech(self, parser):
|
|
"""Tech keys should not retain DEF_ prefix."""
|
|
for key in parser.send_variables:
|
|
assert not key.startswith("DEF_")
|
|
for key in parser.receive_variables:
|
|
assert not key.startswith("DEF_")
|
|
|
|
|
|
class TestIPOCAlwaysPresent:
|
|
"""IPOC must always be in send_variables, even if not in config."""
|
|
|
|
def test_ipoc_present(self, parser):
|
|
"""IPOC should be in send_variables."""
|
|
assert "IPOC" in parser.send_variables
|
|
|
|
def test_ipoc_forced_when_missing(self, tmp_path):
|
|
"""IPOC should be injected even if the config has no DEF_IPOC."""
|
|
# Create a minimal config with no IPOC element
|
|
minimal_xml = tmp_path / "minimal.xml"
|
|
minimal_xml.write_text("""<ROOT>
|
|
<CONFIG>
|
|
<IP_NUMBER>127.0.0.1</IP_NUMBER>
|
|
<PORT>12345</PORT>
|
|
<SENTYPE>Test</SENTYPE>
|
|
<ONLYSEND>FALSE</ONLYSEND>
|
|
</CONFIG>
|
|
<SEND><ELEMENTS>
|
|
<ELEMENT TAG="DEF_RIst" TYPE="DOUBLE" INDX="INTERNAL" />
|
|
</ELEMENTS></SEND>
|
|
<RECEIVE><ELEMENTS>
|
|
<ELEMENT TAG="RKorr.X" TYPE="DOUBLE" INDX="1" />
|
|
</ELEMENTS></RECEIVE>
|
|
</ROOT>""")
|
|
p = ConfigParser(str(minimal_xml))
|
|
assert "IPOC" in p.send_variables
|
|
|
|
|
|
class TestTechKeyMerging:
|
|
"""Verify that Tech.CX and Tech.TX keys merge into a single 'Tech' dict."""
|
|
|
|
def test_tech_merged_in_send(self, parser):
|
|
"""Tech.C1 in send config should merge into a single 'Tech' key."""
|
|
assert "Tech" in parser.send_variables
|
|
# Should not have the dotted form
|
|
for key in parser.send_variables:
|
|
assert not key.startswith("Tech.")
|
|
|
|
def test_tech_merged_in_receive(self, parser):
|
|
"""Tech.T2 in receive config should merge into a single 'Tech' key."""
|
|
assert "Tech" in parser.receive_variables
|
|
for key in parser.receive_variables:
|
|
assert not key.startswith("Tech.")
|
|
|
|
def test_tech_contains_sub_keys(self, parser):
|
|
"""Merged Tech dict should contain the sub-keys from the internal structure."""
|
|
tech_send = parser.send_variables["Tech"]
|
|
assert isinstance(tech_send, dict)
|
|
# Tech.C1 should have expanded to C11, C12, ... C110
|
|
assert "C11" in tech_send
|
|
|
|
def test_tech_receive_contains_sub_keys(self, parser):
|
|
"""Tech in receive should have T2x keys from Tech.T2."""
|
|
tech_recv = parser.receive_variables["Tech"]
|
|
assert isinstance(tech_recv, dict)
|
|
assert "T21" in tech_recv
|
|
|
|
|
|
class TestGetDefaultValue:
|
|
"""Test the static get_default_value helper."""
|
|
|
|
def test_bool_default(self):
|
|
assert ConfigParser.get_default_value("BOOL") is False
|
|
|
|
def test_string_default(self):
|
|
assert ConfigParser.get_default_value("STRING") == ""
|
|
|
|
def test_long_default(self):
|
|
assert ConfigParser.get_default_value("LONG") == 0
|
|
|
|
def test_double_default(self):
|
|
assert ConfigParser.get_default_value("DOUBLE") == 0.0
|
|
|
|
def test_unknown_default(self):
|
|
assert ConfigParser.get_default_value("UNKNOWN") is None
|
|
|
|
|
|
class TestRenameTechKeys:
|
|
"""Test the static rename_tech_keys helper."""
|
|
|
|
def test_merges_tech_keys(self):
|
|
d = {"Tech.C1": {"C11": 0}, "Tech.T2": {"T21": 0}, "Other": 5}
|
|
ConfigParser.rename_tech_keys(d)
|
|
assert "Tech" in d
|
|
assert "Tech.C1" not in d
|
|
assert "Tech.T2" not in d
|
|
assert "C11" in d["Tech"]
|
|
assert "T21" in d["Tech"]
|
|
assert d["Other"] == 5
|
|
|
|
def test_no_tech_keys_leaves_dict_unchanged(self):
|
|
d = {"RIst": {"X": 0}, "IPOC": 0}
|
|
ConfigParser.rename_tech_keys(d)
|
|
assert "Tech" not in d
|
|
assert "RIst" in d
|