diff --git a/examples/somersault.pdx b/examples/somersault.pdx index a17710ae..874e58c8 100644 Binary files a/examples/somersault.pdx and b/examples/somersault.pdx differ diff --git a/examples/somersaultecu.py b/examples/somersaultecu.py index 831398b6..a94721b1 100755 --- a/examples/somersaultecu.py +++ b/examples/somersaultecu.py @@ -713,6 +713,18 @@ class SomersaultSID(IntEnum): bit_position=None, sdgs=[], ), + ValueParameter( + short_name="sault_time", + long_name=None, + semantic=None, + description=None, + physical_default_value_raw="255", + byte_position=2, + dop_ref=OdxLinkRef("somersault.DOP.duration", doc_frags), + dop_snref=None, + bit_position=None, + sdgs=[], + ), ]), byte_size=None, ), diff --git a/odxtools/basicstructure.py b/odxtools/basicstructure.py index 6f459075..02e9b102 100644 --- a/odxtools/basicstructure.py +++ b/odxtools/basicstructure.py @@ -8,7 +8,7 @@ from .dataobjectproperty import DataObjectProperty from .decodestate import DecodeState from .encodestate import EncodeState -from .exceptions import DecodeError, EncodeError, OdxWarning, odxassert, odxraise +from .exceptions import DecodeError, EncodeError, OdxWarning, odxassert, odxraise, strict_mode from .nameditemlist import NamedItemList from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId from .odxtypes import ParameterDict, ParameterValue, ParameterValueDict @@ -124,6 +124,13 @@ def convert_physical_to_internal(self, f"Expected a dictionary for the values of structure {self.short_name}, " f"got {type(param_value)}") + # in strict mode, ensure that no values for unknown parameters are specified. + if strict_mode: + param_names = [param.short_name for param in self.parameters] + for param_key in param_value: + if param_key not in param_names: + odxraise(f"Value for unknown parameter '{param_key}' specified") + encode_state = EncodeState( b'', dict(param_value), diff --git a/tests/test_somersault.py b/tests/test_somersault.py index 516d53cf..5397dfd7 100644 --- a/tests/test_somersault.py +++ b/tests/test_somersault.py @@ -1,7 +1,7 @@ # SPDX-License-Identifier: MIT import unittest -from odxtools.exceptions import odxrequire +from odxtools.exceptions import OdxError, odxrequire from odxtools.load_pdx_file import load_pdx_file from odxtools.parameters.nrcconstparameter import NrcConstParameter @@ -145,9 +145,11 @@ def test_somersault_lazy(self) -> None: self.assertEqual([x.short_name for x in service.negative_responses], ["flips_not_done"]) pr = service.positive_responses.grudging_forward - self.assertEqual([x.short_name for x in pr.parameters], ["sid", "num_flips_done"]) + self.assertEqual([x.short_name for x in pr.parameters], + ["sid", "num_flips_done", "sault_time"]) self.assertEqual([x.short_name for x in pr.required_parameters], []) - self.assertEqual(pr.get_static_bit_length(), 16) + self.assertEqual([x.short_name for x in pr.free_parameters], ["sault_time"]) + self.assertEqual(pr.get_static_bit_length(), 24) nr = service.negative_responses.flips_not_done self.assertEqual( @@ -162,7 +164,16 @@ def test_somersault_lazy(self) -> None: self.assertEqual(nrc_const.coded_values, [0, 1, 2]) -class TestDecode(unittest.TestCase): +class TestEnDecode(unittest.TestCase): + + def test_encode_specify_unknown_param(self) -> None: + ecu = odxdb.ecus.somersault_lazy + service = ecu.services.do_forward_flips + request = odxrequire(service.request) + with self.assertRaises(OdxError) as eo: + request.encode(forward_soberness_check=0x12, num_flips=5, grass_level="what grass?") + + self.assertEqual(str(eo.exception), "Value for unknown parameter 'grass_level' specified") def test_decode_request(self) -> None: messages = odxdb.ecus.somersault_assiduous.decode(bytes([0x03, 0x45])) @@ -226,9 +237,9 @@ def test_code_table_params(self) -> None: dizzyness_level=42, happiness_level=92, last_pos_response=("forward_grudging", { - "dizzyness_level": 42 + "sault_time": 249 })) - self.assertEqual(resp_data.hex(), "622a5c03fa7b") + self.assertEqual(resp_data.hex(), "622a5c03fa7bf9") decoded_resp_data = pr.decode(resp_data) assert isinstance(decoded_resp_data, dict) @@ -241,7 +252,7 @@ def test_code_table_params(self) -> None: self.assertEqual( set(decoded_resp_data["last_pos_response"] [1].keys()), # type: ignore[index, union-attr] - {"sid", "num_flips_done"}) + {"sid", "num_flips_done", "sault_time"}) # the num_flips_done parameter is a matching request parameter # for this response, so it produces a binary blob. possibly, # it should be changed to a ValueParameter... @@ -249,13 +260,16 @@ def test_code_table_params(self) -> None: decoded_resp_data["last_pos_response"][1] # type: ignore[index, call-overload] ["num_flips_done"], # type: ignore[index, call-overload] bytes([123])) + self.assertEqual( + decoded_resp_data["last_pos_response"][1] # type: ignore[index, call-overload] + ["sault_time"], # type: ignore[index, call-overload] + 249) # test the "backward flips grudgingly done" response resp_data = pr.encode( dizzyness_level=75, happiness_level=3, last_pos_response=("backward_grudging", { - 'dizzyness_level': 75, 'num_flips_done': 5, 'grumpiness_level': 150 })) @@ -310,14 +324,14 @@ def test_free_param_info(self) -> None: with patch("sys.stdout", stdout): pos_response.print_free_parameters_info() - expected_output = "forward_soberness_check: uint8\nnum_flips: uint8\n" + expected_output = "forward_soberness_check: uint8\nnum_flips: uint8\nsault_time: uint8\n" actual_output = stdout.getvalue() self.assertEqual(actual_output, expected_output) with patch("sys.stdout", stdout): neg_response.print_free_parameters_info() expected_output = ( - "forward_soberness_check: uint8\nnum_flips: uint8\nflips_successfully_done: uint8\n" + "forward_soberness_check: uint8\nnum_flips: uint8\nsault_time: uint8\nflips_successfully_done: uint8\n" ) actual_output = stdout.getvalue() self.assertEqual(actual_output, expected_output) @@ -336,9 +350,13 @@ def test_decode_response(self) -> None: f"There should be only one service for 0x0145 but there are: {messages}", ) m = messages[0] - self.assertEqual(m.coded_message, bytes([0xFA, 0x03])) + self.assertEqual(m.coded_message.hex(), "fa03ff") self.assertEqual(m.coding_object, pos_response) - self.assertEqual(m.param_dict, {"sid": 0xFA, "num_flips_done": bytearray([0x03])}) + self.assertEqual(m.param_dict, { + "sid": 0xFA, + "num_flips_done": bytearray([0x03]), + "sault_time": 255 + }) class TestNavigation(unittest.TestCase):