# Copyright 2024 Volvo Car Corporation # Licensed under Apache 2.0. """Unit test script for powertrain_build.build module.""" import os import logging import unittest from unittest.mock import MagicMock, patch, PropertyMock from pathlib import Path from powertrain_build import build_defs from powertrain_build.lib import helper_functions from powertrain_build.problem_logger import ProblemLogger from powertrain_build.build_proj_config import BuildProjConfig from powertrain_build.unit_configs import UnitConfigs from powertrain_build.signal_interfaces import CsvSignalInterfaces from powertrain_build.core import Core from powertrain_build import build SRC_DIR = Path(__file__).parent def remove(*files): """Try to remove file.""" for file_ in files: try: os.remove(file_) except FileNotFoundError: pass def exists(*files): """Check if file exists.""" for file_ in files: if not os.path.isfile(file_): raise AssertionError(f'File {file_} does not exist.') return True class TestBuild(unittest.TestCase): """Test case for testing the build module.""" def setUp(self): """Set-up common data structures for all tests in the test case.""" output_folder = Path(SRC_DIR, 'output') cnfg_files_folder = Path(SRC_DIR, 'cnfg_files') common_src_dir = str(Path(SRC_DIR, 'common_src_files')) prj_cnf_dir = str(cnfg_files_folder) prj_out_dir = str(output_folder) prj_src_dir = str(Path(SRC_DIR, 'src_files')) projdir = cnfg_files_folder.resolve() helper_functions.create_dir(Path(SRC_DIR, 'output')) helper_functions.create_dir(Path(cnfg_files_folder, 'output')) self.build_cfg = MagicMock(spec_set=BuildProjConfig(Path(cnfg_files_folder, 'ProjectCfg.json'))) self.build_cfg.get_prj_cfg_dir = MagicMock(return_value=prj_cnf_dir) self.build_cfg.get_prj_config = MagicMock(return_value='CFG1') self.build_cfg.get_reports_dst_dir = MagicMock(return_value=prj_out_dir) self.build_cfg.get_src_code_dst_dir = MagicMock(return_value=prj_out_dir) self.build_cfg.get_unit_cfg_deliv_dir = MagicMock(return_value=prj_out_dir) self.build_cfg.get_did_cfg_file_name = MagicMock(return_value='DIDIds_FullRange') self.build_cfg.get_car_com_dst = MagicMock(return_value=os.path.join(prj_out_dir, 'CarCom_DIDDefs.csv')) self.build_cfg.get_core_dummy_name = MagicMock(return_value=prj_out_dir + '/VcCoreDummy') self.build_cfg.get_nvm_defs = MagicMock(return_value={ 'fileName': 'vcc_nvm_struct', "baseNvmStructs": "nvm_structs_ref_empty.json" }) self.build_cfg.get_common_src_dir = MagicMock(return_value=common_src_dir) self.build_cfg.get_unit_src_dirs = MagicMock(return_value={'mocked': prj_src_dir}) self.build_cfg.get_unit_cfg_dirs = MagicMock(return_value={'mocked': prj_cnf_dir}) self.build_cfg.get_a2l_name = MagicMock(return_value='merged.a2l') self.build_cfg.get_root_dir = MagicMock(return_value=projdir) self.build_cfg.get_ecu_info = MagicMock(return_value=('Denso', 'G2')) self.unit_cfg = MagicMock(spec_set=UnitConfigs) type(self.unit_cfg).base_types_headers = '#include tl_basetypes.h\n' type(self.unit_cfg).code_generators = 'target_link' @staticmethod def test_setup_logging(): """Check that init_logger is called.""" log = logging.getLogger() log_dst_dir = str(Path(SRC_DIR, 'output')) problem_logger = MagicMock(spec_set=ProblemLogger) build.setup_logging(log_dst_dir, problem_logger, debug=True, quiet=True) problem_logger.init_logger.assert_called_once_with(log) def test_check_interfaces(self): """Check that interface check is run.""" signal_if = MagicMock(spec_set=CsvSignalInterfaces) signal_if.check_config = MagicMock(return_value={'sigs': {'ext': {}, 'int': {}}}) build.check_interfaces(self.build_cfg, signal_if) self.build_cfg.get_reports_dst_dir.assert_called_once() signal_if.check_config.called_once() def test_interface_report(self): """Check that interface report is generated.""" signal_if = MagicMock(spec_set=CsvSignalInterfaces) signal_if.check_config = MagicMock(return_value={'sigs': {'ext': {}, 'int': {}}}) filename = str(Path(SRC_DIR, 'output', 'SigIf.html')) remove(filename) build.interface_report(self.build_cfg, self.unit_cfg, signal_if) self.build_cfg.get_reports_dst_dir.assert_called_once() exists(filename) def test_generate_did_files(self): """Check that interface report is generated.""" output_folder = Path(SRC_DIR, 'output') filename = str(Path(output_folder, 'VcDIDDefinition')) cfile = filename + '.c' hfile = filename + '.h' csvfile = str(Path(output_folder, 'CarCom_DIDDefs.csv')) remove(cfile, hfile, csvfile) build.generate_did_files(self.build_cfg, self.unit_cfg) self.build_cfg.get_src_code_dst_dir.assert_called_once() self.build_cfg.get_did_cfg_file_name.assert_called() self.build_cfg.get_prj_cfg_dir.assert_called() exists(cfile, hfile, csvfile) def test_generate_core_dummy_denso(self): """Check that core dummy files are generated.""" self.build_cfg.get_ecu_info = MagicMock(return_value=('Denso', 'G2')) core = MagicMock(spec_set=Core) core.get_current_core_config = MagicMock() filename = str(Path(SRC_DIR, 'output', 'VcCoreDummy')) cfile = filename + '.c' hfile = filename + '.h' remove(cfile, hfile) build.generate_core_dummy(self.build_cfg, core, self.unit_cfg) core.get_current_core_config.assert_called_once() self.build_cfg.get_core_dummy_name.assert_called_once() self.build_cfg.get_ecu_info.assert_called_once() exists(cfile, hfile) def test_generate_core_dummy_rb(self): """Check that core dummy files are generated.""" self.build_cfg.get_ecu_info = MagicMock(return_value=('RB', 'xx')) core = MagicMock(spec_set=Core) core.get_current_core_config = MagicMock() filename = str(Path(SRC_DIR, 'output', 'VcCoreDummy')) cfile = filename + '.c' hfile = filename + '.h' remove(cfile, hfile) build.generate_core_dummy(self.build_cfg, core, self.unit_cfg) core.get_current_core_config.assert_called_once() self.build_cfg.get_core_dummy_name.assert_called_once() self.build_cfg.get_ecu_info.assert_called_once() exists(cfile, hfile) def test_generate_core_dummy_other(self): """Check that core dummy files are generated.""" self.build_cfg.get_ecu_info = MagicMock(return_value=('Other', 'yy')) core = MagicMock(spec_set=Core) core.get_current_core_config = MagicMock() self.assertRaises(ValueError, build.generate_core_dummy, self.build_cfg, core, self.unit_cfg) def test_generate_external_var(self): """Check that ExtVar files are generated.""" self.build_cfg.has_yaml_interface = False nrm_dict = { 'EMS-Output': {}, 'EMS-Input': { 'DummyIn': { 'type': 'Float32', 'min': 0, 'max': 100, 'unit': 'Nm', 'description': 'Dummy', 'init': 0, 'element_index': 5, 'IOType': 'x' } }, 'LIN-Output': {}, 'LIN-Input': {}, 'CAN-Output': {}, 'CAN-Input': {}, 'Private CAN-Output': {}, 'Private CAN-Input': {} } dep_dict = { 'EMS-Output': {}, 'EMS-Input': { 'DummyInSafe': { 'type': 'Float32', 'min': 0, 'max': 100, 'unit': 'Nm', 'description': 'Safe dummy', 'init': 0, 'element_index': 5, 'IOType': 's' } }, 'LIN-Output': {}, 'LIN-Input': {}, 'CAN-Output': {}, 'CAN-Input': {}, 'Private CAN-Output': {}, 'Private CAN-Input': {} } sec_dict = {} filepath = str(Path(SRC_DIR, 'output')) files = [ filepath + '/VcExtVar.c', filepath + '/VcExtVar.a2l', filepath + '/VcExtVarSafe.c', filepath + '/VcExtVarSafe.a2l' ] remove(*files) with patch('powertrain_build.user_defined_types.UserDefinedTypes') as udt_mock: udt_mock.return_value.common_header_files = PropertyMock(return_value=[]) build.generate_external_var( self.build_cfg, self.unit_cfg, udt_mock, build_defs.ASIL_B, nrm_dict, dep_dict, sec_dict ) self.build_cfg.get_src_code_dst_dir.assert_called() exists(*files) self.assertFalse(os.path.isfile(f"{filepath}/VcExtVarSecure.c")) def test_generate_nvm_def(self): """Check that NVM files are generated.""" unit_cfg = MagicMock(spec_set=UnitConfigs) type(unit_cfg).base_types_headers = '' no_nvm_a2l = False filepath = str(Path(SRC_DIR, 'output')) files = [ filepath + '/vcc_nvm_struct.c', filepath + '/vcc_nvm_struct.h', filepath + '/vcc_nvm_struct.a2l'] remove(*files) build.generate_nvm_def(self.build_cfg, unit_cfg, no_nvm_a2l) self.build_cfg.get_src_code_dst_dir.assert_called() unit_cfg.get_per_cfg_unit_cfg.assert_called() exists(*files) @unittest.mock.patch('shutil.copy2') def test_copy_unit_src_to_src_out(self, mock_copy): """Check that source files are copied.""" build.copy_unit_src_to_src_out(self.build_cfg) self.build_cfg.get_unit_src_dirs.assert_called() self.build_cfg.get_src_code_dst_dir.assert_called() mock_copy.assert_called() @unittest.mock.patch('shutil.copy2') def test_copy_unit_cfgs_to_output(self, mock_copy): """Check that unit-configs are copied.""" build.copy_unit_cfgs_to_output(self.build_cfg) self.build_cfg.get_unit_cfg_deliv_dir.assert_called() self.build_cfg.get_unit_cfg_dirs.assert_called() mock_copy.assert_called() def test_merge_a2l_files(self): """Check that a2l-files are merged.""" unit_cfg = MagicMock(spec_set=UnitConfigs) filepath = str(Path(SRC_DIR, 'output')) remove(filepath + '/merged.a2l') build.merge_a2l_files(self.build_cfg, unit_cfg) self.build_cfg.get_unit_src_dirs.assert_called() self.build_cfg.get_src_code_dst_dir.assert_called() self.build_cfg.get_a2l_name.assert_called() unit_cfg.get_per_unit_cfg.assert_called() exists(filepath + '/merged.a2l') def test_build_no_cfg(self): """Check that main entrypoint raises exception for missing config.""" prj_root = str(Path(SRC_DIR, 'cnfg_files', 'NonexistentProjectCfg.json')) with unittest.mock.patch('powertrain_build.build.find_all_project_configs') as mock: mock.return_value = [prj_root] self.assertRaises(FileNotFoundError, build.build, prj_root, quiet=True) @staticmethod @unittest.mock.patch('powertrain_build.unit_configs.UnitConfigs.get_per_unit_cfg') @unittest.mock.patch('powertrain_build.build.find_all_project_configs') def test_build(mock_find_all_project_configs, mock_get_per_unit_cfg): """Check that main entrypoint can be run without exceptions.""" prj_root = str(Path(SRC_DIR, 'cnfg_files', 'ProjectCfg.json')) output = str(Path(SRC_DIR, 'output', 'logs', 'build.log')) remove(output) mock_find_all_project_configs.return_value = [prj_root] mock_get_per_unit_cfg.return_value = {'VcScBCoord': {}, 'VcScCVehMtn': {}, 'VcScFeh': {}} build.build(prj_root, interface=True, core_dummy=True, no_abort=False, debug=True, quiet=True) exists(output) @staticmethod @unittest.mock.patch('powertrain_build.unit_configs.UnitConfigs.get_per_unit_cfg') @unittest.mock.patch('powertrain_build.build.find_all_project_configs') def test_build_ec(mock_find_all_project_configs, mock_get_per_unit_cfg): """Check that main entrypoint can be run without exceptions.""" prj_root = str(Path(SRC_DIR, 'cnfg_files', 'ProjectCfg.json')) output = str(Path(SRC_DIR, 'output', 'logs', 'build.log')) remove(output) mock_find_all_project_configs.return_value = [prj_root] mock_get_per_unit_cfg.return_value = { 'VcScBCoord': {'code_generator': 'embedded_coder'}, 'VcScCVehMtn': {'code_generator': 'embedded_coder'}, 'VcScFeh': {'code_generator': 'embedded_coder'}} build.build(prj_root, interface=True, core_dummy=True, no_abort=False, debug=True, quiet=True) exists(output) @staticmethod @unittest.mock.patch('powertrain_build.unit_configs.UnitConfigs.get_per_unit_cfg') @unittest.mock.patch('powertrain_build.build.find_all_project_configs') def test_build_mixed_ec_tl(mock_find_all_project_configs, mock_get_per_unit_cfg): """Check that main entrypoint can be run without exceptions.""" prj_root = str(Path(SRC_DIR, 'cnfg_files', 'ProjectCfg.json')) output = str(Path(SRC_DIR, 'output', 'logs', 'build.log')) remove(output) mock_find_all_project_configs.return_value = [prj_root] mock_get_per_unit_cfg.return_value = { 'VcScBCoord': {'code_generator': 'embedded_coder'}, 'VcScCVehMtn': {'code_generator': 'target_link'}, 'VcScFeh': {}} build.build(prj_root, interface=True, core_dummy=True, no_abort=False, debug=True, quiet=True) exists(output) @staticmethod @unittest.mock.patch('powertrain_build.unit_configs.UnitConfigs.get_per_unit_cfg') @unittest.mock.patch('powertrain_build.build.find_all_project_configs') def test_build_no_abort(mock_find_all_project_configs, mock_get_per_unit_cfg): """Check that main entrypoint can be run without exceptions with no abort set to True.""" prj_root = str(Path(SRC_DIR, 'cnfg_files', 'ProjectCfg.json')) output = str(Path(SRC_DIR, 'output', 'logs', 'build.log')) remove(output) mock_find_all_project_configs.return_value = [prj_root] mock_get_per_unit_cfg.return_value = {'VcScBCoord': {}, 'VcScCVehMtn': {}, 'VcScFeh': {}} build.build(prj_root, interface=True, core_dummy=True, no_abort=True, debug=True, quiet=True) exists(output)