powertrain-build/tests/zone_controller/test_composition_yaml.py
Henrik Wahlqvist 01534e022f External inports should also be considered as measurable variables
Change-Id: I138e87920189c121e59e5dbdc5caf08f9063fe0a
2025-04-24 11:06:52 +02:00

351 lines
15 KiB
Python

# 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,
)
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_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"])