baldertest 0.1.0b9__py3-none-any.whl → 0.1.0b11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- _balder/_version.py +1 -1
- _balder/balder_session.py +4 -2
- _balder/cnnrelations/__init__.py +7 -0
- _balder/cnnrelations/and_connection_relation.py +149 -0
- _balder/cnnrelations/base_connection_relation.py +270 -0
- _balder/cnnrelations/or_connection_relation.py +65 -0
- _balder/collector.py +116 -14
- _balder/connection.py +400 -881
- _balder/connection_metadata.py +255 -0
- _balder/console/balder.py +1 -1
- _balder/controllers/device_controller.py +26 -12
- _balder/controllers/feature_controller.py +63 -99
- _balder/controllers/normal_scenario_setup_controller.py +7 -7
- _balder/controllers/scenario_controller.py +97 -7
- _balder/controllers/setup_controller.py +2 -3
- _balder/decorator_connect.py +12 -10
- _balder/decorator_fixture.py +4 -7
- _balder/decorator_for_vdevice.py +21 -31
- _balder/decorator_gateway.py +3 -3
- _balder/decorator_parametrize.py +31 -0
- _balder/decorator_parametrize_by_feature.py +36 -0
- _balder/exceptions.py +6 -0
- _balder/executor/basic_executable_executor.py +126 -0
- _balder/executor/basic_executor.py +1 -78
- _balder/executor/executor_tree.py +7 -5
- _balder/executor/parametrized_testcase_executor.py +52 -0
- _balder/executor/scenario_executor.py +5 -2
- _balder/executor/setup_executor.py +5 -2
- _balder/executor/testcase_executor.py +41 -9
- _balder/executor/unresolved_parametrized_testcase_executor.py +209 -0
- _balder/executor/variation_executor.py +148 -123
- _balder/feature.py +1 -1
- _balder/fixture_definition_scope.py +19 -0
- _balder/fixture_execution_level.py +22 -0
- _balder/fixture_manager.py +170 -182
- _balder/fixture_metadata.py +26 -0
- _balder/objects/connections/osi_3_network.py +2 -2
- _balder/objects/connections/osi_4_transport.py +2 -2
- _balder/parametrization.py +75 -0
- _balder/routing_path.py +18 -25
- _balder/solver.py +52 -32
- _balder/testresult.py +1 -1
- _balder/utils.py +27 -1
- balder/__init__.py +6 -0
- balder/exceptions.py +4 -3
- balder/parametrization.py +8 -0
- {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b11.dist-info}/METADATA +2 -2
- baldertest-0.1.0b11.dist-info/RECORD +83 -0
- {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b11.dist-info}/WHEEL +1 -1
- baldertest-0.1.0b9.dist-info/RECORD +0 -68
- {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b11.dist-info}/LICENSE +0 -0
- {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b11.dist-info}/entry_points.txt +0 -0
- {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b11.dist-info}/top_level.txt +0 -0
|
@@ -7,10 +7,11 @@ from abc import ABC, abstractmethod
|
|
|
7
7
|
from _balder.setup import Setup
|
|
8
8
|
from _balder.device import Device
|
|
9
9
|
from _balder.scenario import Scenario
|
|
10
|
+
from _balder.connection_metadata import ConnectionMetadata
|
|
10
11
|
from _balder.controllers.controller import Controller
|
|
11
12
|
from _balder.controllers.device_controller import DeviceController
|
|
12
13
|
from _balder.controllers.vdevice_controller import VDeviceController
|
|
13
|
-
from _balder.exceptions import MultiInheritanceError, DeviceOverwritingError
|
|
14
|
+
from _balder.exceptions import MultiInheritanceError, DeviceOverwritingError, MissingFeaturesOfVDeviceError
|
|
14
15
|
|
|
15
16
|
if TYPE_CHECKING:
|
|
16
17
|
from _balder.connection import Connection
|
|
@@ -278,7 +279,7 @@ class NormalScenarioSetupController(Controller, ABC):
|
|
|
278
279
|
found_it = True
|
|
279
280
|
break
|
|
280
281
|
if not found_it:
|
|
281
|
-
raise
|
|
282
|
+
raise MissingFeaturesOfVDeviceError(
|
|
282
283
|
f"the device `{related_device.__name__}` which is mapped to the VDevice "
|
|
283
284
|
f"`{active_vdevice.__name__}` doesn't have an implementation for the feature "
|
|
284
285
|
f"`{cur_vdevice_feature.__class__.__name__}` required by the VDevice class "
|
|
@@ -374,11 +375,10 @@ class NormalScenarioSetupController(Controller, ABC):
|
|
|
374
375
|
all_devices[all_devices_as_strings.index(cur_parent_cnn.from_device.__name__)]
|
|
375
376
|
related_to_device = all_devices[all_devices_as_strings.index(cur_parent_cnn.to_device.__name__)]
|
|
376
377
|
new_cnn = cur_parent_cnn.clone()
|
|
377
|
-
new_cnn.set_metadata_for_all_subitems(
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
"to_device_node_name": cur_parent_cnn.to_node_name})
|
|
378
|
+
new_cnn.set_metadata_for_all_subitems(ConnectionMetadata(
|
|
379
|
+
from_device=related_from_device, from_device_node_name=cur_parent_cnn.from_node_name,
|
|
380
|
+
to_device=related_to_device, to_device_node_name=cur_parent_cnn.to_node_name)
|
|
381
|
+
)
|
|
382
382
|
all_relevant_cnns.append(new_cnn)
|
|
383
383
|
|
|
384
384
|
# throw warning (but only if this scenario/setup has minimum one of the parent classes has inner
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
from typing import Type, Dict, List, Tuple, Union
|
|
2
|
+
from typing import Type, Dict, List, Tuple, Union, Callable, Iterable, Any
|
|
3
3
|
|
|
4
4
|
import logging
|
|
5
5
|
import inspect
|
|
6
|
+
from collections import OrderedDict
|
|
7
|
+
from _balder.cnnrelations import OrConnectionRelation
|
|
6
8
|
from _balder.device import Device
|
|
7
9
|
from _balder.scenario import Scenario
|
|
8
10
|
from _balder.connection import Connection
|
|
9
11
|
from _balder.controllers.feature_controller import FeatureController
|
|
10
12
|
from _balder.controllers.device_controller import DeviceController
|
|
11
13
|
from _balder.controllers.normal_scenario_setup_controller import NormalScenarioSetupController
|
|
14
|
+
from _balder.parametrization import FeatureAccessSelector, Parameter
|
|
12
15
|
from _balder.exceptions import UnclearAssignableFeatureConnectionError, ConnectionIntersectionError, \
|
|
13
16
|
MultiInheritanceError
|
|
14
17
|
|
|
@@ -26,6 +29,8 @@ class ScenarioController(NormalScenarioSetupController):
|
|
|
26
29
|
#: contains all existing scenarios and its corresponding controller object
|
|
27
30
|
_items: Dict[Type[Scenario], ScenarioController] = {}
|
|
28
31
|
|
|
32
|
+
_parametrization: Dict[Callable, Dict[str, Union[Iterable[Any], FeatureAccessSelector]]] = {}
|
|
33
|
+
|
|
29
34
|
def __init__(self, related_cls, _priv_instantiate_key):
|
|
30
35
|
|
|
31
36
|
# describes if the current controller is for setups or for scenarios (has to be set in child controller)
|
|
@@ -72,6 +77,92 @@ class ScenarioController(NormalScenarioSetupController):
|
|
|
72
77
|
|
|
73
78
|
# ---------------------------------- METHODS -----------------------------------------------------------------------
|
|
74
79
|
|
|
80
|
+
def register_parametrization(
|
|
81
|
+
self,
|
|
82
|
+
test_method: Callable,
|
|
83
|
+
field_name: str,
|
|
84
|
+
values: Iterable[Any] | FeatureAccessSelector
|
|
85
|
+
) -> None:
|
|
86
|
+
"""
|
|
87
|
+
This method registers a custom parametrization for a test method of this Scenario
|
|
88
|
+
"""
|
|
89
|
+
if test_method not in self.get_all_test_methods():
|
|
90
|
+
raise ValueError(f'got test method `{test_method.__qualname__}` which is no part of the '
|
|
91
|
+
f'scenario `{self.related_cls}`')
|
|
92
|
+
if test_method not in self._parametrization.keys():
|
|
93
|
+
self._parametrization[test_method] = {}
|
|
94
|
+
if field_name in self._parametrization[test_method].keys():
|
|
95
|
+
raise ValueError(f'field name `{field_name}` for test method `{test_method.__qualname__}` already '
|
|
96
|
+
f'registered')
|
|
97
|
+
self._parametrization[test_method][field_name] = values
|
|
98
|
+
|
|
99
|
+
def get_parametrization_for(
|
|
100
|
+
self,
|
|
101
|
+
test_method: Callable,
|
|
102
|
+
static: bool = True,
|
|
103
|
+
dynamic: bool = True,
|
|
104
|
+
) -> OrderedDict[str, Iterable[Any] | FeatureAccessSelector] | None:
|
|
105
|
+
"""
|
|
106
|
+
This method returns the parametrization for a test method of this Scenario. It returns the parameter
|
|
107
|
+
configuration for every parameter in an OrderedDict.
|
|
108
|
+
|
|
109
|
+
:param test_method: the test method of the Scenario
|
|
110
|
+
:param static: if False, all static parameters will not be included into the dict.
|
|
111
|
+
:param dynamic: if False, all dynamic parameters will not be included into the dict.
|
|
112
|
+
"""
|
|
113
|
+
if test_method not in self._parametrization.keys():
|
|
114
|
+
return None
|
|
115
|
+
params = self._parametrization[test_method]
|
|
116
|
+
|
|
117
|
+
# get arguments in defined order
|
|
118
|
+
arguments = [name for name in inspect.getfullargspec(test_method).args if name in params.keys()]
|
|
119
|
+
ordered_dict = OrderedDict()
|
|
120
|
+
for cur_arg in arguments:
|
|
121
|
+
cur_value = params[cur_arg]
|
|
122
|
+
if isinstance(cur_value, FeatureAccessSelector) and dynamic is False:
|
|
123
|
+
continue
|
|
124
|
+
if not isinstance(cur_value, FeatureAccessSelector) and static is False:
|
|
125
|
+
continue
|
|
126
|
+
ordered_dict[cur_arg] = params[cur_arg]
|
|
127
|
+
return ordered_dict
|
|
128
|
+
|
|
129
|
+
def check_for_parameter_loop_in_dynamic_parametrization(self, cur_fn: Callable):
|
|
130
|
+
"""
|
|
131
|
+
This method checks for a parameter loop in all dynamic parametrization for a specific test method. If it detects
|
|
132
|
+
a loop an AttributeError is thrown
|
|
133
|
+
"""
|
|
134
|
+
# only dynamic parametrization can have Parameter
|
|
135
|
+
parametrization = self.get_parametrization_for(cur_fn, static=False, dynamic=True)
|
|
136
|
+
|
|
137
|
+
def get_dependent_parameters_of_attribute(attribute: str) -> List[str] | None:
|
|
138
|
+
cur_feature_access_selector = parametrization.get(attribute)
|
|
139
|
+
if cur_feature_access_selector is None:
|
|
140
|
+
return None
|
|
141
|
+
# relevant are parameters only if they are from :class:`Parameter` and contained in the dynamic
|
|
142
|
+
# parametrization
|
|
143
|
+
return [param.name for param in cur_feature_access_selector.parameters.values()
|
|
144
|
+
if isinstance(param, Parameter) and param.name in parametrization.keys()]
|
|
145
|
+
|
|
146
|
+
def recursive_parameter_loop_check(for_attribute, with_attribute: str):
|
|
147
|
+
dependent_attr = get_dependent_parameters_of_attribute(with_attribute)
|
|
148
|
+
if dependent_attr is None:
|
|
149
|
+
# no problematic dependencies because attribute is no dynamic attribute
|
|
150
|
+
return
|
|
151
|
+
if len(dependent_attr) == 0:
|
|
152
|
+
# no problematic dependencies
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
if for_attribute in dependent_attr:
|
|
156
|
+
# loop detected
|
|
157
|
+
raise AttributeError('detect a loop in Parameter() object - can not apply parametrization')
|
|
158
|
+
# go deeper and resolve all dependent
|
|
159
|
+
for cur_dependent_attr in dependent_attr:
|
|
160
|
+
recursive_parameter_loop_check(for_attribute, cur_dependent_attr)
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
for cur_attr in parametrization.keys():
|
|
164
|
+
recursive_parameter_loop_check(cur_attr, cur_attr)
|
|
165
|
+
|
|
75
166
|
def get_next_parent_class(self) -> Union[Type[Scenario], None]:
|
|
76
167
|
"""
|
|
77
168
|
This method returns the next parent class which is a subclass of the :class:`Scenario` itself.
|
|
@@ -125,15 +216,15 @@ class ScenarioController(NormalScenarioSetupController):
|
|
|
125
216
|
|
|
126
217
|
# now check if one or more single of the classbased connection are CONTAINED IN the possible
|
|
127
218
|
# parallel connection (only if there exists more than one parallel)
|
|
128
|
-
feature_cnn =
|
|
129
|
-
|
|
219
|
+
feature_cnn = FeatureController.get_for(
|
|
220
|
+
cur_feature.__class__).get_abs_class_based_for_vdevice()[mapped_vdevice]
|
|
130
221
|
|
|
131
222
|
# search node names that is the relevant connection
|
|
132
223
|
relevant_cnns: List[Connection] = []
|
|
133
224
|
mapped_device_abs_cnns = DeviceController.get_for(mapped_device).get_all_absolute_connections()
|
|
134
225
|
for _, all_connections in mapped_device_abs_cnns.items():
|
|
135
226
|
relevant_cnns += [cur_cnn for cur_cnn in all_connections
|
|
136
|
-
if cur_cnn.has_connection_from_to(cur_from_device, mapped_device)]
|
|
227
|
+
if cur_cnn.has_connection_from_to(cur_from_device, end_device=mapped_device)]
|
|
137
228
|
|
|
138
229
|
if len(relevant_cnns) <= 1:
|
|
139
230
|
# ignore if there are not more than one relevant connection
|
|
@@ -248,10 +339,9 @@ class ScenarioController(NormalScenarioSetupController):
|
|
|
248
339
|
continue
|
|
249
340
|
|
|
250
341
|
# now try to reduce the scenario connections according to the requirements of the feature class
|
|
251
|
-
|
|
342
|
+
cur_feature_cnn = \
|
|
252
343
|
FeatureController.get_for(
|
|
253
344
|
cur_feature.__class__).get_abs_class_based_for_vdevice()[mapped_vdevice]
|
|
254
|
-
cur_feature_cnn = Connection.based_on(*cur_feature_class_based_for_vdevice)
|
|
255
345
|
|
|
256
346
|
device_cnn_singles = get_single_cnns_between_device_for_feature(
|
|
257
347
|
from_device=cur_from_device, to_device=mapped_device, relevant_feature_cnn=cur_feature_cnn)
|
|
@@ -305,7 +395,7 @@ class ScenarioController(NormalScenarioSetupController):
|
|
|
305
395
|
cur_from_device_controller = DeviceController.get_for(cur_from_device)
|
|
306
396
|
cur_to_device_controller = DeviceController.get_for(cur_to_device)
|
|
307
397
|
|
|
308
|
-
new_cnn = Connection.based_on(*cur_single_cnns)
|
|
398
|
+
new_cnn = Connection.based_on(OrConnectionRelation(*cur_single_cnns))
|
|
309
399
|
new_cnn.set_metadata_for_all_subitems(cur_single_cnns[0].metadata)
|
|
310
400
|
if cur_from_device == cur_single_cnns[0].from_device:
|
|
311
401
|
cur_from_device_controller.add_new_absolute_connection(new_cnn)
|
|
@@ -3,7 +3,6 @@ from typing import Type, Dict, Union, TYPE_CHECKING
|
|
|
3
3
|
|
|
4
4
|
import logging
|
|
5
5
|
from _balder.setup import Setup
|
|
6
|
-
from _balder.connection import Connection
|
|
7
6
|
from _balder.exceptions import IllegalVDeviceMappingError, MultiInheritanceError
|
|
8
7
|
from _balder.controllers.feature_controller import FeatureController
|
|
9
8
|
from _balder.controllers.device_controller import DeviceController
|
|
@@ -113,12 +112,12 @@ class SetupController(NormalScenarioSetupController):
|
|
|
113
112
|
continue
|
|
114
113
|
|
|
115
114
|
# there exists a class based requirement for this vDevice
|
|
116
|
-
class_based_cnn =
|
|
115
|
+
class_based_cnn = feature_class_based_for_vdevice[mapped_vdevice]
|
|
117
116
|
# search relevant connection
|
|
118
117
|
cur_device_controller = DeviceController.get_for(cur_device)
|
|
119
118
|
for _, cur_cnn_list in cur_device_controller.get_all_absolute_connections().items():
|
|
120
119
|
for cur_cnn in cur_cnn_list:
|
|
121
|
-
if not cur_cnn.has_connection_from_to(cur_device, mapped_device):
|
|
120
|
+
if not cur_cnn.has_connection_from_to(cur_device, end_device=mapped_device):
|
|
122
121
|
# this connection can be ignored, because it is no connection between the current device
|
|
123
122
|
# and the mapped device
|
|
124
123
|
continue
|
_balder/decorator_connect.py
CHANGED
|
@@ -5,10 +5,15 @@ import re
|
|
|
5
5
|
from _balder.device import Device
|
|
6
6
|
from _balder.connection import Connection
|
|
7
7
|
from _balder.controllers import DeviceController
|
|
8
|
+
from _balder.cnnrelations import AndConnectionRelation, OrConnectionRelation
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
def connect(
|
|
11
|
-
|
|
11
|
+
def connect(
|
|
12
|
+
with_device: Union[Type[Device], str],
|
|
13
|
+
over_connection: Union[Connection, Type[Connection], AndConnectionRelation, OrConnectionRelation],
|
|
14
|
+
self_node_name: str = None,
|
|
15
|
+
dest_node_name: str = None
|
|
16
|
+
):
|
|
12
17
|
"""
|
|
13
18
|
This decorator connects two devices with each other. It can be used for scenarios as well as setup devices.
|
|
14
19
|
|
|
@@ -27,11 +32,6 @@ def connect(with_device: Union[Type[Device], str], over_connection: Union[Type[C
|
|
|
27
32
|
if not isinstance(with_device, str) and not issubclass(with_device, Device):
|
|
28
33
|
raise ValueError("the value of `with_device` must be a `Device` (or a subclass thereof) or the device name "
|
|
29
34
|
"as a string")
|
|
30
|
-
if isinstance(over_connection, tuple):
|
|
31
|
-
# doesn't make sense, because we can describe what's needed in one scenario with several `@connect` decorations
|
|
32
|
-
# anyway. We are describing a setup, therefore an AND link makes no sense here either
|
|
33
|
-
raise TypeError("an AND link (signaled via the tuple) of the connection is not possible here - for further "
|
|
34
|
-
"separate connections use the `@connect` decorator again")
|
|
35
35
|
if isinstance(over_connection, type):
|
|
36
36
|
if not issubclass(over_connection, Connection):
|
|
37
37
|
raise TypeError("the type of `over_connection` must be a `Connection` (or a subclass of it)")
|
|
@@ -91,11 +91,13 @@ def connect(with_device: Union[Type[Device], str], over_connection: Union[Type[C
|
|
|
91
91
|
# in another `@connect()` decorator (and also remove possible metadata from the new clone)
|
|
92
92
|
cur_cnn_instance = over_connection.clone()
|
|
93
93
|
cur_cnn_instance.set_metadata_for_all_subitems(None)
|
|
94
|
-
elif issubclass(over_connection, Connection):
|
|
94
|
+
elif isinstance(over_connection, type) and issubclass(over_connection, Connection):
|
|
95
95
|
# not instantiated -> instantiate it
|
|
96
96
|
cur_cnn_instance = over_connection()
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
elif isinstance(over_connection, (AndConnectionRelation, OrConnectionRelation)):
|
|
98
|
+
over_connection = Connection.based_on(over_connection)
|
|
99
|
+
cur_cnn_instance.metadata.set_from(from_device=decorated_cls, from_device_node_name=self_node_name)
|
|
100
|
+
cur_cnn_instance.metadata.set_to(to_device=with_device, to_device_node_name=dest_node_name)
|
|
99
101
|
|
|
100
102
|
decorated_cls_device_controller.add_new_raw_connection(cur_cnn_instance)
|
|
101
103
|
return decorated_cls
|
_balder/decorator_fixture.py
CHANGED
|
@@ -3,7 +3,7 @@ from typing import Literal
|
|
|
3
3
|
|
|
4
4
|
import functools
|
|
5
5
|
from _balder.collector import Collector
|
|
6
|
-
from _balder.
|
|
6
|
+
from _balder.fixture_execution_level import FixtureExecutionLevel
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def fixture(level: Literal['session', 'setup', 'scenario', 'variation', 'testcase']):
|
|
@@ -12,17 +12,14 @@ def fixture(level: Literal['session', 'setup', 'scenario', 'variation', 'testcas
|
|
|
12
12
|
|
|
13
13
|
:param level: the execution level the fixture should have
|
|
14
14
|
"""
|
|
15
|
-
allowed_levels =
|
|
15
|
+
allowed_levels = [level.value for level in FixtureExecutionLevel]
|
|
16
16
|
|
|
17
17
|
if level not in allowed_levels:
|
|
18
18
|
raise ValueError(f"the value of `level` must be a `str` with one of the values `{'`, `'.join(allowed_levels)}`")
|
|
19
19
|
|
|
20
20
|
def decorator_fixture(func):
|
|
21
|
-
# always
|
|
22
|
-
|
|
23
|
-
if level not in Collector.raw_fixtures.keys():
|
|
24
|
-
Collector.raw_fixtures[level] = []
|
|
25
|
-
Collector.raw_fixtures[level].append(func)
|
|
21
|
+
# always register the raw fixture in Collector - class determination will be done later by :meth:`Collector`
|
|
22
|
+
Collector.register_raw_fixture(func, level)
|
|
26
23
|
|
|
27
24
|
@functools.wraps(func)
|
|
28
25
|
def wrapper_fixture(*args, **kwargs):
|
_balder/decorator_for_vdevice.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Union, Type
|
|
3
3
|
|
|
4
4
|
import inspect
|
|
5
|
+
from _balder.cnnrelations import AndConnectionRelation, OrConnectionRelation
|
|
5
6
|
from _balder.collector import Collector
|
|
6
7
|
from _balder.feature import Feature
|
|
7
|
-
from _balder.device import Device
|
|
8
8
|
from _balder.vdevice import VDevice
|
|
9
9
|
from _balder.connection import Connection
|
|
10
10
|
from _balder.controllers import FeatureController
|
|
@@ -12,11 +12,11 @@ from _balder.exceptions import DuplicateForVDeviceError, UnknownVDeviceException
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def for_vdevice(
|
|
15
|
-
vdevice: Union[str,
|
|
15
|
+
vdevice: Union[str, Type[VDevice]],
|
|
16
16
|
with_connections: Union[
|
|
17
|
-
Type[Connection], Connection,
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
Type[Connection], Connection, AndConnectionRelation, OrConnectionRelation
|
|
18
|
+
] = Connection(),
|
|
19
|
+
):
|
|
20
20
|
"""
|
|
21
21
|
With the `@for_vdevice` you can limit the decorated object for a special allowed connection tree for every existing
|
|
22
22
|
vDevice. This decorator can be used to decorate whole :class:`Feature` classes just like single methods of a
|
|
@@ -38,29 +38,22 @@ def for_vdevice(
|
|
|
38
38
|
|
|
39
39
|
:param with_connections: the assigned connection trees for this class/method (default: a universal connection)
|
|
40
40
|
"""
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
f"has to be a subclass of `{Connection.__name__}`")
|
|
55
|
-
tuple_idx += 1
|
|
56
|
-
elif not isinstance(cur_conn, Connection):
|
|
57
|
-
raise ValueError(f"the given type on position `{idx}` has to be a subclass of `{Connection.__name__}` or "
|
|
58
|
-
f"a element of it")
|
|
59
|
-
|
|
60
|
-
idx += 1
|
|
41
|
+
if isinstance(with_connections, Connection):
|
|
42
|
+
# do nothing
|
|
43
|
+
pass
|
|
44
|
+
elif isinstance(with_connections, (AndConnectionRelation, OrConnectionRelation)):
|
|
45
|
+
# use container connection
|
|
46
|
+
with_connections = Connection.based_on(with_connections)
|
|
47
|
+
elif isinstance(with_connections, type) and issubclass(with_connections, Connection):
|
|
48
|
+
# instantiate it
|
|
49
|
+
with_connections = with_connections()
|
|
50
|
+
else:
|
|
51
|
+
raise TypeError(f"the given element ``with_connection`` needs to be from type `AndConnectionRelation`, "
|
|
52
|
+
f"`OrConnectionRelation` or `Connection` - `{type(with_connections)}` is not allowed")
|
|
53
|
+
|
|
61
54
|
# note: if `args` is an empty list - no special sub-connection-tree bindings
|
|
62
55
|
|
|
63
|
-
if not isinstance(vdevice, str)
|
|
56
|
+
if not (isinstance(vdevice, str) or (isinstance(vdevice, type) and issubclass(vdevice, VDevice))):
|
|
64
57
|
raise ValueError('the given element for `vdevice` has to be a `str` or has to be a subclass of'
|
|
65
58
|
'`VDevice`')
|
|
66
59
|
|
|
@@ -74,9 +67,7 @@ def for_vdevice(
|
|
|
74
67
|
self.func = func
|
|
75
68
|
|
|
76
69
|
# we detect a decorated non-class object -> save it and check it later in collector
|
|
77
|
-
|
|
78
|
-
Collector._possible_method_variations[func] = []
|
|
79
|
-
Collector._possible_method_variations[func].append((vdevice, with_connections))
|
|
70
|
+
Collector.register_possible_method_variation(func, vdevice, with_connections)
|
|
80
71
|
|
|
81
72
|
def __new__(cls, *args, **kwargs): # pylint: disable=unused-argument
|
|
82
73
|
nonlocal vdevice
|
|
@@ -107,7 +98,6 @@ def for_vdevice(
|
|
|
107
98
|
|
|
108
99
|
vdevice = relevant_vdevices[0]
|
|
109
100
|
cls_for_vdevice = fn_feature_controller.get_class_based_for_vdevice()
|
|
110
|
-
cls_for_vdevice = {} if cls_for_vdevice is None else cls_for_vdevice
|
|
111
101
|
if vdevice in cls_for_vdevice.keys():
|
|
112
102
|
raise DuplicateForVDeviceError(
|
|
113
103
|
f'there already exists a decorator for the vDevice `{vdevice}` in the Feature class '
|
_balder/decorator_gateway.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from _balder.controllers.device_controller import DeviceController
|
|
3
4
|
from _balder.device import Device
|
|
4
5
|
from _balder.node_gateway import NodeGateway
|
|
5
6
|
|
|
@@ -25,10 +26,9 @@ def gateway(from_node: str, to_node: str, bidirectional: bool = True):
|
|
|
25
26
|
raise TypeError(
|
|
26
27
|
f"The decorator `gateway` may only be used for `Device` objects. This is not possible for the applied "
|
|
27
28
|
f"class `{cls.__name__}`.")
|
|
29
|
+
decorated_cls_device_controller = DeviceController.get_for(cls)
|
|
28
30
|
|
|
29
|
-
if not hasattr(cls, '_gateways'):
|
|
30
|
-
cls._gateways = []
|
|
31
31
|
new_gateway = NodeGateway(cls, from_node, to_node, bidirectional)
|
|
32
|
-
|
|
32
|
+
decorated_cls_device_controller.add_new_raw_gateway(new_gateway)
|
|
33
33
|
return cls
|
|
34
34
|
return decorator
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Iterable, Any
|
|
3
|
+
|
|
4
|
+
import inspect
|
|
5
|
+
from _balder.collector import Collector
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def parametrize(
|
|
9
|
+
field_name: str,
|
|
10
|
+
values: Iterable[Any],
|
|
11
|
+
):
|
|
12
|
+
"""
|
|
13
|
+
Allows to parametrize a test function. This decorator will be used to statically parametrize a test function.
|
|
14
|
+
|
|
15
|
+
:param field_name: the field name of the test function
|
|
16
|
+
|
|
17
|
+
:param values: an iterable of values, that should be used to parametrize the test function
|
|
18
|
+
"""
|
|
19
|
+
if not isinstance(field_name, str):
|
|
20
|
+
raise ValueError('the given field name must be a string')
|
|
21
|
+
|
|
22
|
+
def decorator(func):
|
|
23
|
+
nonlocal field_name
|
|
24
|
+
nonlocal values
|
|
25
|
+
|
|
26
|
+
if not inspect.isfunction(func):
|
|
27
|
+
raise TypeError('the decorated object needs to be a test method')
|
|
28
|
+
|
|
29
|
+
Collector.register_possible_parametrization(func, field_name, values)
|
|
30
|
+
return func
|
|
31
|
+
return decorator
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Dict, Tuple, Type
|
|
3
|
+
|
|
4
|
+
import inspect
|
|
5
|
+
from _balder.collector import Collector
|
|
6
|
+
from _balder.device import Device
|
|
7
|
+
from _balder.parametrization import FeatureAccessSelector, Parameter, Value
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def parametrize_by_feature(
|
|
11
|
+
field_name: str,
|
|
12
|
+
feature_accessor: Tuple[Type[Device], str, str],
|
|
13
|
+
parameter: Dict[str, FeatureAccessSelector | Parameter | Value] = None
|
|
14
|
+
):
|
|
15
|
+
"""
|
|
16
|
+
Allows to parametrize a test function. This decorator will be used to dynamically parametrize a test function, by
|
|
17
|
+
the value a setup feature returns before entering the test.
|
|
18
|
+
|
|
19
|
+
:param field_name: the field name of the test function
|
|
20
|
+
:param feature_accessor: a tuple that provides information for accessing the feature
|
|
21
|
+
:param parameter: the parameter to parametrize the feature method (if necessary)
|
|
22
|
+
"""
|
|
23
|
+
if not isinstance(field_name, str):
|
|
24
|
+
raise ValueError('the given field name must be a string')
|
|
25
|
+
if parameter is None:
|
|
26
|
+
parameter = {}
|
|
27
|
+
|
|
28
|
+
feature_accessor = FeatureAccessSelector(*feature_accessor, parameters=parameter)
|
|
29
|
+
|
|
30
|
+
def decorator(func):
|
|
31
|
+
if not inspect.isfunction(func):
|
|
32
|
+
raise TypeError('the decorated object needs to be a test method')
|
|
33
|
+
|
|
34
|
+
Collector.register_possible_parametrization(func, field_name, feature_accessor)
|
|
35
|
+
return func
|
|
36
|
+
return decorator
|
_balder/exceptions.py
CHANGED
|
@@ -158,6 +158,12 @@ class IllegalVDeviceMappingError(BalderException):
|
|
|
158
158
|
"""
|
|
159
159
|
|
|
160
160
|
|
|
161
|
+
class MissingFeaturesOfVDeviceError(BalderException):
|
|
162
|
+
"""
|
|
163
|
+
is thrown if the related device does not implement all features specified in its mapped VDevice.
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
|
|
161
167
|
class NotApplicableVariationException(BalderException):
|
|
162
168
|
"""
|
|
163
169
|
is thrown internally after the current variation is not applicable
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
import traceback
|
|
9
|
+
from abc import ABC, abstractmethod
|
|
10
|
+
|
|
11
|
+
from _balder.executor.basic_executor import BasicExecutor
|
|
12
|
+
from _balder.testresult import ResultState, TestcaseResult
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from _balder.fixture_execution_level import FixtureExecutionLevel
|
|
16
|
+
from _balder.fixture_manager import FixtureManager
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BasicExecutableExecutor(BasicExecutor, ABC):
|
|
20
|
+
"""
|
|
21
|
+
The BasicExecutor class is an abstract class that represents the parent class of all executors. Together with other
|
|
22
|
+
executor classes, an executor forms a tree structure in which individual tests, which later on are executed, are
|
|
23
|
+
assigned to individual scenarios
|
|
24
|
+
"""
|
|
25
|
+
fixture_execution_level: FixtureExecutionLevel = None
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
super().__init__()
|
|
29
|
+
|
|
30
|
+
# holds the execution time of this branch (with branch fixtures)
|
|
31
|
+
self.execution_time_sec = None
|
|
32
|
+
|
|
33
|
+
# ---------------------------------- STATIC METHODS ----------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
# ---------------------------------- CLASS METHODS ----------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
# ---------------------------------- PROPERTIES --------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def fixture_manager(self) -> FixtureManager:
|
|
42
|
+
"""returns the active fixture manager instance"""
|
|
43
|
+
|
|
44
|
+
# ---------------------------------- PROTECTED METHODS -------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def _prepare_execution(self, show_discarded):
|
|
48
|
+
"""
|
|
49
|
+
This method runs before the branch will be executed and before the fixture construction code of this branch
|
|
50
|
+
runs.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
@abstractmethod
|
|
54
|
+
def _body_execution(self, show_discarded):
|
|
55
|
+
"""
|
|
56
|
+
This method runs between the fixture construction and teardown code. It should trigger the execution of the
|
|
57
|
+
child branches.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
@abstractmethod
|
|
61
|
+
def _cleanup_execution(self, show_discarded):
|
|
62
|
+
"""
|
|
63
|
+
This method runs after the branch was executed (also after the fixture teardown code ran)
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
# ---------------------------------- METHODS -----------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
def set_result_for_whole_branch(self, value: ResultState):
|
|
69
|
+
"""
|
|
70
|
+
This method sets the executor result for all sub executors.
|
|
71
|
+
|
|
72
|
+
:param value: the new value that should be set for this branch
|
|
73
|
+
"""
|
|
74
|
+
if value not in (ResultState.SKIP, ResultState.COVERED_BY, ResultState.NOT_RUN):
|
|
75
|
+
raise ValueError("can not set a state that is not NOT_RUN, SKIP or COVERED_BY for a whole branch")
|
|
76
|
+
for cur_child_executor in self.all_child_executors:
|
|
77
|
+
if isinstance(cur_child_executor.body_result, TestcaseResult):
|
|
78
|
+
cur_child_executor.body_result.set_result(result=value, exception=None)
|
|
79
|
+
|
|
80
|
+
@abstractmethod
|
|
81
|
+
def cleanup_empty_executor_branches(self, consider_discarded=False):
|
|
82
|
+
"""
|
|
83
|
+
This method searches the whole tree and removes branches where an executor item has no own children. It can
|
|
84
|
+
remove these branches, because they have no valid matchings.
|
|
85
|
+
|
|
86
|
+
:param consider_discarded: true if this method should consider discarded branches, otherwise False
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
def filter_tree_for_user_filters(self):
|
|
90
|
+
"""
|
|
91
|
+
This method calls all user defined filters that are to be applied to the executor tree.
|
|
92
|
+
"""
|
|
93
|
+
for cur_child_executor in self.all_child_executors:
|
|
94
|
+
cur_child_executor.filter_tree_for_user_filters()
|
|
95
|
+
|
|
96
|
+
def execute(self, show_discarded=False):
|
|
97
|
+
"""
|
|
98
|
+
Executes the whole branch
|
|
99
|
+
"""
|
|
100
|
+
start_time = time.perf_counter()
|
|
101
|
+
self._prepare_execution(show_discarded=show_discarded)
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
try:
|
|
105
|
+
if self.has_runnable_tests():
|
|
106
|
+
self.fixture_manager.enter(self)
|
|
107
|
+
self.construct_result.set_result(ResultState.SUCCESS)
|
|
108
|
+
|
|
109
|
+
self._body_execution(show_discarded=show_discarded)
|
|
110
|
+
except Exception as exc: # pylint: disable=broad-exception-caught
|
|
111
|
+
# this has to be a construction fixture error
|
|
112
|
+
traceback.print_exception(*sys.exc_info())
|
|
113
|
+
self.construct_result.set_result(ResultState.ERROR, exc)
|
|
114
|
+
finally:
|
|
115
|
+
if self.has_runnable_tests():
|
|
116
|
+
if self.fixture_manager.is_allowed_to_leave(self):
|
|
117
|
+
self.fixture_manager.leave(self)
|
|
118
|
+
self.teardown_result.set_result(ResultState.SUCCESS)
|
|
119
|
+
except Exception as exc: # pylint: disable=broad-exception-caught
|
|
120
|
+
# this has to be a teardown fixture error
|
|
121
|
+
traceback.print_exception(*sys.exc_info())
|
|
122
|
+
self.teardown_result.set_result(ResultState.ERROR, exc)
|
|
123
|
+
|
|
124
|
+
self._cleanup_execution(show_discarded=show_discarded)
|
|
125
|
+
|
|
126
|
+
self.execution_time_sec = time.perf_counter() - start_time
|