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.
Files changed (53) hide show
  1. _balder/_version.py +1 -1
  2. _balder/balder_session.py +4 -2
  3. _balder/cnnrelations/__init__.py +7 -0
  4. _balder/cnnrelations/and_connection_relation.py +149 -0
  5. _balder/cnnrelations/base_connection_relation.py +270 -0
  6. _balder/cnnrelations/or_connection_relation.py +65 -0
  7. _balder/collector.py +116 -14
  8. _balder/connection.py +400 -881
  9. _balder/connection_metadata.py +255 -0
  10. _balder/console/balder.py +1 -1
  11. _balder/controllers/device_controller.py +26 -12
  12. _balder/controllers/feature_controller.py +63 -99
  13. _balder/controllers/normal_scenario_setup_controller.py +7 -7
  14. _balder/controllers/scenario_controller.py +97 -7
  15. _balder/controllers/setup_controller.py +2 -3
  16. _balder/decorator_connect.py +12 -10
  17. _balder/decorator_fixture.py +4 -7
  18. _balder/decorator_for_vdevice.py +21 -31
  19. _balder/decorator_gateway.py +3 -3
  20. _balder/decorator_parametrize.py +31 -0
  21. _balder/decorator_parametrize_by_feature.py +36 -0
  22. _balder/exceptions.py +6 -0
  23. _balder/executor/basic_executable_executor.py +126 -0
  24. _balder/executor/basic_executor.py +1 -78
  25. _balder/executor/executor_tree.py +7 -5
  26. _balder/executor/parametrized_testcase_executor.py +52 -0
  27. _balder/executor/scenario_executor.py +5 -2
  28. _balder/executor/setup_executor.py +5 -2
  29. _balder/executor/testcase_executor.py +41 -9
  30. _balder/executor/unresolved_parametrized_testcase_executor.py +209 -0
  31. _balder/executor/variation_executor.py +148 -123
  32. _balder/feature.py +1 -1
  33. _balder/fixture_definition_scope.py +19 -0
  34. _balder/fixture_execution_level.py +22 -0
  35. _balder/fixture_manager.py +170 -182
  36. _balder/fixture_metadata.py +26 -0
  37. _balder/objects/connections/osi_3_network.py +2 -2
  38. _balder/objects/connections/osi_4_transport.py +2 -2
  39. _balder/parametrization.py +75 -0
  40. _balder/routing_path.py +18 -25
  41. _balder/solver.py +52 -32
  42. _balder/testresult.py +1 -1
  43. _balder/utils.py +27 -1
  44. balder/__init__.py +6 -0
  45. balder/exceptions.py +4 -3
  46. balder/parametrization.py +8 -0
  47. {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b11.dist-info}/METADATA +2 -2
  48. baldertest-0.1.0b11.dist-info/RECORD +83 -0
  49. {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b11.dist-info}/WHEEL +1 -1
  50. baldertest-0.1.0b9.dist-info/RECORD +0 -68
  51. {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b11.dist-info}/LICENSE +0 -0
  52. {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b11.dist-info}/entry_points.txt +0 -0
  53. {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 Exception(
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(None)
378
- new_cnn.set_metadata_for_all_subitems(
379
- {"from_device": related_from_device, "to_device": related_to_device,
380
- "from_device_node_name": cur_parent_cnn.from_node_name,
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 = Connection.based_on(*FeatureController.get_for(
129
- cur_feature.__class__).get_abs_class_based_for_vdevice()[mapped_vdevice])
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
- cur_feature_class_based_for_vdevice = \
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 = Connection.based_on(*feature_class_based_for_vdevice[mapped_vdevice])
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
@@ -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(with_device: Union[Type[Device], str], over_connection: Union[Type[Connection], Connection],
11
- self_node_name: str = None, dest_node_name: str = None):
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
- cur_cnn_instance.set_devices(from_device=decorated_cls, to_device=with_device)
98
- cur_cnn_instance.update_node_names(from_device_node_name=self_node_name, to_device_node_name=dest_node_name)
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
@@ -3,7 +3,7 @@ from typing import Literal
3
3
 
4
4
  import functools
5
5
  from _balder.collector import Collector
6
- from _balder.fixture_manager import FixtureManager
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 = FixtureManager.EXECUTION_LEVEL_ORDER
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 add the fixture to FixtureManager.raw_fixtures - class determination will be done later by
22
- # :meth:`Collector`
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):
@@ -1,10 +1,10 @@
1
1
  from __future__ import annotations
2
- from typing import List, Union, Type, Tuple
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, Device],
15
+ vdevice: Union[str, Type[VDevice]],
16
16
  with_connections: Union[
17
- Type[Connection], Connection, Tuple[Union[Type[Connection], Connection]],
18
- List[Union[Type[Connection], Connection, Tuple[Union[Type[Connection], Connection]]]]] = Connection(),
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
- idx = 0
42
- if not isinstance(with_connections, list):
43
- with_connections = [with_connections]
44
- for cur_conn in with_connections:
45
- if isinstance(cur_conn, type):
46
- if not issubclass(cur_conn, Connection):
47
- raise ValueError(f"the given element type for the element on position `{idx}` has to be a subclass of "
48
- f"`{Connection.__name__}` or a element of it")
49
- elif isinstance(cur_conn, tuple):
50
- tuple_idx = 0
51
- for cur_tuple_elem in cur_conn:
52
- if not isinstance(cur_tuple_elem, Connection) and not issubclass(cur_tuple_elem, Connection):
53
- raise ValueError(f"the given tuple element `{tuple_idx}` that is given on position `{idx}` "
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) and not isinstance(vdevice, VDevice):
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
- if func not in Collector._possible_method_variations.keys():
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 '
@@ -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
- cls._gateways.append(new_gateway)
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