From a7b1979768c7eb6f4d839fc1ee791f5d467142ce Mon Sep 17 00:00:00 2001 From: Henrik Wahlqvist Date: Tue, 2 Jul 2024 16:27:52 +0200 Subject: [PATCH] Add feature for ZC DIDs and DTCs Change-Id: I170192eb10727b8c569920f925ccfc1a7f82ca08 --- pybuild/build.py | 15 +- pybuild/core.py | 103 ++++++++++ pybuild/dids.py | 178 ++++++++++++++++++ pybuild/interface/zone_controller.py | 2 +- pybuild/zone_controller/composition_yaml.py | 48 ++++- test_data/pybuild/test_dids/zc_dids.py | 155 +++++++++++++++ .../test_composition_yaml/composition_yaml.py | 1 + .../composition_yaml_with_a2l_axis_data.py | 1 + .../composition_yaml_with_calls_all_fields.py | 1 + ...tion_yaml_with_calls_no_optional_fields.py | 1 + .../composition_yaml_with_dids.py | 81 ++++++++ .../composition_yaml_with_dtcs.py | 71 +++++++ tests/pybuild/test_core.py | 107 ++++++++++- tests/pybuild/test_dids.py | 71 ++++++- .../zone_controller/test_composition_yaml.py | 56 +++++- 15 files changed, 879 insertions(+), 12 deletions(-) create mode 100644 test_data/pybuild/test_dids/zc_dids.py create mode 100644 test_data/zone_controller/test_composition_yaml/composition_yaml_with_dids.py create mode 100644 test_data/zone_controller/test_composition_yaml/composition_yaml_with_dtcs.py diff --git a/pybuild/build.py b/pybuild/build.py index 67df554..3244ff3 100644 --- a/pybuild/build.py +++ b/pybuild/build.py @@ -20,10 +20,10 @@ from pathlib import Path from pybuild import __config_version__, __version__, build_defs from pybuild.a2l_merge import A2lMerge from pybuild.build_proj_config import BuildProjConfig -from pybuild.core import Core, HICore +from pybuild.core import Core, HICore, ZCCore from pybuild.core_dummy import CoreDummy from pybuild.create_conversion_table import create_conversion_table -from pybuild.dids import DIDs, HIDIDs +from pybuild.dids import DIDs, HIDIDs, ZCDIDs from pybuild.dummy import DummyVar from pybuild.dummy_spm import DummySpm from pybuild.ext_dbg import ExtDbg @@ -834,6 +834,11 @@ def build( LOG.info("Generating DID files") dids = HIDIDs(build_cfg, unit_cfg) dids.generate_did_files() + elif ecu_supplier in ["ZC"]: + LOG.info("******************************************************") + LOG.info("Generating Core header") + zc_core = ZCCore(build_cfg, unit_cfg) + zc_core.generate_dtc_files() else: generate_did_files(build_cfg, unit_cfg) # generate core dummy files if requested @@ -920,15 +925,19 @@ def build( create_conversion_table(ctable_json, ctable_a2l) merged_a2l = merge_a2l_files(build_cfg, unit_cfg, complete_a2l, silver_a2l) if ecu_supplier in ["ZC"]: + zc_dids = ZCDIDs(build_cfg, unit_cfg) axis_data = merged_a2l.get_characteristic_axis_data() composition_yaml = CompositionYaml( - build_cfg, signal_if.composition_spec, unit_cfg, axis_data + build_cfg, signal_if.composition_spec, unit_cfg, zc_core, zc_dids, axis_data ) composition_yaml.generate_yaml() zc_calibration = ZoneControllerCalibration( build_cfg, composition_yaml.cal_class_info["tl"] ) zc_calibration.generate_calibration_interface_files() + LOG.info("******************************************************") + LOG.info("Generating DID files") + zc_dids.generate_did_files() a2l_file_path = Path(build_cfg.get_src_code_dst_dir(), build_cfg.get_a2l_name()) replace_tab_verb(a2l_file_path) diff --git a/pybuild/core.py b/pybuild/core.py index 76792f8..1b4b2d0 100644 --- a/pybuild/core.py +++ b/pybuild/core.py @@ -294,3 +294,106 @@ class HICore(ProblemLogger): file_path = Path(src_dst_dir, self.FILE_NAME + extension) with file_path.open(mode='w', encoding='utf-8') as file_handler: file_handler.writelines(content) + + +class ZCCore(ProblemLogger): + """A class holding ZC core configuration data.""" + + FILE_NAME = 'VcCoreSupplierAbstraction' + + def __init__(self, project_cfg, unit_cfgs): + """Parse the config files to an internal representation. + + Args: + project_cfg (BuildProjConfig): Project configuration. + unit_cfgs (UnitConfigs): Unit definitions. + """ + super().__init__() + self._prj_cfg = project_cfg + self._unit_cfgs = unit_cfgs + self.project_dtcs = self._get_project_dtcs() + + def _get_project_dtcs(self): + """Return a set with DTCs in the currently included SW-Units. + + Returns: + (set): Set of DTCs in the currently included SW-Units. + """ + project_dtcs = set() + unit_cfg = self._unit_cfgs.get_per_unit_cfg() + for unit, data in unit_cfg.items(): + event_data = data.get('core', {}).get('Events') + if event_data is None: + self.critical(f'No "core" or "core.Events" key in unit config for {unit}.') + continue + project_dtcs |= set(event_data.keys()) + return project_dtcs + + def get_diagnostic_trouble_codes(self, event_data): + """Return a set of DTCs appearing in both the project and the project yaml file. + + Args: + event_data (dict): Diagnositc event data. + Returns: + (dict): Dict of DTCs, project yaml dict where the keys also appear in the project. + """ + yaml_dtcs = set(event_data.keys()) + dtcs_not_in_yaml = self.project_dtcs - yaml_dtcs + for key in dtcs_not_in_yaml: + self.warning(f'Ignoring DTC {key} since it does not appear in the project diagnostics yaml file.') + + valid_dtcs = {} + supported_operations = {"SetEventStatus"} + for dtc_name, dtc_data in event_data.items(): + if dtc_name not in self.project_dtcs: + self.warning(f'Ignoring DTC {dtc_name}, not defined in any model.') + continue + + valid_dtcs[dtc_name] = dtc_data + + operations = set(dtc_data["operations"]) + if not operations.issubset(supported_operations): + self.warning(f'Ignoring unsupported operations {supported_operations - operations} for DTC {dtc_name}.') + valid_dtcs[dtc_name]["operations"] = list(operations & supported_operations) + + return valid_dtcs + + def get_header_content(self): + """Get content for the DTC header file. + + Returns: + (list(str)): List of lines to write to the DTC header file. + """ + name = self._prj_cfg.get_swc_name() + header_guard = f'{self.FILE_NAME.upper()}_H' + header = [ + f'#ifndef {header_guard}\n', + f'#define {header_guard}\n', + '\n', + '/* Core API Supplier Abstraction */\n', + '\n', + '#include "tl_basetypes.h"\n', + f'#include "Rte_{name}.h"\n', + '\n' + ] + footer = [f'\n#endif /* {header_guard} */\n'] + + body = [ + '/* enum EventStatus {passed=0, failed=1, prepassed=2, prefailed=3} */\n', + '#define Dem_SetEventStatus(EventName, EventStatus)', + ' ', + 'Rte_Call_Event_##EventName##_SetEventStatus(EventStatus)\n' + ] + return header + body + footer + + def generate_dtc_files(self): + """Generate required ZC Core header files. + Only use for some projects, which doesn't copy static code.""" + file_contents = { + '.h': self.get_header_content() + } + src_dst_dir = self._prj_cfg.get_src_code_dst_dir() + for extension, content in file_contents.items(): + file_path = Path(src_dst_dir, self.FILE_NAME + extension) + with file_path.open(mode='w', encoding='utf-8') as file_handler: + file_handler.writelines(content) diff --git a/pybuild/dids.py b/pybuild/dids.py index 962f0a7..14ac85f 100644 --- a/pybuild/dids.py +++ b/pybuild/dids.py @@ -602,3 +602,181 @@ class HIDIDs(ProblemLogger): file_path = Path(src_dst_dir, self.FILE_NAME + extension) with file_path.open(mode='w', encoding='utf-8') as file_handler: file_handler.writelines(content) + + +class ZCDIDs(ProblemLogger): + """A class for handling of ZC DID definitions.""" + + FILE_NAME = 'VcDIDAPI' + + def __init__(self, build_cfg, unit_cfgs): + """Init. + + Args: + build_cfg (BuildProjConfig): Project configuration + unit_cfgs (UnitConfigs): Unit definitions + """ + super().__init__() + self._build_cfg = build_cfg + self._unit_cfgs = unit_cfgs + self._valid_dids = None + self.project_dids = self._get_project_dids() + + @property + def valid_dids(self): + return self._valid_dids + + @valid_dids.setter + def valid_dids(self, yaml_dids): + """Return a set of DIDs appearing in both the project and the project yaml file. + + Args: + yaml_dids (dict): DIDs listed in the DID configuration yaml file. + Returns: + valid_dids (dict): Validated DIDs listed in both DID configuration yaml file as well as project. + """ + self._valid_dids = {} + + yaml_did_names = set(yaml_dids.keys()) + dids_not_in_project = yaml_did_names - set(self.project_dids.keys()) + for did in dids_not_in_project: + self.warning(f'Ignoring DID {did}, not defined in any model.') + + supported_operations = set(self.get_operation_data().keys()) + for did, did_data in self.project_dids.items(): + if did not in yaml_dids: + self.warning(f'DID {did} not defined in project diagnostics yaml file.') + continue + + yaml_operations = set(yaml_dids[did]['operations'].keys()) + if not yaml_operations.issubset(supported_operations): + self.warning(f'Ignoring unsupported operations {yaml_operations - supported_operations} for DID {did}.') + + operations = { + "operations": { + operation: {} for operation in supported_operations + } + } + + self._valid_dids[did] = deep_dict_update(did_data, operations) + + def _get_project_dids(self): + """Return a dict with DIDs defined in the project. + Throws a critical error if something goes wrong. + + Returns: + project_dids (dict): a dict with all dids in the project. + """ + get_did_error_messages, project_dids = get_dids_in_prj(self._unit_cfgs) + if get_did_error_messages: + self.critical('\n'.join(get_did_error_messages)) + return {} + return project_dids + + def get_operation_data(self, operation=None): + """Get operation function data of supported operations. + + Args: + operation (str): Operation to get data for. + Returns: + (dict): Operation function data. + """ + operation_data = { + 'ReadData': { + 'declaration': 'UInt8 Run_{did_name}_ReadData({data_type} *Data)', + 'body': ( + '{{\n' + ' *Data = {did_name};\n' + ' return 0U;\n' + '}}\n' + ), + } + } + if operation is None: + return operation_data + return operation_data[operation] + + def _get_header_file_content(self): + """Get content for the DID API header file. + + Returns: + (list(str)): List of lines to write to DID API header file. + """ + name = self._build_cfg.get_swc_name() + header_guard = f'{self.FILE_NAME.upper()}_H' + header = [ + f'#ifndef {header_guard}\n', + f'#define {header_guard}\n', + '\n', + '#include "tl_basetypes.h"\n', + f'#include "Rte_{name}.h"\n', + '\n' + ] + footer = [f'\n#endif /* {header_guard} */\n'] + + if not self.valid_dids: + return header + footer + + body = [f'#include "{build_defs.PREDECL_DISP_ASIL_D_START}"\n'] + for did_data in self.valid_dids.values(): + define = did_data["class"].split('/')[-1] # E.q. for ASIL D it is ASIL_D/CVC_DISP_ASIL_D + body.append(f'extern {define} {did_data["type"]} {did_data["name"]};\n') + body.append(f'#include "{build_defs.PREDECL_DISP_ASIL_D_END}"\n') + + body.append(f'\n#include "{build_defs.PREDECL_CODE_ASIL_D_START}"\n') + for did_data in self.valid_dids.values(): + body.extend( + [ + self.get_operation_data(operation)['declaration'].format( + did_name=did_data["name"], + data_type=did_data["type"] + ) + ';\n' for operation in did_data["operations"] + ] + ) + body.append(f'#include "{build_defs.PREDECL_CODE_ASIL_D_END}"\n') + + return header + body + footer + + def _get_source_file_content(self): + """Get content for the DID API source file. + + Returns: + (list(str)): List of lines to write to DID API source file. + """ + header = [ + f'#include "{self.FILE_NAME}.h"\n', + '\n' + ] + + if not self.valid_dids: + return header + + body = [f'#include "{build_defs.CVC_CODE_ASIL_D_START}"\n'] + for did_data in self.valid_dids.values(): + for operation in did_data["operations"]: + body.append( + self.get_operation_data(operation)['declaration'].format( + did_name=did_data["name"], + data_type=did_data["type"] + ) + '\n' + self.get_operation_data(operation)['body'].format(did_name=did_data["name"]) + ) + body.append(f'#include "{build_defs.CVC_CODE_ASIL_D_END}"\n') + + return header + body + + def generate_did_files(self): + """Generate required DID API files. + Only use for some projects, which doesn't copy static code.""" + if self.valid_dids is None: + self.critical('Valid DIDs not set. Cannot generate DID files.') + return + + file_contents = { + '.h': self._get_header_file_content(), + '.c': self._get_source_file_content() + } + src_dst_dir = self._build_cfg.get_src_code_dst_dir() + for extension, content in file_contents.items(): + file_path = Path(src_dst_dir, self.FILE_NAME + extension) + with file_path.open(mode='w', encoding='utf-8') as file_handler: + file_handler.writelines(content) diff --git a/pybuild/interface/zone_controller.py b/pybuild/interface/zone_controller.py index 0f55216..0cd14f5 100644 --- a/pybuild/interface/zone_controller.py +++ b/pybuild/interface/zone_controller.py @@ -142,7 +142,7 @@ class ZCAL(BaseApplication): raw = self.read_translation_files(definition) self.composition_spec = { - key: raw[key] for key in ("port_interfaces", "data_types", "calls") if key in raw + key: raw[key] for key in ("port_interfaces", "data_types", "calls", "Diagnostics") if key in raw } ports_info = {} for port_name, port in raw.get("ports", {}).items(): diff --git a/pybuild/zone_controller/composition_yaml.py b/pybuild/zone_controller/composition_yaml.py index 4ea2bef..5080fbb 100644 --- a/pybuild/zone_controller/composition_yaml.py +++ b/pybuild/zone_controller/composition_yaml.py @@ -15,13 +15,15 @@ from pybuild.zone_controller.calibration import ZoneControllerCalibration as ZCC class CompositionYaml(ProblemLogger): """Class for handling ZoneController composition yaml generation.""" - def __init__(self, build_cfg, composition_spec, unit_cfg, a2l_axis_data): + def __init__(self, build_cfg, composition_spec, unit_cfg, zc_core, zc_dids, a2l_axis_data): """Init. Args: build_cfg (BuildProjConfig): Object with build configuration settings. composition_spec (dict): Dict with port interface information. unit_cfg (UnitConfig): Object with unit configurations. + zc_core (ZCCore): Object with zone controller diagnositic event information. + zc_dids (ZCDIDs): Object with zone controller diagnostic DID information. a2l_axis_data (dict): Dict with characteristic axis data from A2L file. """ self.tl_to_autosar_base_types = { @@ -38,6 +40,8 @@ class CompositionYaml(ProblemLogger): self.unit_src_dirs = build_cfg.get_unit_src_dirs() self.composition_spec = composition_spec self.unit_cfg = unit_cfg + self.zc_core = zc_core + self.zc_dids = zc_dids self.a2l_axis_data = a2l_axis_data base_data_types = self.get_base_data_types() # Might not be necessary in the long run self.data_types = { @@ -294,6 +298,47 @@ class CompositionYaml(ProblemLogger): return trigger_signal + def _get_diagnostic_event_info(self, event_dict): + """Get diagnostic event information from an even dictionary. + + Args: + event_dict (dict): Dict with event information. + Returns: + valid_event_dict (dict): Dict with diagnostic event information supported by yaml2arxml script. + """ + valid_event_dict = {} + dtcs = self.zc_core.get_diagnostic_trouble_codes(event_dict) + for dtc_name, dtc_data in dtcs.items(): + valid_event_dict[dtc_name] = {"operations": dtc_data["operations"], "runnable": dtc_data["runnable"]} + return valid_event_dict + + def _get_diagnostic_did_info(self, did_dict): + """Get diagnostic DID information from a DID dictionary. + NOTE: This function sets the valid_dids property of the ZCDIDs object. + + Args: + did_dict (dict): Dict with DID information. + Returns: + valid_did_dict (dict): Dict with diagnostic DID information supported by yaml2arxml script. + """ + valid_did_dict = {} + self.zc_dids.valid_dids = did_dict + for did_name, did_data in self.zc_dids.valid_dids.items(): + valid_did_dict[did_name] = {"operations": did_data["operations"]} + return valid_did_dict + + def _get_diagnostic_info(self): + """Get diagnostic information from composition spec. + + Returns: + (dict): Dict containing diagnostic information. + """ + diag_dict = self.composition_spec.get("Diagnostics", {}) + return { + "events": self._get_diagnostic_event_info(diag_dict.get("events", {})), + "dids": self._get_diagnostic_did_info(diag_dict.get("dids", {})), + } + def _get_ports_info(self): """Creates a dict containing port information. @@ -385,6 +430,7 @@ class CompositionYaml(ProblemLogger): swcs[software_component_name]["shared"] = self.cal_class_info["autosar"]["class_info"] swcs[software_component_name]["static"] = self.meas_class_info["autosar"]["class_info"] swcs[software_component_name]["ports"] = self._get_ports_info() + swcs[software_component_name]["diagnostics"] = self._get_diagnostic_info() return swcs, data_types def _get_variables(self): diff --git a/test_data/pybuild/test_dids/zc_dids.py b/test_data/pybuild/test_dids/zc_dids.py new file mode 100644 index 0000000..6250f01 --- /dev/null +++ b/test_data/pybuild/test_dids/zc_dids.py @@ -0,0 +1,155 @@ +# Copyright 2024 Volvo Car Corporation +# Licensed under Apache 2.0. + +"""Unit test data for pybuild.dids.ZCDIDs.""" + +dummy_project_dids = { + 'dummy_did_one': { + 'handle': 'VcDummy/VcDummy/Subsystem/VcDummy/VcDummy/1_VcDummy/Rel', + 'name': 'dummy_did_one', + 'configs': '((ALWAYS_ACTIVE))', + 'description': 'Dummy DID', + 'type': 'UInt8', + 'unit': '', + 'offset': '', + 'lsb': '', + 'min': '-', + 'max': '-', + 'class': 'ASIL_D/CVC_DISP_ASIL_D' + }, + 'dummy_did_two': { + 'handle': 'VcDummyTwo/VcDummyTwo/Subsystem/VcDummyTwo/VcDummyTwo/1_VcDummyTwo/Rel', + 'name': 'dummy_did_two', + 'configs': '((ALWAYS_ACTIVE))', + 'description': 'Dummy DID number 2', + 'type': 'UInt8', + 'unit': '', + 'offset': '', + 'lsb': '', + 'min': '-', + 'max': '-', + 'class': 'ASIL_D/CVC_DISP_ASIL_D' + } +} + +valid_dids = { + 'dummy_did_one': { + 'operations': { + 'ReadData': {}, + }, + }, + 'dummy_did_two': { + 'operations': { + 'ReadData': {}, + }, + } +} + +bad_valid_dids = { + 'dummy_did_one': { + 'operations': { + 'ReadData': {'random_data': {}}, + 'WriteData': {'random_data': {}}, + 'ShortTermAdjustment': {'random_data': {}}, + 'ReturnControlToECU': {'random_data': {}}, + }, + }, + 'dummy_did_two': { + 'operations': { + 'ReadData': {'random_data': {}}, + 'WriteData': {'random_data': {}}, + 'ShortTermAdjustment': {'random_data': {}}, + 'ReturnControlToECU': {'random_data': {}}, + }, + }, + 'dummy_did_three': { + 'operations': { + 'ReadData': {'random_data': {}}, + 'WriteData': {'random_data': {}}, + 'ShortTermAdjustment': {'random_data': {}}, + 'ReturnControlToECU': {'random_data': {}}, + }, + }, +} + +test_valid_dids_setter_expected = { + 'dummy_did_one': { + 'handle': 'VcDummy/VcDummy/Subsystem/VcDummy/VcDummy/1_VcDummy/Rel', + 'name': 'dummy_did_one', + 'configs': '((ALWAYS_ACTIVE))', + 'description': 'Dummy DID', + 'type': 'UInt8', + 'unit': '', + 'offset': '', + 'lsb': '', + 'min': '-', + 'max': '-', + 'class': 'ASIL_D/CVC_DISP_ASIL_D', + 'operations': { + 'ReadData': {}, + }, + }, + 'dummy_did_two': { + 'handle': 'VcDummyTwo/VcDummyTwo/Subsystem/VcDummyTwo/VcDummyTwo/1_VcDummyTwo/Rel', + 'name': 'dummy_did_two', + 'configs': '((ALWAYS_ACTIVE))', + 'description': 'Dummy DID number 2', + 'type': 'UInt8', + 'unit': '', + 'offset': '', + 'lsb': '', + 'min': '-', + 'max': '-', + 'class': 'ASIL_D/CVC_DISP_ASIL_D', + 'operations': { + 'ReadData': {}, + }, + }, +} + +test_get_operation_data_expected = { + 'ReadData': { + 'declaration': 'UInt8 Run_{did_name}_ReadData({data_type} *Data)', + 'body': ( + '{{\n' + ' *Data = {did_name};\n' + ' return 0U;\n' + '}}\n' + ), + } +} + +TEST_GET_HEADER_FILE_CONTENT_EXPECTED = ( + '#ifndef VCDIDAPI_H\n' + '#define VCDIDAPI_H\n' + '\n' + '#include "tl_basetypes.h"\n' + '#include "Rte_DUMMY.h"\n' + '\n' + '#include "PREDECL_DISP_ASIL_D_START.h"\n' + 'extern CVC_DISP_ASIL_D UInt8 dummy_did_one;\n' + 'extern CVC_DISP_ASIL_D UInt8 dummy_did_two;\n' + '#include "PREDECL_DISP_ASIL_D_END.h"\n' + '\n#include "PREDECL_CODE_ASIL_D_START.h"\n' + 'UInt8 Run_dummy_did_one_ReadData(UInt8 *Data);\n' + 'UInt8 Run_dummy_did_two_ReadData(UInt8 *Data);\n' + '#include "PREDECL_CODE_ASIL_D_END.h"\n' + '\n#endif /* VCDIDAPI_H */\n' +) + +TEST_GET_SOURCE_FILE_CONTENT_EXPECTED = ( + '#include "VcDIDAPI.h"\n' + '\n' + '#include "CVC_CODE_ASIL_D_START.h"\n' + 'UInt8 Run_dummy_did_one_ReadData(UInt8 *Data)\n' + '{\n' + ' *Data = dummy_did_one;\n' + ' return 0U;\n' + '}\n' + 'UInt8 Run_dummy_did_two_ReadData(UInt8 *Data)\n' + '{\n' + ' *Data = dummy_did_two;\n' + ' return 0U;\n' + '}\n' + '#include "CVC_CODE_ASIL_D_END.h"\n' +) diff --git a/test_data/zone_controller/test_composition_yaml/composition_yaml.py b/test_data/zone_controller/test_composition_yaml/composition_yaml.py index 0e16ea7..a431ac1 100644 --- a/test_data/zone_controller/test_composition_yaml/composition_yaml.py +++ b/test_data/zone_controller/test_composition_yaml/composition_yaml.py @@ -26,6 +26,7 @@ expected_result = { "accesses": composition_yaml_setup.base_accesses } }, + "diagnostics": {"events": {}, "dids": {}}, "static": composition_yaml_setup.base_static, "shared": composition_yaml_setup.base_shared, "ports": { diff --git a/test_data/zone_controller/test_composition_yaml/composition_yaml_with_a2l_axis_data.py b/test_data/zone_controller/test_composition_yaml/composition_yaml_with_a2l_axis_data.py index f732dea..85ae8f3 100644 --- a/test_data/zone_controller/test_composition_yaml/composition_yaml_with_a2l_axis_data.py +++ b/test_data/zone_controller/test_composition_yaml/composition_yaml_with_a2l_axis_data.py @@ -188,6 +188,7 @@ expected_result = { ] } }, + "diagnostics": {"events": {}, "dids": {}}, "static": composition_yaml_setup.base_static, "shared": { "ctestName_SC_TriggerReadRteCData": { diff --git a/test_data/zone_controller/test_composition_yaml/composition_yaml_with_calls_all_fields.py b/test_data/zone_controller/test_composition_yaml/composition_yaml_with_calls_all_fields.py index c50d61c..29e77a5 100644 --- a/test_data/zone_controller/test_composition_yaml/composition_yaml_with_calls_all_fields.py +++ b/test_data/zone_controller/test_composition_yaml/composition_yaml_with_calls_all_fields.py @@ -32,6 +32,7 @@ expected_result = { "accesses": composition_yaml_setup.base_accesses } }, + "diagnostics": {"events": {}, "dids": {}}, "static": composition_yaml_setup.base_static, "shared": composition_yaml_setup.base_shared, "ports": { diff --git a/test_data/zone_controller/test_composition_yaml/composition_yaml_with_calls_no_optional_fields.py b/test_data/zone_controller/test_composition_yaml/composition_yaml_with_calls_no_optional_fields.py index cec2106..5ffcd97 100644 --- a/test_data/zone_controller/test_composition_yaml/composition_yaml_with_calls_no_optional_fields.py +++ b/test_data/zone_controller/test_composition_yaml/composition_yaml_with_calls_no_optional_fields.py @@ -31,6 +31,7 @@ expected_result = { "accesses": composition_yaml_setup.base_accesses } }, + "diagnostics": {"events": {}, "dids": {}}, "static": composition_yaml_setup.base_static, "shared": composition_yaml_setup.base_shared, "ports": { diff --git a/test_data/zone_controller/test_composition_yaml/composition_yaml_with_dids.py b/test_data/zone_controller/test_composition_yaml/composition_yaml_with_dids.py new file mode 100644 index 0000000..081ce80 --- /dev/null +++ b/test_data/zone_controller/test_composition_yaml/composition_yaml_with_dids.py @@ -0,0 +1,81 @@ +# Copyright 2024 Volvo Car Corporation +# Licensed under Apache 2.0. + +"""Unit test data for pybuild.zone_controller.composition_yaml with DIDs.""" + +from test_data.zone_controller.test_composition_yaml import composition_yaml_setup + +diagnostics = { + "dids": { + "DID1": { + "identifier": "34EE", + "numberOfParameters": 1, + "totalNumberOfBytes": 2, + "operations": { + "ReadData": { + "securityAccess": "", + "readLocalVariables": ["MyIRV"], + "writtenLocalVariables": ["MyIRV"], + }, + "WriteData": { + "securityAccess": "", + "readLocalVariables": ["MyIRV3"], + "writtenLocalVariables": ["MyIRV3"], + }, + "ShortTermAdjustment": { + "securityAccess": "", + "readLocalVariables": ["MyIRV4"], + "writtenLocalVariables": ["MyIRV4"], + }, + "ReturnControlToECU": { + "securityAccess": "", + "readLocalVariables": ["MyIRV5"], + "writtenLocalVariables": ["MyIRV6"], + }, + }, + }, + }, +} + +expected_result = { + "SoftwareComponents": { + "testName_SC": { + "type": "SWC", + "template": "ARTCSC", + "runnables": { + "AR_prefix_VcExtINI": { + "type": "INIT", + "accesses": composition_yaml_setup.base_accesses + }, + "AR_prefix_testRunnable": { + "period": 10, + "type": "PERIODIC", + "accesses": composition_yaml_setup.base_accesses, + }, + "AR_testName_SC_ZcCalibrationStep": { + "period": 0.1, + "type": "PERIODIC", + "accesses": composition_yaml_setup.base_accesses + } + }, + "diagnostics": { + "events": {}, + "dids": { + "DID1": { + "operations": { + "ReadData": {}, + }, + }, + } + }, + "static": composition_yaml_setup.base_static, + "shared": composition_yaml_setup.base_shared, + "ports": { + "GlobSignNme": {"direction": "IN", "interface": "PIGlobSignNme"}, + } + } + }, + "DataTypes": composition_yaml_setup.base_data_types, + "PortInterfaces": composition_yaml_setup.base_port_interfaces, + "ExternalFiles": composition_yaml_setup.base_configuration +} diff --git a/test_data/zone_controller/test_composition_yaml/composition_yaml_with_dtcs.py b/test_data/zone_controller/test_composition_yaml/composition_yaml_with_dtcs.py new file mode 100644 index 0000000..2ec3c43 --- /dev/null +++ b/test_data/zone_controller/test_composition_yaml/composition_yaml_with_dtcs.py @@ -0,0 +1,71 @@ +# Copyright 2024 Volvo Car Corporation +# Licensed under Apache 2.0. + +"""Unit test data for pybuild.zone_controller.composition_yaml with DTCs.""" + +from test_data.zone_controller.test_composition_yaml import composition_yaml_setup + +diagnostics = { + "events": { + "DTC1": { + "operations": ["SetEventStatus", "SomethingElse"], + "runnable": ["dummy"], + "identifier": "34EA", + "ThresholdUnconfirmed": 1, + "StepDown": 0, + "JumpDown": True, + "JumpDownInit": 1, + "StepUp": 1, + "JumpUp": True, + "JumpUpInit": 127, + "TestFailedLimit": 0, + "TestPassedLimit": 0, + "AgedDTCLimit": 0, + "ConfirmedDTCLimit": 1, + "DTCEventPriority": 1, + "OperationCycle": "Battery", + }, + }, +} + +expected_result = { + "SoftwareComponents": { + "testName_SC": { + "type": "SWC", + "template": "ARTCSC", + "runnables": { + "AR_prefix_VcExtINI": { + "type": "INIT", + "accesses": composition_yaml_setup.base_accesses + }, + "AR_prefix_testRunnable": { + "period": 10, + "type": "PERIODIC", + "accesses": composition_yaml_setup.base_accesses, + }, + "AR_testName_SC_ZcCalibrationStep": { + "period": 0.1, + "type": "PERIODIC", + "accesses": composition_yaml_setup.base_accesses + } + }, + "diagnostics": { + "events": { + "DTC1": { + "operations": ["SetEventStatus"], + "runnable": ["dummy"], + }, + }, + "dids": {} + }, + "static": composition_yaml_setup.base_static, + "shared": composition_yaml_setup.base_shared, + "ports": { + "GlobSignNme": {"direction": "IN", "interface": "PIGlobSignNme"}, + } + } + }, + "DataTypes": composition_yaml_setup.base_data_types, + "PortInterfaces": composition_yaml_setup.base_port_interfaces, + "ExternalFiles": composition_yaml_setup.base_configuration +} diff --git a/tests/pybuild/test_core.py b/tests/pybuild/test_core.py index 16afc6b..83edfd6 100644 --- a/tests/pybuild/test_core.py +++ b/tests/pybuild/test_core.py @@ -7,7 +7,7 @@ from unittest.mock import MagicMock from pathlib import Path from pybuild.build_proj_config import BuildProjConfig from pybuild.unit_configs import UnitConfigs -from pybuild.core import Core, HICore +from pybuild.core import Core, HICore, ZCCore from .core_cnfg import CORE_CFG @@ -168,7 +168,6 @@ class TestHICore(unittest.TestCase): def test_get_source_content(self): """Test get_source_content.""" - self.maxDiff = None self.hi_core.diagnostic_trouble_codes = self.dummy_yaml_dtcs result = self.hi_core.get_source_content() expected = [ @@ -206,3 +205,107 @@ class TestHICore(unittest.TestCase): '\n' ] self.assertListEqual(expected, result) + + +class TestZCCore(unittest.TestCase): + """Test case for testing class ZCCore.""" + + def setUp(self): + """Set-up common data structures for all tests in the test case.""" + project_config = MagicMock() + project_config.get_swc_name.return_value = 'DUMMY' + unit_configs = MagicMock() + unit_configs.get_per_unit_cfg.return_value = {} + + self.zc_core = ZCCore(project_config, unit_configs) + self.zc_core.project_dtcs = {'VcEventOne', 'VcEventTwo', 'VcEventThree'} + + self.dummy_yaml_dtcs = { + 'VcEventOne': { + 'operations': ["SetEventStatus"], + 'random_data': {}, + }, + 'VcEventTwo': { + 'operations': ["SetEventStatus"], + 'random_data': {}, + }, + 'VcEventThree': { + 'operations': ["SetEventStatus"], + 'random_data': {}, + }, + } + + def test_get_diagnostic_trouble_codes(self): + """Test the get_diagnostic_trouble_codes function.""" + result = self.zc_core.get_diagnostic_trouble_codes(self.dummy_yaml_dtcs) + self.assertEqual(result, self.dummy_yaml_dtcs) + + def test_get_diagnostic_trouble_codes_missing_in_project(self): + """Test the get_diagnostic_trouble_codes function, when a DTC is in yaml but not project.""" + self.zc_core.project_dtcs = {'VcEventOne', 'VcEventTwo'} + result = self.zc_core.get_diagnostic_trouble_codes(self.dummy_yaml_dtcs) + expected = { + 'VcEventOne': { + 'operations': ["SetEventStatus"], + 'random_data': {}, + }, + 'VcEventTwo': { + 'operations': ["SetEventStatus"], + 'random_data': {}, + }, + } + self.assertEqual(result, expected) + + def test_get_diagnostic_trouble_codes_missing_in_yaml(self): + """Test the get_diagnostic_trouble_codes function, when a DTC is in project but not in yaml.""" + dummy_yaml_dtcs = { + 'VcEventOne': { + 'operations': ["SetEventStatus"], + 'random_data': {}, + }, + 'VcEventTwo': { + 'operations': ["SetEventStatus"], + 'random_data': {}, + }, + } + result = self.zc_core.get_diagnostic_trouble_codes(dummy_yaml_dtcs) + self.assertEqual(result, dummy_yaml_dtcs) + + def test_get_diagnostic_trouble_codes_unsupported_operation(self): + """Test the get_diagnostic_trouble_codes function, when an operation is not supported.""" + dummy_yaml_dtcs = { + 'VcEventOne': { + 'operations': ["SetEventStatus"], + 'random_data': {}, + }, + 'VcEventTwo': { + 'operations': ["SetEventStatus"], + 'random_data': {}, + }, + 'VcEventThree': { + 'operations': ["SetEventStatus", "SomeUnsupportedOperation"], + 'random_data': {}, + }, + } + result = self.zc_core.get_diagnostic_trouble_codes(dummy_yaml_dtcs) + self.assertEqual(result, self.dummy_yaml_dtcs) + + def test_get_header_content(self): + """Test get_header_content.""" + result = self.zc_core.get_header_content() + expected = [ + '#ifndef VCCORESUPPLIERABSTRACTION_H\n', + '#define VCCORESUPPLIERABSTRACTION_H\n', + '\n', + '/* Core API Supplier Abstraction */\n', + '\n', + '#include "tl_basetypes.h"\n', + '#include "Rte_DUMMY.h"\n', + '\n', + '/* enum EventStatus {passed=0, failed=1, prepassed=2, prefailed=3} */\n', + '#define Dem_SetEventStatus(EventName, EventStatus)', + ' ', + 'Rte_Call_Event_##EventName##_SetEventStatus(EventStatus)\n', + '\n#endif /* VCCORESUPPLIERABSTRACTION_H */\n', + ] + self.assertListEqual(expected, result) diff --git a/tests/pybuild/test_dids.py b/tests/pybuild/test_dids.py index ab02242..dbfaa42 100644 --- a/tests/pybuild/test_dids.py +++ b/tests/pybuild/test_dids.py @@ -9,7 +9,16 @@ from unittest.mock import patch from unittest.mock import mock_open from pathlib import Path from pybuild.build_proj_config import BuildProjConfig -from pybuild.dids import DIDs, HIDIDs +from pybuild.dids import DIDs, HIDIDs, ZCDIDs +from test_data.pybuild.test_dids.zc_dids import ( + dummy_project_dids, + valid_dids, + bad_valid_dids, + test_valid_dids_setter_expected, + test_get_operation_data_expected, + TEST_GET_HEADER_FILE_CONTENT_EXPECTED, + TEST_GET_SOURCE_FILE_CONTENT_EXPECTED, +) class TestDIDsTL(unittest.TestCase): @@ -966,3 +975,63 @@ class TestHIDIDs(unittest.TestCase): '\n' ] self.assertListEqual(expected, result) + + +class TestZCDIDs(unittest.TestCase): + """Test case for testing ZCDIDs class.""" + + def setUp(self): + build_cfg = MagicMock() + build_cfg.get_swc_name.return_value = 'DUMMY' + unit_cfg = MagicMock() + self.zc_dids = ZCDIDs(build_cfg, unit_cfg) + self.zc_dids.project_dids = dummy_project_dids + self.zc_dids.valid_dids = valid_dids + + def test_valid_dids_setter(self): + """Test setting property ZCDIDs.valid_dids.""" + self.zc_dids.valid_dids = {} + self.assertDictEqual(self.zc_dids.valid_dids, {}) + + self.zc_dids.valid_dids = bad_valid_dids + self.assertDictEqual(self.zc_dids.valid_dids, test_valid_dids_setter_expected) + + def test_get_operation_data(self): + """Test ZCDIDs.get_operation_data.""" + result = self.zc_dids.get_operation_data() + self.assertDictEqual(result, test_get_operation_data_expected) + + def test_get_header_file_content_no_dids(self): + """Test ZCDIDs._get_header_file_content without DIDs.""" + self.zc_dids.valid_dids = {} + result = self.zc_dids._get_header_file_content() + expected = [ + '#ifndef VCDIDAPI_H\n', + '#define VCDIDAPI_H\n', + '\n', + '#include "tl_basetypes.h"\n', + '#include "Rte_DUMMY.h"\n', + '\n', + '\n#endif /* VCDIDAPI_H */\n' + ] + self.assertListEqual(expected, result) + + def test_get_source_file_content_no_dids(self): + """Test ZCDIDs._get_source_file_content without DIDs.""" + self.zc_dids.valid_dids = {} + result = self.zc_dids._get_source_file_content() + expected = [ + '#include "VcDIDAPI.h"\n', + '\n' + ] + self.assertListEqual(expected, result) + + def test_get_header_file_content(self): + """Test ZCDIDs._get_header_file_content.""" + result = ''.join(self.zc_dids._get_header_file_content()) + self.assertEqual(result, TEST_GET_HEADER_FILE_CONTENT_EXPECTED) + + def test_get_source_file_content(self): + """Test ZCDIDs._get_source_file_content.""" + result = ''.join(self.zc_dids._get_source_file_content()) + self.assertEqual(result, TEST_GET_SOURCE_FILE_CONTENT_EXPECTED) diff --git a/tests/zone_controller/test_composition_yaml.py b/tests/zone_controller/test_composition_yaml.py index 0cfc325..74852e3 100644 --- a/tests/zone_controller/test_composition_yaml.py +++ b/tests/zone_controller/test_composition_yaml.py @@ -10,6 +10,8 @@ from pathlib import Path from unittest.mock import MagicMock, patch from pybuild.build_proj_config import BuildProjConfig +from pybuild.core import ZCCore +from pybuild.dids import ZCDIDs from pybuild.unit_configs import UnitConfigs from pybuild.zone_controller.composition_yaml import CompositionYaml from test_data.zone_controller.test_composition_yaml import ( @@ -18,6 +20,8 @@ from test_data.zone_controller.test_composition_yaml import ( 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, ) SRC_DIR = Path(__file__).parent @@ -54,6 +58,12 @@ class TestCompositionYaml(unittest.TestCase): 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.zc_spec = copy.deepcopy(composition_yaml_setup.zc_spec) self.calibration_definitions = copy.deepcopy(composition_yaml_setup.calibration_definitions) @@ -63,7 +73,9 @@ class TestCompositionYaml(unittest.TestCase): "_get_all_calibration_definitions", return_value=self.calibration_definitions ): - self.composition_yaml = CompositionYaml(self.build_cfg, self.zc_spec, self.unit_cfg, {}) + self.composition_yaml = CompositionYaml( + self.build_cfg, self.zc_spec, self.unit_cfg, self.zc_core, self.zc_dids, {} + ) # Common expected results variables self.base_configuration = copy.deepcopy(composition_yaml_setup.base_configuration) @@ -103,7 +115,9 @@ class TestCompositionYaml(unittest.TestCase): 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.zc_spec, self.unit_cfg, a2l_axis_data) + self.composition_yaml = CompositionYaml( + self.build_cfg, self.zc_spec, self.unit_cfg, self.zc_core, self.zc_dids, a2l_axis_data + ) result = self.composition_yaml.gather_yaml_info() self.assertDictEqual(composition_yaml_with_a2l_axis_data.expected_result, result) @@ -123,7 +137,9 @@ class TestCompositionYaml(unittest.TestCase): "_get_all_calibration_definitions", return_value=self.calibration_definitions ): - self.composition_yaml = CompositionYaml(self.build_cfg, self.zc_spec, self.unit_cfg, {}) + self.composition_yaml = CompositionYaml( + self.build_cfg, self.zc_spec, self.unit_cfg, self.zc_core, self.zc_dids, {} + ) result = self.composition_yaml.gather_yaml_info() self.assertDictEqual(composition_yaml_with_calls_all_fields.expected_result, result) @@ -140,10 +156,42 @@ class TestCompositionYaml(unittest.TestCase): "_get_all_calibration_definitions", return_value=self.calibration_definitions ): - self.composition_yaml = CompositionYaml(self.build_cfg, self.zc_spec, self.unit_cfg, {}) + self.composition_yaml = CompositionYaml( + self.build_cfg, self.zc_spec, self.unit_cfg, self.zc_core, self.zc_dids, {} + ) 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": {"dummy_data": {}}} + self.zc_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.zc_spec, self.unit_cfg, self.zc_core, self.zc_dids, {} + ) + 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.zc_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.zc_spec, self.unit_cfg, self.zc_core, self.zc_dids, {} + ) + result = self.composition_yaml.gather_yaml_info() + self.assertDictEqual(composition_yaml_with_dtcs.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()