Skip to content
Snippets Groups Projects
Commit bae0d369 authored by René Paris's avatar René Paris
Browse files

feat(ConverterRegistry): Add registry and modify tests

parent 2d8c0fc9
No related branches found
No related tags found
No related merge requests found
Pipeline #45446 canceled
################################################################################
# Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# http://www.eclipse.org/legal/epl-2.0.
#
# SPDX-License-Identifier: EPL-2.0
################################################################################
from dataclasses import dataclass
from typing import List
from typing import Dict, List
import functools
def init_with_lowercase_keys(cls):
wrapped_init = cls.__init__
@functools.wraps(wrapped_init)
def wrapper(self, *args, **kwargs):
lowercase_kwargs = {key.lower(): value for key, value in kwargs.items()}
wrapped_init(self, *args, **lowercase_kwargs)
cls.__init__ = wrapper
return cls
@init_with_lowercase_keys
@dataclass(frozen=True)
class Dependency:
name: str
type: str
include: str
name: str
type: str
include: str
class DependencyException(Exception):
def __init__(self, dependency_name):
......@@ -13,23 +37,46 @@ class DependencyException(Exception):
message = f"The referenced dependency '{dependency_name}' does not exist."
super().__init__(message)
class Converter:
TYPE_MAPPING = {
"runtime": True,
"initialization": False}
def __init__(self, dependencies: List[Dependency], name: str, values={}):
self.name = name
self.properties = sorted(values.get("Properties", [name]))
self.keep = values.get("Keep", [])
self.runtime = Converter.TYPE_MAPPING[values.get("Type", "initialization").lower()]
self.dependencies = Converter._parse_dependencies(dependencies, values)
@staticmethod
def _parse_dependencies(dependencies, values):
collected_deps = set()
for d in values.get("Dependencies", []):
if d not in dependencies:
raise DependencyException(d)
collected_deps.add(dependencies[d])
return sorted(list(collected_deps))
TYPE_MAPPING = {
"runtime": True,
"initialization": False}
def __init__(self, dependencies: Dict[str, Dependency], name: str, values={}):
self.name = name
self.properties = sorted(values.get("Properties", [name]))
self.keep = values.get("Keep", [])
self.runtime = Converter.TYPE_MAPPING[values.get(
"Type", "initialization").lower()]
self.dependencies = Converter._parse_dependencies(dependencies, values)
@staticmethod
def _parse_dependencies(dependencies, values):
collected_deps = set()
for d in values.get("Dependencies", []):
if d not in dependencies:
raise DependencyException(d)
collected_deps.add(dependencies[d])
return sorted(list(collected_deps))
class ConverterRegistry:
def __init__(self, dependencies: List[Dependency], converter):
self.general = self._parse_definition(
dependencies, converter.get("General", {}))
self.node_specific = self._parse_definitions(
dependencies, converter.get("NodeSpecific", {}))
def _parse_definitions(self, dependencies, definitions):
return {node_name: self._parse_definition(dependencies, definition)
for node_name, definition in definitions.items()}
def _parse_definition(self, dependencies, definition):
return [Converter(dependencies, conv_name, conf_config)
for conv_name, conf_config in definition.items()]
def __getitem__(self, node_name):
converters = list(self.general)
converters.extend(self.node_specific.get(node_name, []))
return converters
################################################################################
# Copyright (c) 2021-2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
# Copyright (c) 2021-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
......
################################################################################
# Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# http://www.eclipse.org/legal/epl-2.0.
#
# SPDX-License-Identifier: EPL-2.0
################################################################################
from typing import List
from open_scenario_tree import OscProperty
from converter import Converter
......
################################################################################
# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
# Copyright (c) 2023-2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
......@@ -12,13 +12,16 @@ import pytest
from typing import List
from yaml import load, FullLoader
from converter import Converter, Dependency, DependencyException
from converter import ConverterRegistry, Converter, Dependency, DependencyException
TEST_CONVERTER = """
TEST_METAINFO = """
Converter:
General:
Position:
Description: >
Position are always converted when the scenario is initialized.
The converter needs the environment.
Type: initialization
Dependencies:
- Environment
......@@ -26,18 +29,57 @@ Converter:
NodeSpecific:
LongitudinalDistanceAction:
LongitudinalDistance:
Description: >
LongitudinalDistance needs distance and timegap referenced w.r.t an entity.
Properties:
- entityRef
- distance: consume
- timegap: consume
- distance
- timegap
Keep:
- entityRef
Type: runtime
Dependencies:
- Environment
"""
TEST_DEPS = """
SomeActionWithTwoConverters:
FirstConverter:
Description: >
FirstConverter needs a and b.
Properties:
- a
- b
SecondConverter:
Description: >
SecondConverter needs c and d.
Properties:
- c
- d
AssignControllerAction:
Controller:
Properties:
- controller
- catalogReference
AssignRouteAction:
Route:
Properties:
- route
- catalogReference
EnvironmentAction:
Environment:
Properties:
- environment
- catalogReference
FollowTrajectoryAction:
Trajectory:
Properties:
- trajectory
- catalogReference
Dependencies:
Environment:
Name: environment
......@@ -45,51 +87,46 @@ Dependencies:
Include: '<MantleAPI/Execution/i_environment.h>'
"""
@pytest.fixture
def dependencies():
data = load(TEST_METAINFO, FullLoader)
return {d: Dependency(**value) for d, value in data.get("Dependencies", {}).items()}
@pytest.fixture
def node_specific():
data = load(TEST_CONVERTER, FullLoader)
return data.get("Converter", {}).get("NodeSpecific", {})
def converter_registry(dependencies):
data = load(TEST_METAINFO, FullLoader)
return ConverterRegistry(dependencies, data.get("Converter", {}))
def test__converter_registry__given_full_definition__aggregates_general_and_node_specific_converters(converter_registry):
# collects the general converters
assert(len(converter_registry["I don't care"]) == 1)
@pytest.fixture
def general():
data = load(TEST_CONVERTER, FullLoader)
return data.get("Converter", {}).get("General", {})
# collects the general converters + the converter for the specific node
assert(len(converter_registry["LongitudinalDistanceAction"]) == 2)
# collects the general converters + the converter for the specific node
assert(len(converter_registry["SomeActionWithTwoConverters"]) == 3)
@pytest.fixture
def deps():
data = load(TEST_DEPS, FullLoader)
return data.get("Dependencies", {})
def test__converter__given_full_info__parses_everything(node_specific):
class FakeEnvironment: pass
fake_deps = {"Environment": FakeEnvironment}
node_under_test = next(iter(node_specific.values()))
converter = Converter(fake_deps, *next(iter(node_under_test.items())))
assert converter.name == "LongitudinalDistance"
assert set(converter.properties) == {"entityRef", "distance", "timegap"}
assert converter.keep == ["entityRef"]
assert converter.runtime == True
assert converter.dependencies == [FakeEnvironment]
def test__converter__given_partial_info__parses_everything(general):
class FakeEnvironment: pass
fake_deps = {"Environment": FakeEnvironment}
converter = Converter(fake_deps, *next(iter(general.items())))
def test__converter_registry__given_full_definition__parses_general_converters(converter_registry, dependencies):
converter = converter_registry["I don't care"][0] # 0 belongs to general converters
assert converter.name == "Position"
assert converter.properties == ["Position"]
assert converter.keep == []
assert converter.runtime == False
assert converter.dependencies == [FakeEnvironment]
assert converter.dependencies == [dependencies["Environment"]]
def test__converter_registry__given_full_definition__parses_everything(converter_registry, dependencies):
converters = converter_registry["LongitudinalDistanceAction"][1] # 0 belongs to general converters
assert converters.name == "LongitudinalDistance"
assert set(converters.properties) == {"entityRef", "distance", "timegap"}
assert converters.keep == ["entityRef"]
assert converters.runtime == True
assert converters.dependencies == [dependencies["Environment"]]
def test__converter__given_no_info__parses_everything():
def test__converter__given_no_info__creates_defaults():
fake_deps = {"Environment": None}
converter = Converter(fake_deps,"SomeValue")
converter = Converter(fake_deps, "SomeValue")
assert converter.name == "SomeValue"
assert converter.properties == ["SomeValue"]
assert converter.keep == []
......@@ -103,9 +140,26 @@ def test__converter__given_unknown_dependency__raises_error():
assert "UnknownDependency" in str(excinfo.value)
def test__dependency__creation(deps):
dep = Dependency(**{key.lower(): value for key, value in deps['Environment'].items()})
def test__dependency__given_uppercase_keys__creates_dependencies():
dependency_definition = {"NAME": "test_name", "TYPE": "test_type", "INCLUDE": "test_include"}
dep = Dependency(**dependency_definition)
assert dep.include == 'test_include'
assert dep.name == 'test_name'
assert dep.type == 'test_type'
def test__dependency__given_capitalized_keys__creates_dependencies():
dependency_definition = {"Name": "test_name", "Type": "test_type", "Include": "test_include"}
dep = Dependency(**dependency_definition)
assert dep.include == 'test_include'
assert dep.name == 'test_name'
assert dep.type == 'test_type'
def test__dependency__given_lowercase_keys__creates_dependencies():
dependency_definition = {"name": "test_name", "type": "test_type", "include": "test_include"}
dep = Dependency(**dependency_definition)
assert dep.include == 'test_include'
assert dep.name == 'test_name'
assert dep.type == 'test_type'
def test_converter__given_properies__sorts_alphabetically():
fake_deps = {"Environment": None}
......
from dataclasses import dataclass
################################################################################
# Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# http://www.eclipse.org/legal/epl-2.0.
#
# SPDX-License-Identifier: EPL-2.0
################################################################################
from open_scenario_tree import OscNode, parse_properties
from converter import Converter, Dependency
from typing import List
......@@ -27,6 +36,9 @@ class NodeView:
def _generate_properties_view(self):
properties = []
unconverted_properties = set(self.node.properties)
## apply converters to properties of the node OR which are flagged as general
for converter in self.converter:
if set(converter.properties).issubset({prop.name for prop in self.node.properties}):
consumed_properties = [prop for prop in self.node.properties if prop.name in converter.properties]
......@@ -216,3 +228,17 @@ def test__given_node_with_optional_property_and_runtime__generates_ternary_opera
assert node_view.properties == [
"[=](){ return ConvertScenarioCustomPropName(nodeUnderTest_->IsSetPropName1() ? std::make_optional(nodeUnderTest_->GetPropName1()) : std::nullopt); }"]
### Use Case MR190
def test__given_node_with_environment_and_catalog_reference__consumes_catalog_reference():
osc_node = OscNode("EnvironmentAction", parse_properties({
"environment": { "type.name": "Environment" },
"catalogReference": { "type.name": "CatalogReference" }
}))
converter = Converter([], "environment", {'Properties': ['environment', 'catalogReference']})
node_view = NodeView(osc_node, [converter])
assert node_view.properties == [
"ConvertScenarioEnvironment(environmentAction_->GetEnvironment(), environmentAction_->GetCatalogReference())"
]
################################################################################
# Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# http://www.eclipse.org/legal/epl-2.0.
#
# SPDX-License-Identifier: EPL-2.0
################################################################################
import pytest
from property_view import PropertyView
......
from open_scenario_tree import OpenScenarioTree, OscNode, Entity
################################################################################
# Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# http://www.eclipse.org/legal/epl-2.0.
#
# SPDX-License-Identifier: EPL-2.0
################################################################################
from open_scenario_tree import OpenScenarioTree
from generate import get_from_json
import pytest
......@@ -18,13 +28,15 @@ class NodeView():
def uml_model():
return get_from_json()
def test__given_choice__generates_data_what_else(uml_model):
osc_tree = OpenScenarioTree(uml_model)
node_under_test = NodeView(osc_tree["Action"])
assert node_under_test.is_choice
assert node_under_test.filename == "Action"
assert node_under_test.items == ["PrivateAction", "GlobalAction", "UserDefinedAction"]
assert node_under_test.items == [
"PrivateAction", "GlobalAction", "UserDefinedAction"]
def test__given_list__generates_data_what_else(uml_model):
......@@ -35,6 +47,7 @@ def test__given_list__generates_data_what_else(uml_model):
assert node_under_test.filename == "GlobalActions"
assert node_under_test.items == ["GlobalAction"]
def test__given_list__generates_data_what_else(uml_model):
osc_tree = OpenScenarioTree(uml_model)
node_under_test = NodeView(osc_tree["GlobalActions"])
......@@ -52,6 +65,7 @@ def test__given_leaf__generates_leaf():
assert node_under_test.filename == "VariableCondition"
assert node_under_test.items == ["VariableCondition"]
def test__given_leaf__generates_leaf():
osc_tree = OpenScenarioTree(uml_model)
node_under_test = NodeView(osc_tree["TrafficStopAction"])
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment