baldertest 0.1.0__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 (89) hide show
  1. _balder/__init__.py +12 -0
  2. _balder/_version.py +34 -0
  3. _balder/balder_plugin.py +73 -0
  4. _balder/balder_session.py +341 -0
  5. _balder/balder_settings.py +15 -0
  6. _balder/cnnrelations/__init__.py +7 -0
  7. _balder/cnnrelations/and_connection_relation.py +176 -0
  8. _balder/cnnrelations/base_connection_relation.py +270 -0
  9. _balder/cnnrelations/or_connection_relation.py +65 -0
  10. _balder/collector.py +874 -0
  11. _balder/connection.py +863 -0
  12. _balder/connection_metadata.py +255 -0
  13. _balder/console/__init__.py +0 -0
  14. _balder/console/balder.py +58 -0
  15. _balder/controllers/__init__.py +12 -0
  16. _balder/controllers/base_device_controller.py +72 -0
  17. _balder/controllers/controller.py +29 -0
  18. _balder/controllers/device_controller.py +446 -0
  19. _balder/controllers/feature_controller.py +715 -0
  20. _balder/controllers/normal_scenario_setup_controller.py +402 -0
  21. _balder/controllers/scenario_controller.py +524 -0
  22. _balder/controllers/setup_controller.py +134 -0
  23. _balder/controllers/vdevice_controller.py +95 -0
  24. _balder/decorator_connect.py +104 -0
  25. _balder/decorator_covered_by.py +74 -0
  26. _balder/decorator_fixture.py +29 -0
  27. _balder/decorator_for_vdevice.py +118 -0
  28. _balder/decorator_gateway.py +34 -0
  29. _balder/decorator_insert_into_tree.py +52 -0
  30. _balder/decorator_parametrize.py +31 -0
  31. _balder/decorator_parametrize_by_feature.py +36 -0
  32. _balder/device.py +18 -0
  33. _balder/exceptions.py +182 -0
  34. _balder/executor/__init__.py +0 -0
  35. _balder/executor/basic_executable_executor.py +133 -0
  36. _balder/executor/basic_executor.py +205 -0
  37. _balder/executor/executor_tree.py +217 -0
  38. _balder/executor/parametrized_testcase_executor.py +52 -0
  39. _balder/executor/scenario_executor.py +169 -0
  40. _balder/executor/setup_executor.py +163 -0
  41. _balder/executor/testcase_executor.py +203 -0
  42. _balder/executor/unresolved_parametrized_testcase_executor.py +184 -0
  43. _balder/executor/variation_executor.py +882 -0
  44. _balder/exit_code.py +19 -0
  45. _balder/feature.py +74 -0
  46. _balder/feature_replacement_mapping.py +107 -0
  47. _balder/feature_vdevice_mapping.py +88 -0
  48. _balder/fixture_definition_scope.py +19 -0
  49. _balder/fixture_execution_level.py +22 -0
  50. _balder/fixture_manager.py +483 -0
  51. _balder/fixture_metadata.py +26 -0
  52. _balder/node_gateway.py +103 -0
  53. _balder/objects/__init__.py +0 -0
  54. _balder/objects/connections/__init__.py +0 -0
  55. _balder/objects/connections/osi_1_physical.py +116 -0
  56. _balder/objects/connections/osi_2_datalink.py +35 -0
  57. _balder/objects/connections/osi_3_network.py +47 -0
  58. _balder/objects/connections/osi_4_transport.py +40 -0
  59. _balder/objects/connections/osi_5_session.py +13 -0
  60. _balder/objects/connections/osi_6_presentation.py +13 -0
  61. _balder/objects/connections/osi_7_application.py +83 -0
  62. _balder/objects/devices/__init__.py +0 -0
  63. _balder/objects/devices/this_device.py +12 -0
  64. _balder/parametrization.py +75 -0
  65. _balder/plugin_manager.py +138 -0
  66. _balder/previous_executor_mark.py +23 -0
  67. _balder/routing_path.py +335 -0
  68. _balder/scenario.py +20 -0
  69. _balder/setup.py +18 -0
  70. _balder/solver.py +246 -0
  71. _balder/testresult.py +163 -0
  72. _balder/unmapped_vdevice.py +13 -0
  73. _balder/utils/__init__.py +0 -0
  74. _balder/utils/functions.py +103 -0
  75. _balder/utils/inner_device_managing_metaclass.py +14 -0
  76. _balder/utils/mixin_can_be_covered_by_executor.py +24 -0
  77. _balder/utils/typings.py +4 -0
  78. _balder/vdevice.py +9 -0
  79. balder/__init__.py +56 -0
  80. balder/connections.py +43 -0
  81. balder/devices.py +9 -0
  82. balder/exceptions.py +44 -0
  83. balder/parametrization.py +8 -0
  84. baldertest-0.1.0.dist-info/METADATA +356 -0
  85. baldertest-0.1.0.dist-info/RECORD +89 -0
  86. baldertest-0.1.0.dist-info/WHEEL +5 -0
  87. baldertest-0.1.0.dist-info/entry_points.txt +2 -0
  88. baldertest-0.1.0.dist-info/licenses/LICENSE +21 -0
  89. baldertest-0.1.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,335 @@
1
+ from __future__ import annotations
2
+ from typing import List, Union, Dict, Type, Tuple, Iterable, TYPE_CHECKING
3
+
4
+ import copy
5
+ from _balder.connection import Connection
6
+ from _balder.node_gateway import NodeGateway
7
+ from _balder.controllers import DeviceController
8
+ from _balder.exceptions import RoutingBrokenChainError
9
+
10
+ if TYPE_CHECKING:
11
+ from _balder.device import Device
12
+
13
+
14
+ class RoutingPath:
15
+ """
16
+ This object describes a possible routing path between two devices (which may be possible via several individual
17
+ connections). If there are several possible paths between two devices, there are correspondingly many of these
18
+ RoutingPath objects.
19
+ """
20
+
21
+ def __init__(self, *routing_elems: Union[Connection, NodeGateway], start_device: Type[Device],
22
+ start_node_name: str):
23
+ """
24
+ :param routing_elems: all elements that are parts of this route
25
+
26
+ :param start_device: the device this route starts with (if the first element is a gateway this is optional)
27
+
28
+ :param start_node_name: the node this route starts from (always required!)
29
+ """
30
+
31
+ self._routing_elems = []
32
+
33
+ self._start_device = start_device
34
+ self._start_node_name = start_node_name
35
+
36
+ if not isinstance(routing_elems[0], Connection):
37
+ raise TypeError("the first element is no `Connection` object - every route has to start and end with a"
38
+ "`Connection` object")
39
+ if not isinstance(routing_elems[-1], Connection):
40
+ raise TypeError("the last element is no `Connection` object - every route has to start and end with a"
41
+ "`Connection` object")
42
+
43
+ first_elem = routing_elems[0]
44
+ if isinstance(first_elem, Connection):
45
+ if self._start_device not in (first_elem.from_device, first_elem.to_device):
46
+ raise ValueError(f"the given `start_device={self._start_device.__name__}` does not match with one of "
47
+ f"the available devices of the first routing element")
48
+ if self._start_node_name not in (first_elem.from_node_name, first_elem.to_node_name):
49
+ raise ValueError(f"the given `start_node_name={self._start_node_name}` does not match with one of the "
50
+ f"available node names of the first routing element")
51
+ else:
52
+ raise TypeError(f"the first given element is not an instance of `{Connection.__name__}` or "
53
+ f"`{NodeGateway.__name__}` (is `{type(first_elem)}`)")
54
+
55
+ for cur_elem in routing_elems:
56
+ self.append_element(cur_elem)
57
+
58
+ # ---------------------------------- STATIC METHODS ----------------------------------------------------------------
59
+
60
+ @staticmethod
61
+ def __get_abs_setup_dev_cnns_for(setup_devices: Iterable[Type[Device]]) -> List[Connection]:
62
+ """
63
+ Determines all absolute device connections for a given list of setup devices.
64
+ """
65
+ setup_devices_cnns = []
66
+ for cur_setup_device in setup_devices:
67
+ for cur_cnn_list in DeviceController.get_for(cur_setup_device).get_all_absolute_connections().values():
68
+ setup_devices_cnns.extend(cur_cnn_list)
69
+ # remove duplicates
70
+ return list(set(setup_devices_cnns))
71
+
72
+ @staticmethod
73
+ def route_through(
74
+ scenario_connection: Connection,
75
+ device_mapping: Dict[Type[Device], Type[Device]],
76
+ alternative_setup_device_cnns: Union[List[Connection], None] = None
77
+ ) -> List[RoutingPath]:
78
+ """
79
+ This static method tries to route the given ``scenario_connection`` with the device_mapping. It returns a list
80
+ of all matched routings between the mapped devices, where the routing is valid to support the requested
81
+ `scenario_connection`.
82
+
83
+ :param scenario_connection: the scenario-device connection object
84
+
85
+ :param device_mapping: the used device mapping for the given `scenario_connection`
86
+
87
+ :param alternative_setup_device_cnns: the alternative used connections between all relevant setup devices (if
88
+ this is none, the router uses the setup-device connections from method
89
+ `get_all_absolute_connections()`, otherwise it uses this dictionary here)
90
+ """
91
+ setup_devices_cnns = alternative_setup_device_cnns
92
+ if alternative_setup_device_cnns is None:
93
+ setup_devices_cnns = RoutingPath.__get_abs_setup_dev_cnns_for(device_mapping.values())
94
+
95
+ from_setup_device = device_mapping[scenario_connection.from_device]
96
+ to_setup_device = device_mapping[scenario_connection.to_device]
97
+
98
+ # contains a list with all routes that start and end correctly
99
+ all_completed_routes = []
100
+ # contains a list with all possible routes
101
+ all_possible_routes = []
102
+
103
+ # add all connection objects that are mentioned in `from_setup_device`
104
+ for cur_from_setup_node_conn in setup_devices_cnns:
105
+ # only if there is a connection outgoing from `from_setup_device`
106
+ if cur_from_setup_node_conn.has_connection_from_to(start_device=from_setup_device):
107
+ all_possible_routes.append(
108
+ RoutingPath(
109
+ cur_from_setup_node_conn,
110
+ start_device=from_setup_device,
111
+ start_node_name=(cur_from_setup_node_conn.from_node_name
112
+ if cur_from_setup_node_conn.from_device == from_setup_device
113
+ else cur_from_setup_node_conn.to_node_name)
114
+ )
115
+ )
116
+ # now go through every possibility and add them - filter all Routes that ``has_loop() == True`` or
117
+ # are completed
118
+ while len(all_possible_routes) > 0:
119
+
120
+ # remove all routings that have a loop
121
+ all_possible_routes = [route for route in all_possible_routes.copy() if not route.has_loop()]
122
+
123
+ # remove all not working routing because they have the wrong connection type, by checking that one part
124
+ # connection matches the requirements of the given `scenario_connection`
125
+ all_possible_routes = [
126
+ r for r in all_possible_routes
127
+ if scenario_connection.contained_in(r.get_virtual_connection(), ignore_metadata=True)
128
+ ]
129
+
130
+ # move all completely routed connections to `all_completed_routes`
131
+ for cur_routing in all_possible_routes.copy():
132
+ if cur_routing.end_device == to_setup_device:
133
+ # note that all routes already have a virtual connection that matches the requirements of given
134
+ # `scenario_connection`
135
+ all_completed_routes.append(cur_routing)
136
+ all_possible_routes.remove(cur_routing)
137
+
138
+ new_possible_routes = []
139
+ for cur_routing in all_possible_routes.copy():
140
+ # add all existing connections
141
+ all_next_conns = [cur_cnn for cur_cnn in setup_devices_cnns
142
+ if ((cur_cnn.from_device == cur_routing.end_device and
143
+ cur_cnn.from_node_name == cur_routing.end_node_name)
144
+ or (cur_cnn.to_device == cur_routing.end_device and
145
+ cur_cnn.to_node_name == cur_routing.end_node_name))]
146
+ for cur_next_conn in all_next_conns:
147
+ if cur_next_conn == cur_routing.elements[-1]:
148
+ # is the same connection as the last of routing -> SKIP
149
+ continue
150
+
151
+ if cur_next_conn.has_connection_from_to(start_device=cur_routing.end_device):
152
+ # the connection allows the direction the routing needs - only then add it
153
+ copied_routing = cur_routing.copy()
154
+ copied_routing.append_element(cur_next_conn)
155
+ new_possible_routes.append(copied_routing)
156
+ # add all possible gateways
157
+ # for cur_next_gateway in cur_routing.end_device._get_all_gateways():
158
+ # if cur_next_gateway == cur_routing.elements[-1]:
159
+ # # is the same gateway as the last one of the routing -> SKIP
160
+ # continue
161
+ # elif cur_next_gateway.has_connection_from_to(start_node=cur_routing.end_node_name):
162
+ # # the gateway allows the direction the routing needs - only then add it
163
+ # copied_routing = cur_routing.copy()
164
+ # copied_routing.append_element(cur_next_gateway)
165
+ # all_possible_routes.append(copied_routing)
166
+ all_possible_routes = new_possible_routes
167
+
168
+ return all_completed_routes
169
+
170
+ # ---------------------------------- CLASS METHODS ----------------------------------------------------------------
171
+
172
+ # ---------------------------------- PROPERTIES --------------------------------------------------------------------
173
+
174
+ @property
175
+ def elements(self) -> List[Union[Connection, NodeGateway]]:
176
+ """returns all elements that belongs to this routing path"""
177
+ return self._routing_elems
178
+
179
+ @property
180
+ def start_device(self) -> Type[Device]:
181
+ """returns the device the route starts from"""
182
+ return self._start_device
183
+
184
+ @property
185
+ def start_node_name(self) -> str:
186
+ """returns the node of the `start_device` this route starts from"""
187
+ return self._start_node_name
188
+
189
+ @property
190
+ def end_device(self) -> Type[Device]:
191
+ """returns the device the route ends"""
192
+ return self._get_end_device_and_node()[0]
193
+
194
+ @property
195
+ def end_node_name(self) -> str:
196
+ """returns the node of the `end_device` this route ends"""
197
+ return self._get_end_device_and_node()[1]
198
+
199
+ # ---------------------------------- PROTECTED METHODS -------------------------------------------------------------
200
+
201
+ def _get_end_device_and_node(self, till_idx: int = None) -> Tuple[Type[Device], str]:
202
+ """
203
+ helper method that determines the end_device and end_node_name
204
+
205
+ :param till_idx: the index of the latest element that should be considered
206
+
207
+ :return: a tuple with the latest device and node
208
+ """
209
+ cur_device = self._start_device
210
+ cur_node_name = self._start_node_name
211
+
212
+ elements = self._routing_elems
213
+ if till_idx is not None:
214
+ elements = self._routing_elems[:till_idx+1]
215
+
216
+ for cur_route_elem in elements:
217
+ if isinstance(cur_route_elem, NodeGateway):
218
+ if cur_node_name == cur_route_elem.from_node_name:
219
+ cur_node_name = cur_route_elem.to_node_name
220
+ elif cur_node_name == cur_route_elem.to_node_name:
221
+ cur_node_name = cur_route_elem.from_node_name
222
+ else:
223
+ raise RoutingBrokenChainError(
224
+ f"can not chain the routing element `{cur_route_elem.__name__}` with the device "
225
+ f"`{cur_device.__name__}` and node `{cur_node_name}` of element before")
226
+ else:
227
+ if cur_node_name == cur_route_elem.from_node_name and cur_device == cur_route_elem.from_device:
228
+ cur_node_name = cur_route_elem.to_node_name
229
+ cur_device = cur_route_elem.to_device
230
+ elif cur_node_name == cur_route_elem.to_node_name and cur_device == cur_route_elem.to_device:
231
+ cur_node_name = cur_route_elem.from_node_name
232
+ cur_device = cur_route_elem.from_device
233
+ else:
234
+ raise RoutingBrokenChainError(
235
+ f"can not chain the routing element `{cur_route_elem.__name__}` with the device "
236
+ f"`{cur_device.__name__}` and node `{cur_node_name}` of element before")
237
+ return cur_device, cur_node_name
238
+
239
+ # ---------------------------------- METHODS -----------------------------------------------------------------------
240
+
241
+ def has_loop(self) -> bool:
242
+ """
243
+ This method returns True if it detects an internal loop. An internal loop is given, if one :class:`Device`/node
244
+ pair is mentioned twice (or more) in internal `routing_elements`.
245
+ """
246
+ all_contact_points = [(self.start_device, self.start_node_name)]
247
+
248
+ for idx in range(len(self._routing_elems)):
249
+ next_device, next_node = self._get_end_device_and_node(idx)
250
+ # now check if one point is mentioned twice
251
+ if (next_device, next_node) in all_contact_points:
252
+ return True
253
+ all_contact_points.append((next_device, next_node))
254
+
255
+ return False
256
+
257
+ def is_bidirectional(self):
258
+ """
259
+ This method checks whether the route is completely bidirectional. If only one connection is not bidirectional,
260
+ it will return False.
261
+ """
262
+ for cur_elem in self._routing_elems:
263
+ if isinstance(cur_elem, Connection):
264
+ if not cur_elem.is_bidirectional():
265
+ return False
266
+ else:
267
+ if not cur_elem.is_bidirectional():
268
+ return False
269
+ return True
270
+
271
+ def copy(self) -> RoutingPath:
272
+ """
273
+ This method creates a copy of this routing.
274
+ """
275
+ copied_elem = copy.copy(self)
276
+ # also copy list reference
277
+ copied_elem._routing_elems = self._routing_elems.copy() # pylint: disable=protected-access
278
+ return copied_elem
279
+
280
+ def append_element(self, elem: Union[Connection, NodeGateway]) -> None:
281
+ """
282
+ This method appends an element to the route.
283
+ """
284
+ elem_before = None if len(self._routing_elems) == 0 else self._routing_elems[-1]
285
+
286
+ if not isinstance(elem, Connection) and not isinstance(elem, NodeGateway):
287
+ raise TypeError(f"the given attribute passed at position {len(self._routing_elems)} is not of the type "
288
+ f"`Connection` or `NodeGateway`")
289
+
290
+ if elem_before is not None:
291
+ # check if device and nodes of the two elements are the same -> have to be a chain
292
+ if isinstance(elem, Connection):
293
+ if (self.end_device, self.end_node_name) not in \
294
+ [(elem.from_device, elem.from_node_name), (elem.to_device, elem.to_node_name)]:
295
+ raise RoutingBrokenChainError(
296
+ f"can not append connection, because neither the from-device/node "
297
+ f"(device: `{elem.from_device.__name__}` | node: `{elem.from_node_name}`) nor the "
298
+ f"to-device/node (device: `{elem.to_device.__name__}` | node: `{elem.to_node_name}`) of the "
299
+ f"connection match with the latest end-device/node (device: `{self.end_device.__name__}` | "
300
+ f"node: `{self.end_node_name}`) of this route")
301
+ else:
302
+ # is a gateway class
303
+ if self.end_device != elem.device:
304
+ raise RoutingBrokenChainError(
305
+ f"can not append gateway, because the device of the gateway (`{elem.device}`) doesn't "
306
+ f"match with the latest end-device (`{self.end_device}`) of this route")
307
+ if self.end_node_name not in [elem.from_node_name, elem.to_node_name]:
308
+ raise RoutingBrokenChainError(
309
+ f"can not append gateway, because neither the from-node "
310
+ f"(`{elem.from_node_name}`) nor the to-node (`{elem.to_node_name}`) of the gateway match with "
311
+ f"the latest end-node (`{self.end_node_name}`) of this route")
312
+
313
+ self._routing_elems.append(elem)
314
+
315
+ def get_virtual_connection(self) -> Connection:
316
+ """
317
+ This method returns a virtual connection object that describes the connection type this routing
318
+ supports for all of its elements.
319
+ """
320
+ virtual_connection = self.elements[0].clone()
321
+ virtual_connection.set_metadata_for_all_subitems(None)
322
+ for cur_element in self.elements[1:]:
323
+ if isinstance(cur_element, Connection):
324
+ cur_element_clone = cur_element.clone()
325
+ cur_element_clone.set_metadata_for_all_subitems(None)
326
+ virtual_connection = cur_element_clone.intersection_with(virtual_connection)
327
+ else:
328
+ # is a gateway class
329
+ # todo
330
+ pass
331
+ # set metadata based on this routing
332
+ virtual_connection.metadata.set_from(from_device=self.start_device, from_device_node_name=self.start_node_name)
333
+ virtual_connection.metadata.set_to(to_device=self.end_device, to_device_node_name=self.end_node_name)
334
+
335
+ return virtual_connection
_balder/scenario.py ADDED
@@ -0,0 +1,20 @@
1
+ from __future__ import annotations
2
+ from _balder.utils.inner_device_managing_metaclass import InnerDeviceManagingMetaclass
3
+
4
+
5
+ class Scenario(metaclass=InnerDeviceManagingMetaclass):
6
+ """
7
+ This is the basic scenario class. It represents an abstract class that should be the base class for all scenarios.
8
+ """
9
+ SKIP = []
10
+ IGNORE = []
11
+
12
+ # ---------------------------------- STATIC METHODS ----------------------------------------------------------------
13
+
14
+ # ---------------------------------- CLASS METHODS ----------------------------------------------------------------
15
+
16
+ # ---------------------------------- PROPERTIES --------------------------------------------------------------------
17
+
18
+ # ---------------------------------- PROTECTED METHODS -------------------------------------------------------------
19
+
20
+ # ---------------------------------- METHODS -----------------------------------------------------------------------
_balder/setup.py ADDED
@@ -0,0 +1,18 @@
1
+ from __future__ import annotations
2
+ from _balder.utils.inner_device_managing_metaclass import InnerDeviceManagingMetaclass
3
+
4
+
5
+ class Setup(metaclass=InnerDeviceManagingMetaclass):
6
+ """
7
+ This is the abstract basic setup class. It has to be the base class for all setups.
8
+ """
9
+
10
+ # ---------------------------------- STATIC METHODS ----------------------------------------------------------------
11
+
12
+ # ---------------------------------- CLASS METHODS ----------------------------------------------------------------
13
+
14
+ # ---------------------------------- PROPERTIES --------------------------------------------------------------------
15
+
16
+ # ---------------------------------- PROTECTED METHODS -------------------------------------------------------------
17
+
18
+ # ---------------------------------- METHODS -----------------------------------------------------------------------
_balder/solver.py ADDED
@@ -0,0 +1,246 @@
1
+ from __future__ import annotations
2
+ from typing import List, Dict, Tuple, Type, Union, Callable, TYPE_CHECKING
3
+
4
+ import itertools
5
+ from _balder.fixture_manager import FixtureManager
6
+ from _balder.executor.executor_tree import ExecutorTree
7
+ from _balder.executor.setup_executor import SetupExecutor
8
+ from _balder.executor.scenario_executor import ScenarioExecutor
9
+ from _balder.executor.testcase_executor import TestcaseExecutor
10
+ from _balder.executor.variation_executor import VariationExecutor
11
+ from _balder.executor.parametrized_testcase_executor import ParametrizedTestcaseExecutor
12
+ from _balder.executor.unresolved_parametrized_testcase_executor import UnresolvedParametrizedTestcaseExecutor
13
+ from _balder.previous_executor_mark import PreviousExecutorMark
14
+ from _balder.controllers import ScenarioController, SetupController
15
+
16
+ if TYPE_CHECKING:
17
+ from _balder.setup import Setup
18
+ from _balder.device import Device
19
+ from _balder.scenario import Scenario
20
+ from _balder.connection import Connection
21
+ from _balder.plugin_manager import PluginManager
22
+
23
+
24
+ class Solver:
25
+ """
26
+ The solver class is the class to map corresponding :class:`Scenario` with :class:`Setup` classes. This class
27
+ determines all possibilities in which a scenario can be mapped to a setup constellation.
28
+ """
29
+
30
+ def __init__(self, setups: List[Type[Setup]], scenarios: List[Type[Scenario]], connections: List[Type[Connection]],
31
+ fixture_manager: Union[FixtureManager, None]):
32
+ #: contains all available setup classes
33
+ self._all_existing_setups = setups
34
+ #: contains all available scenario classes
35
+ self._all_existing_scenarios = scenarios
36
+ #: contains all available connection classes
37
+ self._all_existing_connections = connections
38
+ #: contains all mappings between :meth:`Scenario`'s and :meth:`Setup`'s
39
+ #: will contain all possibilities and will then be reduced to the really applicable set using the `filter_*()`
40
+ #: methods
41
+ self._mapping: List[Tuple[Type[Setup], Type[Scenario], Dict[Type[Device], Type[Device]]]] = []
42
+ self._resolving_was_executed = False
43
+
44
+ self._fixture_manager = fixture_manager
45
+
46
+ # ---------------------------------- STATIC METHODS ----------------------------------------------------------------
47
+
48
+ # ---------------------------------- CLASS METHODS -----------------------------------------------------------------
49
+
50
+ def _set_data_for_covered_by_in_tree(self, executor_tree: ExecutorTree):
51
+ """
52
+ This method updates the executor tree and sets all metadata for the `@covered_by()` decorator.
53
+
54
+ :param executor_tree: the executor tree object, that should be updated
55
+ """
56
+ # now determine all covered_by items
57
+ all_scenarios = executor_tree.get_all_scenario_executors()
58
+ all_testcases = executor_tree.get_all_testcase_executors()
59
+ all_available_testcase_functions = [
60
+ cur_testcase_executor.base_testcase_callable for cur_testcase_executor in all_testcases]
61
+ all_available_scenario_classes = [
62
+ cur_scenario_executor.base_scenario_class for cur_scenario_executor in all_scenarios]
63
+
64
+ covered_by_mapping_of_interest = {}
65
+ for cur_scenario in all_scenarios:
66
+ covered_by_item = cur_scenario.get_covered_by_element()
67
+ if covered_by_item is not None:
68
+ covered_by_mapping_of_interest[cur_scenario] = covered_by_item
69
+
70
+ for cur_testcase in all_testcases:
71
+ covered_by_item = cur_testcase.get_covered_by_element()
72
+ if covered_by_item is not None:
73
+ covered_by_mapping_of_interest[cur_testcase] = covered_by_item
74
+
75
+ # now go throw all covered_by items and check if the destination is contained in the tree -> set prev_mark
76
+ for cur_elem, covered_from_items in covered_by_mapping_of_interest.items():
77
+ all_matched_covered_from_executors = []
78
+ for cur_covered_from_item in covered_from_items:
79
+ if cur_covered_from_item in all_available_scenario_classes:
80
+ all_matched_covered_from_executors.append(
81
+ all_scenarios[all_available_scenario_classes.index(cur_covered_from_item)])
82
+
83
+ elif cur_covered_from_item in all_available_testcase_functions:
84
+ all_matched_covered_from_executors.append(
85
+ all_testcases[all_available_testcase_functions.index(cur_covered_from_item)])
86
+ if len(all_matched_covered_from_executors) > 0:
87
+ cur_elem.prev_mark = PreviousExecutorMark.COVERED_BY
88
+ cur_elem.covered_by_executors = all_matched_covered_from_executors
89
+
90
+ # ---------------------------------- PROPERTIES --------------------------------------------------------------------
91
+
92
+ @property
93
+ def all_mappings(self) -> List[Tuple[Type[Setup], Type[Scenario], Dict[Type[Device], Type[Device]]]]:
94
+ """
95
+ returns all still active mappings between :meth:`Scenario`'s and :meth:`Setup`'s.
96
+ """
97
+ if not self._mapping or self._resolving_was_executed is False:
98
+ raise AttributeError("please call the `resolve()` method before omitting this value")
99
+ return self._mapping
100
+
101
+ # ---------------------------------- PROTECTED METHODS -------------------------------------------------------------
102
+
103
+ def _get_all_unfiltered_mappings(self) -> List[Tuple[Type[Setup], Type[Scenario]]]:
104
+ """
105
+ This method searches for all possible hits for the internal lists `_all_existing_setups` and
106
+ `_all_existing_scenarios`. It returns all combinations of :meth:`Setup`'s and :meth:`Scenario`'s that could
107
+ exist.
108
+
109
+ :return: a list with tuple pairs that defines all possible pairs of :meth:`Setup` classes and :meth:`Scenario`
110
+ classes
111
+ """
112
+ matching_list = []
113
+ for cur_setup in self._all_existing_setups:
114
+ for cur_scenario in self._all_existing_scenarios:
115
+ matching_list.append((cur_setup, cur_scenario))
116
+ return matching_list
117
+
118
+ # ---------------------------------- METHODS -----------------------------------------------------------------------
119
+
120
+ def get_initial_mapping(self) -> List[Tuple[Type[Setup], Type[Scenario], Dict[Type[Device], Type[Device]]]]:
121
+ """
122
+ This method creates the initial amount of data for `self._mapping`. Only those elements are returned where the
123
+ :meth:`Setup` class has more or the same amount of :meth:`Device`'s than the :meth:`Scenario` class.
124
+ The method does not reduce the set with other criteria yet. This will be done later with filter methods.
125
+ """
126
+ setup_scenario_matches = self._get_all_unfiltered_mappings()
127
+
128
+ mapping = []
129
+
130
+ for cur_setup, cur_scenario in setup_scenario_matches:
131
+
132
+ setup_devices = SetupController.get_for(cur_setup).get_all_abs_inner_device_classes()
133
+ scenario_devices = ScenarioController.get_for(cur_scenario).get_all_abs_inner_device_classes()
134
+ if len(scenario_devices) <= len(setup_devices):
135
+ # only if there are more or as many devices in the setup as in the scenario
136
+
137
+ # go through every possible constellation
138
+ for cur_setup_devices in itertools.permutations(setup_devices, len(scenario_devices)):
139
+ # get device mapping for this constellation
140
+ device_mapping = {scenario_devices[idx]: cur_setup_devices[idx] for idx in range(
141
+ 0, len(scenario_devices))}
142
+ mapping.append((cur_setup, cur_scenario, device_mapping))
143
+ return mapping
144
+
145
+ def resolve(self, plugin_manager: PluginManager) -> None: # pylint: disable=unused-argument
146
+ """
147
+ This method carries out the entire resolve process and saves the end result in the object property
148
+ `self._mapping`.
149
+ """
150
+ # reset mapping list
151
+ self._mapping = []
152
+ initial_mapping = self.get_initial_mapping()
153
+ self._mapping = initial_mapping
154
+ self._resolving_was_executed = True
155
+
156
+ def get_static_parametrized_testcase_executor_for(
157
+ self,
158
+ variation_executor: VariationExecutor,
159
+ testcase: Callable
160
+ ) -> List[UnresolvedParametrizedTestcaseExecutor | ParametrizedTestcaseExecutor]:
161
+ """
162
+ returns a list of all testcase executors, with already resolved static parametrization -
163
+ :class:`UnresolvedParametrizedTest` will be returned for unresolved dynamic parametrization
164
+
165
+ :param variation_executor: the current variation executor
166
+ :param testcase: the current testcase
167
+ """
168
+
169
+ scenario_controller = variation_executor.parent_executor.base_scenario_controller
170
+ if scenario_controller.get_parametrization_for(testcase) is None:
171
+ raise ValueError(f'can not determine parametrization for test `{testcase.__qualname__}` because no '
172
+ f'parametrization exist')
173
+ static_parametrization = scenario_controller.get_parametrization_for(testcase, static=True, dynamic=False)
174
+ executor_typ = UnresolvedParametrizedTestcaseExecutor \
175
+ if scenario_controller.get_parametrization_for(testcase, static=False, dynamic=True) \
176
+ else ParametrizedTestcaseExecutor
177
+
178
+ if not static_parametrization:
179
+ return [executor_typ(testcase, parent=variation_executor)]
180
+
181
+ # generate product
182
+ result = []
183
+ for cur_product in itertools.product(*static_parametrization.values()):
184
+ cur_product_parametrization = dict(zip(static_parametrization.keys(), cur_product))
185
+ result.append(executor_typ(testcase, variation_executor, cur_product_parametrization))
186
+ return result
187
+
188
+ # pylint: disable-next=unused-argument
189
+ def get_executor_tree(self, plugin_manager: PluginManager, add_discarded=False) -> ExecutorTree:
190
+ """
191
+ This method builds the ExecutorTree from the resolved data and returns it
192
+
193
+ :param plugin_manager: the related plugin manager object
194
+ :param add_discarded: True in case discarded elements should be added to the tree, otherwise False
195
+
196
+ :return: the executor tree is built on the basis of the mapping data
197
+ """
198
+
199
+ executor_tree = ExecutorTree(self._fixture_manager)
200
+
201
+ # create all setup executor
202
+
203
+ for cur_setup, cur_scenario, cur_device_mapping in self._mapping:
204
+ setup_executor = executor_tree.get_executor_for_setup(setup=cur_setup)
205
+ if setup_executor is None:
206
+ # setup is not available -> create new SetupExecutor
207
+ setup_executor = SetupExecutor(cur_setup, parent=executor_tree)
208
+ executor_tree.add_setup_executor(setup_executor)
209
+
210
+ scenario_executor = setup_executor.get_executor_for_scenario(scenario=cur_scenario)
211
+ if scenario_executor is None:
212
+ # scenario is not available -> create new ScenarioExecutor
213
+ scenario_executor = ScenarioExecutor(cur_scenario, parent=setup_executor)
214
+ setup_executor.add_scenario_executor(scenario_executor)
215
+
216
+ variation_executor = scenario_executor.get_executor_for_device_mapping(device_mapping=cur_device_mapping)
217
+ if variation_executor is None:
218
+ # variation is not available -> create new VariationExecutor
219
+ variation_executor = VariationExecutor(device_mapping=cur_device_mapping, parent=scenario_executor)
220
+ variation_executor.verify_applicability()
221
+
222
+ if not variation_executor.can_be_applied():
223
+ continue
224
+
225
+ scenario_executor.add_variation_executor(variation_executor)
226
+
227
+ for cur_testcase in scenario_executor.base_scenario_controller.get_all_test_methods():
228
+ # we have a parametrization for this test case
229
+ if scenario_executor.base_scenario_controller.get_parametrization_for(cur_testcase):
230
+ testcase_executors = self.get_static_parametrized_testcase_executor_for(
231
+ variation_executor, cur_testcase
232
+ )
233
+ for cur_testcase_executor in testcase_executors:
234
+ variation_executor.add_testcase_executor(cur_testcase_executor)
235
+ else:
236
+ testcase_executor = TestcaseExecutor(cur_testcase, parent=variation_executor)
237
+ variation_executor.add_testcase_executor(testcase_executor)
238
+
239
+ # now filter all elements that have no child elements
240
+ # -> these are items that have no valid matching, because no variation can be applied for it (there are no
241
+ # required :class:`Feature` matching or there exists no possible routing for the variation)
242
+ executor_tree.cleanup_empty_executor_branches(consider_discarded=add_discarded)
243
+
244
+ self._set_data_for_covered_by_in_tree(executor_tree=executor_tree)
245
+
246
+ return executor_tree