# Copyright 2024 Volvo Car Corporation # Licensed under Apache 2.0. """Unit test script for powertrain_build.zone_controller.generate_yaml.""" import copy import os import unittest from pathlib import Path from unittest.mock import MagicMock, patch from powertrain_build.build_proj_config import BuildProjConfig from powertrain_build.core import ZCCore from powertrain_build.dids import ZCDIDs from powertrain_build.unit_configs import UnitConfigs from powertrain_build.zone_controller.composition_yaml import CompositionYaml from test_data.zone_controller.test_composition_yaml import ( composition_yaml_setup, composition_yaml, composition_yaml_with_a2l_axis_data, composition_yaml_with_calls_all_fields, composition_yaml_with_calls_no_optional_fields, composition_yaml_with_dids, composition_yaml_with_dtcs, composition_yaml_with_external_io, composition_yaml_with_nvm, prepare_for_xml, ) SRC_DIR = Path(__file__).parent class BuildProjConfigMock(BuildProjConfig): """Class mocking BuildProjConfig.""" name = "" def mock_get_code_generation_config_default(item): """Function to mock BuildProjConfig.get_code_generation_config.""" return { "generalAsilLevelDebug": "B", "generalAsilLevelDependability": "B", "generateCalibrationInterfaceFiles": False, "useCalibrationRteMacroExpansion": False, "generateCoreDummy": False, "generateDummyVar": False, "generateInterfaceHeaders": False, "generateRteCheckpointIds": False, "generateYamlInterfaceFile": False, "includeAllEnums": False, "mapToRteEnums": False, "propagateTagName": False, "useA2lSymbolLinks": False, "useRteNvmStructs": False, }[item] def mock_get_code_generation_config_calibration_interface(item): """Function to mock BuildProjConfig.get_code_generation_config.""" return { "generalAsilLevelDebug": "B", "generalAsilLevelDependability": "B", "generateCalibrationInterfaceFiles": True, "useCalibrationRteMacroExpansion": False, "generateCoreDummy": False, "generateDummyVar": False, "generateInterfaceHeaders": False, "generateRteCheckpointIds": False, "generateYamlInterfaceFile": False, "includeAllEnums": False, "mapToRteEnums": False, "propagateTagName": False, "useA2lSymbolLinks": False, "useRteNvmStructs": False, }[item] def mock_get_composition_config_default(key): """Function to mock BuildProjConfig.get_composition_config.""" return { "compositionArxml": "some_arxml.arxml", "compositionName": "compositionName", "compositionEnding": "yml", "softwareComponentName": "testName_SC", "softwareComponentTemplate": "ARTCSC", "asil": "QM", "secure": False, "customYamlInitFunctionName": None, "customYamlStepFunctionName": None, "generateExternalImplementationType": True, "includeStatic": True, "includeShared": True, "includeDiagnostics": True, "includeNvm": True, "scaleMapsAndCurves": True, }[key] def mock_get_composition_config_custom_names(key): """Function to mock BuildProjConfig.get_composition_config.""" return { "compositionArxml": "some_arxml.arxml", "compositionName": "compositionName", "compositionEnding": "yml", "softwareComponentName": "testName_SC", "softwareComponentTemplate": "ARTCSC", "asil": "QM", "secure": False, "customYamlInitFunctionName": "dummy_init", "customYamlStepFunctionName": "dummy_step", "generateExternalImplementationType": True, "includeStatic": True, "includeShared": True, "includeDiagnostics": True, "includeNvm": True, "scaleMapsAndCurves": True, }[key] class TestCompositionYaml(unittest.TestCase): """Test case for testing composition_yaml.""" def setUp(self): """Set-up common data structures for all tests in the test case.""" self.build_cfg = MagicMock(spec_set=BuildProjConfigMock) self.build_cfg.get_code_generation_config.side_effect = mock_get_code_generation_config_default self.build_cfg.get_composition_config.side_effect = mock_get_composition_config_default self.build_cfg.name = "XVC" self.build_cfg.get_scheduler_prefix = MagicMock(return_value="prefix_") self.build_cfg.get_src_code_dst_dir = MagicMock( return_value=os.path.abspath("output") ) self.build_cfg.get_units_raster_cfg = MagicMock( return_value=({"SampleTimes": {"testRunnable": 10}}) ) self.unit_cfg = MagicMock(spec_set=UnitConfigs) self.unit_cfg.get_per_cfg_unit_cfg.return_value = copy.deepcopy( composition_yaml_setup.get_per_cfg_unit_cfg_return_value ) with patch.object(ZCCore, "_get_project_dtcs", return_value=set()): self.zc_core = ZCCore(self.build_cfg, self.unit_cfg) with patch.object(ZCDIDs, "_get_project_dids", return_value={}): self.zc_dids = ZCDIDs(self.build_cfg, self.unit_cfg) self.signal_interfaces = MagicMock() self.signal_interfaces.composition_spec = copy.deepcopy(composition_yaml_setup.composition_spec) self.nvm_def = MagicMock() self.nvm_def.struct_member_prefix = "e_" self.calibration_definitions = copy.deepcopy(composition_yaml_setup.calibration_definitions) with patch.object( CompositionYaml, "_get_all_calibration_definitions", return_value=self.calibration_definitions ): self.composition_yaml = CompositionYaml( self.build_cfg, self.signal_interfaces, self.unit_cfg, self.zc_core, self.zc_dids, self.nvm_def, {}, {} ) def test_prepare_for_xml(self): """Checking that the XML preparation is done correctly.""" # Normal result = self.composition_yaml._prepare_for_xml("dummy", prepare_for_xml.description) self.assertEqual(result, prepare_for_xml.description) # Illegal result = self.composition_yaml._prepare_for_xml("dummy", prepare_for_xml.illegal_description) self.assertEqual(result, prepare_for_xml.fixed_illegal_description) # Long normal result = self.composition_yaml._prepare_for_xml("dummy", prepare_for_xml.long_description) expected = "".join(prepare_for_xml.long_description.splitlines())[:255] self.assertEqual(result, expected) # Long illegal result = self.composition_yaml._prepare_for_xml("dummy", prepare_for_xml.long_illegal_description) self.assertEqual(result, prepare_for_xml.fixed_long_illegal_description) # Long illegal description which is truncated in the middle of an XML tag result = self.composition_yaml._prepare_for_xml("dummy", prepare_for_xml.long_illegal_description_truncated_tag) self.assertEqual(result, prepare_for_xml.fixed_long_illegal_description_truncated_tag) def test_composition_yaml(self): """Checking that the dict is generated correctly""" result = self.composition_yaml.gather_yaml_info() self.assertDictEqual(composition_yaml.expected_result, result) def test_composition_yaml_extra_runnable_keys(self): """Checking that the dict is generated correctly with extra runnable keys.""" self.signal_interfaces.composition_spec["mode_switch_points"] = ["DummyPort"] self.composition_yaml = CompositionYaml( self.build_cfg, self.signal_interfaces, self.unit_cfg, self.zc_core, self.zc_dids, self.nvm_def, {}, {} ) result = self.composition_yaml.gather_yaml_info() self.assertDictEqual(composition_yaml.expected_extra_runnable_keys_result, result) def test_composition_yaml_with_custom_names(self): """Checking that the dict is generated correctly with custom names.""" self.build_cfg.get_composition_config.side_effect = mock_get_composition_config_custom_names self.composition_yaml = CompositionYaml( self.build_cfg, self.signal_interfaces, self.unit_cfg, self.zc_core, self.zc_dids, self.nvm_def, {}, {} ) result = self.composition_yaml.gather_yaml_info() self.assertDictEqual(composition_yaml.expected_custom_names_result, result) def test_composition_yaml_with_calibration(self): """Checking that the dict is generated correctly including calibration data, setting generateCalibrationInterfaceFiles to true.""" self.build_cfg.get_code_generation_config.side_effect = mock_get_code_generation_config_calibration_interface with patch.object( CompositionYaml, "_get_all_calibration_definitions", return_value=self.calibration_definitions ): self.composition_yaml = CompositionYaml( self.build_cfg, self.signal_interfaces, self.unit_cfg, self.zc_core, self.zc_dids, self.nvm_def, {}, {} ) result = self.composition_yaml.gather_yaml_info() self.assertDictEqual(composition_yaml.expected_cal_result, result) def test_composition_yaml_with_calibration_and_rte_macro(self): """Checking that the dict is generated correctly including calibration data, setting both generateCalibrationInterfaceFiles and useCalibrationRteMacroExpansion to true (sort of).""" self.build_cfg.get_code_generation_config = MagicMock(return_value=True) with patch.object( CompositionYaml, "_get_all_calibration_definitions", return_value=self.calibration_definitions ): self.composition_yaml = CompositionYaml( self.build_cfg, self.signal_interfaces, self.unit_cfg, self.zc_core, self.zc_dids, self.nvm_def, {}, {} ) result = self.composition_yaml.gather_yaml_info() self.assertDictEqual(composition_yaml.expected_result, result) def test_composition_yaml_with_a2l_axis_data(self): """Checking that the dict is generated correctly, including a2l axis data.""" self.unit_cfg.get_per_cfg_unit_cfg.return_value = \ composition_yaml_with_a2l_axis_data.get_per_cfg_unit_cfg_return_value calibration_definitions = \ self.calibration_definitions + composition_yaml_with_a2l_axis_data.calibration_definitions with patch.object(CompositionYaml, "_get_all_calibration_definitions", return_value=calibration_definitions): self.composition_yaml = CompositionYaml( self.build_cfg, self.signal_interfaces, self.unit_cfg, self.zc_core, self.zc_dids, self.nvm_def, composition_yaml_with_a2l_axis_data.a2l_axis_data, {} ) result = self.composition_yaml.gather_yaml_info() self.assertDictEqual(composition_yaml_with_a2l_axis_data.expected_result, result) def test_composition_yaml_with_calls_all_fields(self): """Checking that the dict is generated correctly, with calls including all fields.""" self.signal_interfaces.composition_spec["calls"] = { "CallOne": { "interface": "InterfaceOne", "direction": "IN", "operation": "OperationOne", "timeout": 0.1, } } with patch.object( CompositionYaml, "_get_all_calibration_definitions", return_value=self.calibration_definitions ): self.composition_yaml = CompositionYaml( self.build_cfg, self.signal_interfaces, self.unit_cfg, self.zc_core, self.zc_dids, self.nvm_def, {}, {} ) result = self.composition_yaml.gather_yaml_info() self.assertDictEqual(composition_yaml_with_calls_all_fields.expected_result, result) def test_composition_yaml_with_calls_no_optional_fields(self): """Checking that the dict is generated correctly, with calls without optional fields.""" self.signal_interfaces.composition_spec["calls"] = { "CallOne": { "direction": "IN", "operation": "OperationOne", } } with patch.object( CompositionYaml, "_get_all_calibration_definitions", return_value=self.calibration_definitions ): self.composition_yaml = CompositionYaml( self.build_cfg, self.signal_interfaces, self.unit_cfg, self.zc_core, self.zc_dids, self.nvm_def, {}, {} ) result = self.composition_yaml.gather_yaml_info() self.assertDictEqual(composition_yaml_with_calls_no_optional_fields.expected_result, result) def test_composition_yaml_with_dids(self): """Checking that the dict is generated correctly, with DIDs.""" self.zc_dids.project_dids = {"DID1": {"type": "UInt8"}} self.signal_interfaces.composition_spec["diagnostics"] = composition_yaml_with_dids.diagnostics with patch.object( CompositionYaml, "_get_all_calibration_definitions", return_value=self.calibration_definitions ): self.composition_yaml = CompositionYaml( self.build_cfg, self.signal_interfaces, self.unit_cfg, self.zc_core, self.zc_dids, self.nvm_def, {}, {} ) result = self.composition_yaml.gather_yaml_info() self.assertDictEqual(composition_yaml_with_dids.expected_result, result) def test_composition_yaml_with_dtcs(self): """Checking that the dict is generated correctly, with DTCs.""" self.zc_core.project_dtcs = {"DTC1"} self.signal_interfaces.composition_spec["diagnostics"] = composition_yaml_with_dtcs.diagnostics with patch.object( CompositionYaml, "_get_all_calibration_definitions", return_value=self.calibration_definitions ): self.composition_yaml = CompositionYaml( self.build_cfg, self.signal_interfaces, self.unit_cfg, self.zc_core, self.zc_dids, self.nvm_def, {}, {} ) result = self.composition_yaml.gather_yaml_info() self.assertDictEqual(composition_yaml_with_dtcs.expected_result, result) def test_composition_yaml_with_nvm(self): """Checking that the dict is generated correctly, with NVM.""" self.nvm_def.nvm_definitions = composition_yaml_with_nvm.project_nvm_definitions self.nvm_def.project_nvm_definitions = { f"prefix_{item['name']}": item for item in self.nvm_def.nvm_definitions } self.signal_interfaces.composition_spec["nv-needs"] = composition_yaml_with_nvm.yaml_nvm_definitions with patch.object( CompositionYaml, "_get_all_calibration_definitions", return_value=self.calibration_definitions ): self.composition_yaml = CompositionYaml( self.build_cfg, self.signal_interfaces, self.unit_cfg, self.zc_core, self.zc_dids, self.nvm_def, {}, {} ) result = self.composition_yaml.gather_yaml_info() self.assertDictEqual(composition_yaml_with_nvm.expected_result, result) def test_composition_yaml_with_external_io(self): """Checking that the dict is generated correctly, with external IO.""" self.signal_interfaces.get_external_io.return_value = composition_yaml_with_external_io.external_io with patch.object( CompositionYaml, "_get_all_calibration_definitions", return_value=self.calibration_definitions ): self.composition_yaml = CompositionYaml( self.build_cfg, self.signal_interfaces, self.unit_cfg, self.zc_core, self.zc_dids, self.nvm_def, {}, {} ) result = self.composition_yaml.gather_yaml_info() self.assertDictEqual(composition_yaml_with_external_io.expected_result, result) def test_get_init_values_expecting_failure(self): """Test CompositionYaml.get_init_values with a non-existing calibration definition.""" self.composition_yaml.clear_log() json_variables = {"signal_name": "dummy"} c_definitions = ["CVC_CAL Float32 signal_name_other = 1.F; "] with patch.object(CompositionYaml, "_get_all_calibration_definitions", return_value=c_definitions): init_values = self.composition_yaml.get_init_values(json_variables) logged_problems = self.composition_yaml.get_problems() self.assertEqual(init_values, {}) self.assertEqual(logged_problems["warning"], []) self.assertEqual(logged_problems["critical"], ["Missing init values for calibration variables:\nsignal_name"])