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,483 @@
1
+ from __future__ import annotations
2
+
3
+ import itertools
4
+ from typing import List, Tuple, Generator, Dict, Union, Type, Callable, Iterable, TYPE_CHECKING
5
+
6
+ import inspect
7
+ from graphlib import TopologicalSorter
8
+ from _balder.executor.testcase_executor import TestcaseExecutor
9
+ from _balder.scenario import Scenario
10
+ from _balder.setup import Setup
11
+ from _balder.fixture_definition_scope import FixtureDefinitionScope
12
+ from _balder.fixture_execution_level import FixtureExecutionLevel
13
+ from _balder.fixture_metadata import FixtureMetadata
14
+ from _balder.executor.basic_executor import BasicExecutor
15
+ from _balder.executor.setup_executor import SetupExecutor
16
+ from _balder.executor.scenario_executor import ScenarioExecutor
17
+ from _balder.executor.variation_executor import VariationExecutor
18
+ from _balder.exceptions import LostInExecutorTreeException, FixtureReferenceError, UnclearSetupScopedFixtureReference, \
19
+ UnclearUniqueClassReference
20
+
21
+ if TYPE_CHECKING:
22
+ from _balder.utils.functions import MethodLiteralType
23
+ from _balder.executor.executor_tree import ExecutorTree
24
+
25
+
26
+ class FixtureManager:
27
+ """
28
+ This class is the global fixture manager. It provides various methods to manage fixtures in the balder system.
29
+ """
30
+
31
+ def __init__(
32
+ self,
33
+ fixtures: Dict[FixtureExecutionLevel,
34
+ Dict[Union[None, Type[Scenario], Type[Setup]], List[Tuple[MethodLiteralType, Callable]]]]):
35
+
36
+ # The first key is the fixture level, the second key is the namespace in which the fixture is defined. As value
37
+ # a list with tuples is returned. The first element is the type of the method/function and the second is the
38
+ # callable itself.
39
+ self.fixtures: Dict[FixtureExecutionLevel,
40
+ Dict[Union[None, Type[Scenario], Type[Setup]], List[Tuple[MethodLiteralType, Callable]]]] \
41
+ = fixtures
42
+
43
+ # contains all active fixtures with their namespace, their func_type, their callable, the generator object and
44
+ # the result according to the fixture's construction code (will be cleaned after it leaves a level)
45
+ self.current_tree_fixtures: Dict[FixtureExecutionLevel, List[FixtureMetadata]] = {}
46
+
47
+ # ---------------------------------- STATIC METHODS ----------------------------------------------------------------
48
+
49
+ # ---------------------------------- CLASS METHODS ----------------------------------------------------------------
50
+
51
+ # ---------------------------------- PROPERTIES --------------------------------------------------------------------
52
+
53
+ @property
54
+ def all_already_run_fixtures(self) -> List[Callable]:
55
+ """
56
+ returns a list of all fixtures that have already been run
57
+ """
58
+ complete_list_in_order = []
59
+ for cur_level in FixtureExecutionLevel:
60
+ if cur_level in self.current_tree_fixtures.keys():
61
+ complete_list_in_order += [
62
+ cur_fixture_metadata.callable for cur_fixture_metadata in self.current_tree_fixtures[cur_level]]
63
+ return complete_list_in_order
64
+
65
+ # ---------------------------------- PROTECTED METHODS -------------------------------------------------------------
66
+
67
+ def _validate_for_unclear_setup_scoped_fixture_reference(
68
+ self, fixture_callable_namespace: Union[None, Type[Scenario], Type[Setup]],
69
+ fixture_callable: Callable, arguments: List[str], cur_execution_level: FixtureExecutionLevel):
70
+ """
71
+ This helper method validates a given fixture reference for the unclear setup scoped fixture reference.
72
+
73
+ The problem:
74
+ A scenario scoped fixture (defined in a scenario) with the execution level SESSION references a fixture (over
75
+ an argument) with a name that is also used for one or more setup scoped fixtures with execution level SESSION.
76
+
77
+ For this constellation it is unclear which referenced setup scoped fixture is the correct one.
78
+
79
+ .. note::
80
+ Note that the error will also be thrown if only one setup fixture with the referenced name exists. (This
81
+ would work, but it becomes difficult to understand it. Balder prohibits it.
82
+
83
+ .. note::
84
+ The error will be thrown if a scenario scoped fixture with the execution level SESSION references a fixture
85
+ name that exists in one or more setup scoped fixtures with the execution level SESSION.
86
+
87
+ :param fixture_callable_namespace: the namespace of the current fixture or None if it is a `balderglob.py` file
88
+
89
+ :param fixture_callable: the callable with the arguments
90
+
91
+ :param arguments: the execution level of the fixture
92
+
93
+ :param cur_execution_level: the current execution level that is currently active
94
+ """
95
+ # this method is only interested in fixtures with execution level SESSION!
96
+
97
+ if (fixture_callable_namespace is not None and isinstance(fixture_callable_namespace, Scenario)
98
+ and cur_execution_level == FixtureExecutionLevel.SESSION):
99
+ all_setup_scoped_fixtures = []
100
+
101
+ for cur_namespace_type, fixtures in self.fixtures.get(FixtureExecutionLevel.SESSION, {}).items():
102
+ if cur_namespace_type is not None and issubclass(cur_namespace_type, Setup):
103
+ all_setup_scoped_fixtures += [cur_fixt.__name__ for _, cur_fixt in fixtures]
104
+ for cur_arg in arguments:
105
+ if cur_arg in all_setup_scoped_fixtures:
106
+ raise UnclearSetupScopedFixtureReference(
107
+ f"your fixture `{fixture_callable.__name__}` has the definition scope SCENARIO and tries to "
108
+ f"access a fixture name `{cur_arg}` - the environment has one or more SESSION "
109
+ f"fixtures with the definition scope that have the same name - please rename them!")
110
+
111
+ def _sort_fixture_list_of_same_definition_scope(
112
+ self, fixture_namespace_dict: Dict[Union[None, Type[Scenario], Type[Setup]], List[object]],
113
+ outer_scope_fixtures: List[object]) \
114
+ -> List[Tuple[Union[None, Type[Scenario], Type[Setup]], str, Callable]]:
115
+ """
116
+ This is a helper method that allows to sort the fixtures given in `fixture_namespace_dict`, depending on their
117
+ arguments.
118
+ It also checks that every fixture that is mentioned in the arguments is available and an item of
119
+ ``fixture_namespace_dict`` or ``outer_scope_fixtures``. If the method detects a loop between some fixtures, the
120
+ method throws an exception.
121
+
122
+ :param fixture_namespace_dict: the unordered dictionary, where the definition scope namespace object is the key
123
+ and a list with the fixture tuple is the value
124
+
125
+ :param outer_scope_fixtures: the list of fixtures that have already been ordered as elements before the given
126
+ one in ``fixture_list`` (for example the item from another **definition-scope** or
127
+ from another **execution-level**)
128
+
129
+ :return: the list of tuples where the first element is the definition scope object, the second element is the
130
+ func_type of the fixture and the third is the fixture callable itself
131
+ """
132
+ if len(fixture_namespace_dict) == 0:
133
+ return []
134
+
135
+ # returns the func_type for the given fixture
136
+ fixture_func_types = {}
137
+ for _, cur_fixture_list in fixture_namespace_dict.items():
138
+ for cur_func_type, cur_fixture in cur_fixture_list:
139
+ fixture_func_types[cur_fixture] = cur_func_type
140
+
141
+ # - if we want to access a fixture (over name) from another fixture we have to be careful!
142
+ # -> if we try to access a fixture with a fixture which is not part of the same namespace scope -> ERROR
143
+ # -> if we try to access a fixture with a fixture which is part of the same namespace scope -> this fixture
144
+ # has to run before the other (and return the value to the referencing one)
145
+
146
+ # now resolve dependencies, by returning them as single items and add them to the TopologicalSorter
147
+ sorter = TopologicalSorter()
148
+
149
+ for cur_namespace_type, cur_fixture_list in fixture_namespace_dict.items():
150
+ for _, cur_fixture in cur_fixture_list:
151
+ # add the item to the sorter, because it should always be executed (also if it is not referenced from
152
+ # another one)
153
+ sorter.add((cur_namespace_type, cur_fixture))
154
+ # determine all function/method arguments that has to be resolved for this fixture
155
+ cur_fixture_args = inspect.getfullargspec(cur_fixture).args
156
+ if fixture_func_types[cur_fixture] in ["instancemethod", "classmethod"]:
157
+ # this is a class method (remove `cls`) or an instance method (remove `self`)
158
+ cur_fixture_args = cur_fixture_args[1:]
159
+ # now try to find a possible fixture for every argument
160
+ for cur_arg in cur_fixture_args:
161
+ # search the fixture in current namespace
162
+ cur_fixture_list_as_names = [search_fixture.__name__ for _, search_fixture in cur_fixture_list]
163
+ found = False
164
+ # found referenced fixture in current namespace and definition scope
165
+ if cur_arg in cur_fixture_list_as_names:
166
+ _, cur_arg_fixture = cur_fixture_list[cur_fixture_list_as_names.index(cur_arg)]
167
+ # only add fixture if the referenced one is not the same as the current fixture
168
+ if cur_fixture != cur_arg_fixture:
169
+ sorter.add((cur_namespace_type, cur_fixture), (cur_namespace_type, cur_arg_fixture))
170
+ found = True
171
+ # did not find referenced fixture in current namespace and definition scope -> search for it in
172
+ # outer scopes
173
+ if not found:
174
+ # search it in outer fixture list to secure that they exist
175
+ if cur_arg not in \
176
+ [cur_outer_fixture.__name__ for cur_outer_fixture in outer_scope_fixtures]:
177
+ raise FixtureReferenceError(
178
+ f"can not find a previous fixture with the name `{cur_arg}`")
179
+
180
+ return [(cur_definition_scope, fixture_func_types[cur_func], cur_func)
181
+ for cur_definition_scope, cur_func in sorter.static_order()]
182
+
183
+ def _determine_setup_and_scenario_type(
184
+ self,
185
+ from_branch: Union[ExecutorTree, SetupExecutor, ScenarioExecutor, VariationExecutor, TestcaseExecutor],
186
+ callable_func_namespace: Union[None, Type[Scenario], Type[Setup]]) \
187
+ -> Tuple[Union[None, Type[Setup]], Union[None, Type[Scenario]]]:
188
+ """
189
+ determines the setup and scenario type for a specific fixture based on the branch the system is in
190
+
191
+ :param from_branch: the branch for which the types should be determined
192
+ :param callable_func_namespace: the namespace of the current fixture or `None` if it is defined in balderglob
193
+ file
194
+ """
195
+ # determine namespaces (in the correct order)
196
+ setup_type = None
197
+ scenario_type = None
198
+ if isinstance(from_branch, SetupExecutor):
199
+ setup_type = from_branch.base_setup_class.__class__
200
+ # normally the scenario is None - only if the current namespace is a scenario we can use it
201
+ if callable_func_namespace is not None and issubclass(callable_func_namespace, Scenario):
202
+ scenario_type = callable_func_namespace
203
+ else:
204
+ scenario_type = None
205
+ elif isinstance(from_branch, ScenarioExecutor):
206
+ setup_type = from_branch.parent_executor.base_setup_class.__class__
207
+ scenario_type = from_branch.base_scenario_class.__class__
208
+ elif isinstance(from_branch, VariationExecutor):
209
+ setup_type = from_branch.cur_setup_class.__class__
210
+ scenario_type = from_branch.cur_scenario_class.__class__
211
+ elif isinstance(from_branch, TestcaseExecutor):
212
+ setup_type = from_branch.parent_executor.cur_setup_class.__class__
213
+ scenario_type = from_branch.parent_executor.cur_scenario_class.__class__
214
+ return setup_type, scenario_type
215
+
216
+ # ---------------------------------- METHODS -----------------------------------------------------------------------
217
+
218
+ def is_allowed_to_enter(
219
+ self, branch: Union[BasicExecutor, ExecutorTree, SetupExecutor, ScenarioExecutor, VariationExecutor,
220
+ TestcaseExecutor]) -> bool:
221
+ """
222
+ This method return true if the given branch can be entered, otherwise false
223
+ """
224
+ return branch.fixture_execution_level not in self.current_tree_fixtures.keys()
225
+
226
+ def is_allowed_to_leave(
227
+ self, branch: Union[BasicExecutor, ExecutorTree, SetupExecutor, ScenarioExecutor, VariationExecutor,
228
+ TestcaseExecutor]) \
229
+ -> bool:
230
+ """
231
+ This method returns true if the given branch can be left now (there exist entries from earlier run enter()
232
+ for this branch), otherwise false
233
+ """
234
+ return branch.fixture_execution_level in self.current_tree_fixtures.keys()
235
+
236
+ def enter(self, branch: Union[BasicExecutor, ExecutorTree, SetupExecutor, ScenarioExecutor, VariationExecutor,
237
+ TestcaseExecutor]):
238
+ """
239
+ With this method you enter a branch for the fixture manager in order to execute the fixtures contained in it
240
+
241
+ :param branch: specifies the element of the ExecutorTree that should be entered (note that the current position
242
+ is very important here)
243
+
244
+ :raise BalderFixtureException: is thrown if an error occurs while executing a user fixture
245
+ """
246
+
247
+ if not self.is_allowed_to_enter(branch):
248
+ raise LostInExecutorTreeException(
249
+ "the current branch that should be entered is not allowed, because other branches weren't left yet")
250
+
251
+ def empty():
252
+ yield None
253
+ # now iterate over all fixtures that should be executed in this enter() call
254
+ # -> collect them with all different DEFINITION-SCOPES
255
+ for cur_definition_scope in FixtureDefinitionScope:
256
+ cur_fixture_list = self.get_all_fixtures_for_current_level(branch=branch).get(cur_definition_scope)
257
+ for cur_scope_namespace_type, cur_fixture_func_type, cur_fixture in cur_fixture_list:
258
+ try:
259
+ if cur_fixture_func_type in ["function", "staticmethod"]:
260
+ # fixture is a function or a staticmethod - no first special attribute
261
+ kwargs = self.get_all_attribute_values(branch, cur_scope_namespace_type, cur_fixture,
262
+ cur_fixture_func_type)
263
+ cur_generator = cur_fixture(**kwargs)
264
+ elif cur_fixture_func_type == "classmethod":
265
+ kwargs = self.get_all_attribute_values(branch, cur_scope_namespace_type, cur_fixture,
266
+ cur_fixture_func_type)
267
+ cur_generator = cur_fixture(cur_scope_namespace_type, **kwargs)
268
+ elif cur_fixture_func_type == "instancemethod":
269
+ self_reference = branch.get_all_base_instances_of_this_branch(
270
+ with_type=cur_scope_namespace_type, only_runnable_elements=True)
271
+ if len(self_reference) != 1:
272
+ raise UnclearUniqueClassReference(
273
+ f"can not find exactly one reference of the class "
274
+ f"`{cur_scope_namespace_type.__name__}` in current tree branch")
275
+ kwargs = self.get_all_attribute_values(branch, cur_scope_namespace_type, cur_fixture,
276
+ cur_fixture_func_type)
277
+ cur_generator = cur_fixture(self_reference[0], **kwargs)
278
+ else:
279
+ raise ValueError(f"found illegal value for func_type `{cur_fixture_func_type}` for fixture "
280
+ f"`{cur_fixture.__name__}`")
281
+ if isinstance(cur_generator, Generator):
282
+ cur_retvalue = next(cur_generator)
283
+ else:
284
+ cur_retvalue = cur_generator
285
+ cur_generator = empty()
286
+ next(cur_generator)
287
+ # add the executed fixtures to global reference
288
+ if branch.fixture_execution_level not in self.current_tree_fixtures.keys():
289
+ self.current_tree_fixtures[branch.fixture_execution_level] = []
290
+ self.current_tree_fixtures[branch.fixture_execution_level].append(
291
+ FixtureMetadata(namespace=cur_scope_namespace_type, function_type=cur_fixture_func_type,
292
+ callable=cur_fixture, generator=cur_generator, retval=cur_retvalue))
293
+ except StopIteration:
294
+ pass
295
+ # every other exception that is thrown, will be recognized and rethrown
296
+
297
+ def leave(self, branch: Union[BasicExecutor, ExecutorTree, SetupExecutor, ScenarioExecutor, VariationExecutor,
298
+ TestcaseExecutor]):
299
+ """
300
+ With this method you leave a previously entered branch and execute the teardown code of the fixtures. Note that
301
+ you can only leave the branch that you entered before!
302
+
303
+ :param branch: specifies the element of the ExecutorTree that should be left (note that the current position
304
+ is very important here)
305
+ """
306
+ if branch.fixture_execution_level not in self.current_tree_fixtures.keys():
307
+ raise LostInExecutorTreeException("can not leave the current branch, because it was not entered before")
308
+
309
+ current_tree_fixtures_reversed = self.current_tree_fixtures[branch.fixture_execution_level]
310
+ current_tree_fixtures_reversed.reverse()
311
+ exception = None
312
+ for cur_fixture_metadata in current_tree_fixtures_reversed:
313
+ try:
314
+ next(cur_fixture_metadata.generator)
315
+ except StopIteration:
316
+ pass
317
+ except Exception as exc: # pylint: disable=broad-exception-caught
318
+ if not exception:
319
+ # only save the first exception
320
+ exception = exc
321
+
322
+ # reset the left location
323
+ del self.current_tree_fixtures[branch.fixture_execution_level]
324
+
325
+ if exception:
326
+ raise exception
327
+
328
+ def get_all_attribute_values(
329
+ self, branch: Union[ExecutorTree, SetupExecutor, ScenarioExecutor, VariationExecutor, TestcaseExecutor],
330
+ callable_func_namespace: Union[None, Type[Scenario], Type[Setup]], callable_func: Callable,
331
+ func_type: str, ignore_attributes: Iterable[str] = None) -> Dict[str, object]:
332
+ """
333
+ This method tries to fill the unresolved function/method arguments of the given fixture callable. For this it
334
+ searches the return values of all already executed fixtures and supplies the argument values of the
335
+ given ``fixture`` callable in a dictionary.
336
+
337
+ It automatically manages `self` / `cls` references for func_type `instancemethod` or `classmethod`. It
338
+ only returns the fixture references and ignores `self` / `cls`
339
+
340
+ First the method tries to find the arguments in all fixtures that are in the same namespace. If it does not find
341
+ any references, it will look in the next higher scope. It only uses fixture that has run before!
342
+
343
+ :param branch: the current active branch
344
+ :param callable_func_namespace: the namespace of the current fixture or `None` if it is defined in balderglob
345
+ file
346
+ :param callable_func: the callable with the arguments
347
+ :param func_type: returns the func_type of the fixture - depending on this value the first argument will be
348
+ ignored, because it has to be `cls` for `classmethod` and `self` for `instancemethod`
349
+ :param ignore_attributes: holds a list of attributes in the test method that should be ignored
350
+ :return: the method returns a dictionary with the attribute name as key and the return value as value
351
+
352
+ """
353
+ arguments = inspect.getfullargspec(callable_func).args
354
+ result_dict = {}
355
+
356
+ if func_type in ["classmethod", "instancemethod"]:
357
+ arguments = arguments[1:]
358
+ if ignore_attributes is None:
359
+ ignore_attributes = []
360
+
361
+ self._validate_for_unclear_setup_scoped_fixture_reference(
362
+ callable_func_namespace, callable_func, arguments, cur_execution_level=branch.fixture_execution_level)
363
+ all_possible_namespaces = [None]
364
+ setup_type, scenario_type = self._determine_setup_and_scenario_type(
365
+ from_branch=branch, callable_func_namespace=callable_func_namespace)
366
+
367
+ # add to possible namespaces only if the namespace of the current fixture allows this
368
+ if callable_func_namespace is not None:
369
+ if (issubclass(callable_func_namespace, Setup) or issubclass(callable_func_namespace, Scenario)) \
370
+ and setup_type is not None:
371
+ all_possible_namespaces.append(setup_type)
372
+ if issubclass(callable_func_namespace, Scenario) and scenario_type is not None:
373
+ all_possible_namespaces.append(scenario_type)
374
+
375
+ for cur_arg in arguments:
376
+ if cur_arg in ignore_attributes:
377
+ continue
378
+ # go to the most specific fixture, because more specific ones overwrite the more global ones
379
+ for cur_possible_namespace, cur_level in itertools.product(all_possible_namespaces, FixtureExecutionLevel):
380
+ if cur_level not in self.current_tree_fixtures.keys():
381
+ continue
382
+ # filter only these fixtures that have the same namespace
383
+ for cur_fixture_metadata in self.current_tree_fixtures[cur_level]:
384
+ if (cur_fixture_metadata.namespace == cur_possible_namespace
385
+ and cur_fixture_metadata.callable.__name__ == cur_arg):
386
+ result_dict[cur_arg] = cur_fixture_metadata.retval
387
+ if cur_arg not in result_dict.keys():
388
+ raise FixtureReferenceError(
389
+ f"the argument `{cur_arg}` in fixture `{callable_func.__qualname__}` could not be resolved")
390
+ return result_dict
391
+
392
+ def get_fixture_for_class(self, execution_level: FixtureExecutionLevel,
393
+ setup_or_scenario_class: Union[None, Type[Setup], Type[Scenario]],
394
+ parent_classes: bool = True) -> List[Tuple[MethodLiteralType, Callable]]:
395
+ """
396
+ This method returns all classes of a specific Setup/Scenario class for a specific execution-level.
397
+
398
+ :param execution_level: the execution level the fixture should be returned for
399
+ :param setup_or_scenario_class: the scenario or setup class, the fixtures should be returned for
400
+ :param parent_classes: true if the method should look for fixtures in parent classes too
401
+ :return: list with all fixtures that are matching search criteria
402
+ """
403
+ # get all fixtures of the current relevant level (only `execution_level` is relevant - all other levels are
404
+ # not relevant for this call)
405
+ fixtures_of_exec_level = self.fixtures.get(execution_level, {})
406
+ if setup_or_scenario_class is not None and parent_classes:
407
+ all_fixtures = []
408
+ for cur_parent_class in inspect.getmro(setup_or_scenario_class):
409
+ if issubclass(cur_parent_class, (Scenario, Setup)):
410
+ all_fixtures += self.get_fixture_for_class(execution_level, cur_parent_class, False)
411
+ # go through list and remove all overwritten fixtures
412
+ _added_fixtures = []
413
+ remaining_fixtures = []
414
+ for cur_fixture_tuple in all_fixtures:
415
+ if cur_fixture_tuple[1].__name__ not in _added_fixtures:
416
+ _added_fixtures.append(cur_fixture_tuple[1].__name__)
417
+ remaining_fixtures.append(cur_fixture_tuple)
418
+ return remaining_fixtures
419
+ return fixtures_of_exec_level.get(setup_or_scenario_class, [])
420
+
421
+ def get_all_fixtures_for_current_level(
422
+ self, branch: Union[ExecutorTree, SetupExecutor, ScenarioExecutor, VariationExecutor, TestcaseExecutor]) \
423
+ -> Dict[FixtureDefinitionScope, List[Tuple[Union[None, Type[Scenario], Type[Setup]], str, object]]]:
424
+ """
425
+ This method delivers all fixtures which should be executed for the given branch of the executor tree.
426
+
427
+ The method collects the following fixtures from all three possible DEFINITION-SCOPES:
428
+ 1. collect them from balderglob.py (see ``FixtureManager._glob_fixtures``)
429
+ 2. collect them from current used Setup OR if no specific one is set (because we are in a higher branch):
430
+ collect them from all that are part of the current branch
431
+ 3. collect them from current used Scenario OR if no specific one is set (because we are in a higher branch):
432
+ collect them from all that are part of the current branch
433
+
434
+ :param branch: the branch which is currently active
435
+
436
+ :return: a dictionary where the definition object (DEFINITION SCOPE) is the key and a list is the
437
+ value - this value list has tuple elements as children, where the namespace class (:class:`Scenario`
438
+ or :class:`Setup`) as first argument, the fixture func_type as second and the fixture callable as third
439
+ argument (this list is ordered after the call hierarchy)
440
+ """
441
+ all_fixtures = {}
442
+ # get all relevant fixtures of `balderglob.py` (None is key for balderglob fixtures)
443
+ glob_fixtures = self.get_fixture_for_class(branch.fixture_execution_level, None)
444
+ all_fixtures[FixtureDefinitionScope.GLOB] = {}
445
+ all_fixtures[FixtureDefinitionScope.GLOB][None] = glob_fixtures
446
+ # get all relevant fixtures with definition scope "setup"
447
+ all_fixtures[FixtureDefinitionScope.SETUP] = {}
448
+ for cur_setup in branch.get_all_base_instances_of_this_branch(Setup, only_runnable_elements=True):
449
+ # check if there exists fixtures for the current setup
450
+ cur_setup_fixtures = self.get_fixture_for_class(branch.fixture_execution_level, cur_setup.__class__)
451
+ if cur_setup_fixtures:
452
+ all_fixtures[FixtureDefinitionScope.SETUP][cur_setup.__class__] = cur_setup_fixtures
453
+
454
+ # get all relevant fixtures with definition scope "scenario"
455
+ all_fixtures[FixtureDefinitionScope.SCENARIO] = {}
456
+ for cur_scenario in branch.get_all_base_instances_of_this_branch(Scenario, only_runnable_elements=True):
457
+ cur_scenario_fixtures = self.get_fixture_for_class(branch.fixture_execution_level, cur_scenario.__class__)
458
+ if cur_scenario_fixtures:
459
+ all_fixtures[FixtureDefinitionScope.SCENARIO][cur_scenario.__class__] = cur_scenario_fixtures
460
+
461
+ ordered_fixtures = {}
462
+ # Now the basic order is: [All of ExecutorTree] -> [All of Setup] -> [All of Scenario]
463
+ # but the order within these DEFINITION SCOPES has to be determined now!
464
+ outer_scope_fixtures = self.all_already_run_fixtures
465
+ ordered_fixtures[FixtureDefinitionScope.GLOB] = self._sort_fixture_list_of_same_definition_scope(
466
+ fixture_namespace_dict=all_fixtures[FixtureDefinitionScope.GLOB], outer_scope_fixtures=outer_scope_fixtures)
467
+
468
+ outer_scope_fixtures = \
469
+ self.all_already_run_fixtures + \
470
+ [cur_fixture for _, _, cur_fixture in ordered_fixtures[FixtureDefinitionScope.GLOB]]
471
+ ordered_fixtures[FixtureDefinitionScope.SETUP] = self._sort_fixture_list_of_same_definition_scope(
472
+ fixture_namespace_dict=all_fixtures[FixtureDefinitionScope.SETUP],
473
+ outer_scope_fixtures=outer_scope_fixtures)
474
+
475
+ outer_scope_fixtures = \
476
+ self.all_already_run_fixtures + \
477
+ [cur_fixture for _, _, cur_fixture in ordered_fixtures[FixtureDefinitionScope.GLOB]] + \
478
+ [cur_fixture for _, _, cur_fixture in ordered_fixtures[FixtureDefinitionScope.SETUP]]
479
+ ordered_fixtures[FixtureDefinitionScope.SCENARIO] = self._sort_fixture_list_of_same_definition_scope(
480
+ fixture_namespace_dict=all_fixtures[FixtureDefinitionScope.SCENARIO],
481
+ outer_scope_fixtures=outer_scope_fixtures)
482
+
483
+ return ordered_fixtures
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+ from typing import Union, Type, Callable, Generator, TYPE_CHECKING
3
+ import dataclasses
4
+
5
+ from .utils.typings import MethodLiteralType
6
+
7
+ if TYPE_CHECKING:
8
+ from _balder.scenario import Scenario
9
+ from _balder.setup import Setup
10
+
11
+
12
+ @dataclasses.dataclass
13
+ class FixtureMetadata:
14
+ """
15
+ describes meta information for a fixture
16
+ """
17
+ #: the namespace where it is defined (None if it is in a balderglob file)
18
+ namespace: Union[None, Type[Scenario], Type[Setup]]
19
+ #: the type of the fixture function
20
+ function_type: MethodLiteralType
21
+ #: the fixture callable itself
22
+ callable: Callable
23
+ #: the generator object (if the fixture is not a generator, it holds an empty generator)
24
+ generator: Generator
25
+ #: result according to the fixture's construction code (will be cleaned after it leaves a level)
26
+ retval: object
@@ -0,0 +1,103 @@
1
+ from __future__ import annotations
2
+
3
+ from _balder.exceptions import NodeNotExistsError
4
+ from _balder.controllers import DeviceController
5
+
6
+
7
+ class NodeGateway:
8
+ """
9
+ This is a class that describes a gateway between two nodes of a device. A gateway enables the interaction of
10
+ different connection nodes of a device.
11
+ """
12
+
13
+ def __init__(self, device, from_node: str, to_node: str, bidirectional: bool):
14
+ """
15
+
16
+ :param device: the device this gateway belongs to
17
+
18
+ :param from_node: the node name where the gateway starts
19
+
20
+ :param to_node: the node name where the gateway ends
21
+
22
+ :param bidirectional: if this value is true, the gateway can "translate" in both directions
23
+ """
24
+ #: the device this gateway belongs to
25
+ self.device = device
26
+
27
+ #: the node name of the device the gateway starts
28
+ self.from_node_name = from_node
29
+
30
+ #: the node name of the device the gateway ends
31
+ self.to_node_name = to_node
32
+
33
+ #: if this value is true, the gateway can "translate" in both directions
34
+ self._bidirectional = bidirectional
35
+
36
+ # ---------------------------------- STATIC METHODS ----------------------------------------------------------------
37
+
38
+ # ---------------------------------- CLASS METHODS ----------------------------------------------------------------
39
+
40
+ # ---------------------------------- PROPERTIES --------------------------------------------------------------------
41
+
42
+ # ---------------------------------- PROTECTED METHODS -------------------------------------------------------------
43
+
44
+ # ---------------------------------- METHODS -----------------------------------------------------------------------
45
+
46
+ def validate_given_node_names(self):
47
+ """
48
+ This method validates whether the given node names actually exist in the transferred device. Otherwise, this
49
+ method throws an exception.
50
+ """
51
+ device_controller = DeviceController.get_for(self.device)
52
+ if self.from_node_name not in device_controller.get_all_connections().keys():
53
+ raise NodeNotExistsError(f"the from_node `{self.from_node_name}` mentioned by the `@gateway` decorator "
54
+ f"does not exist as a node in device `{self.__class__.__name__}`")
55
+ if self.to_node_name not in device_controller.get_all_connections().keys():
56
+ raise NodeNotExistsError(f"the to_node `{self.to_node_name}` mentioned by the `@gateway` decorator does "
57
+ f"not exist as a node in device `{self.__class__.__name__}`")
58
+
59
+ def get_conn_partner_of(self, node) -> str:
60
+ """
61
+ This method returns the partner node of this gateway - it always returns the other not given side
62
+ """
63
+ if node not in (self.from_node_name, self.to_node_name):
64
+ raise ValueError(f"the given node `{node}` is no component of this connection")
65
+
66
+ return self.to_node_name if node == self.from_node_name else self.from_node_name
67
+
68
+ def has_connection_from_to(self, start_node, end_node=None) -> bool:
69
+ """
70
+ This method checks if there is a connection from ``start_node`` to ``end_node``. This will always return true
71
+ if the gateway supports bidirectional communication or the ``start_node`` and ``end_node`` given in this method
72
+ are also the ``start_node`` and ``end_node`` mentioned in this connection object.
73
+
74
+ .. note::
75
+ Of course the given node names have to be the nodes mentioned internally!
76
+
77
+ :param start_node: the node the connection should start
78
+
79
+ :param end_node: the node the connection should end (optional, will be autofilled automatically)
80
+
81
+ :return: returns true if the given direction is possible
82
+ """
83
+ if start_node not in (self.from_node_name, self.to_node_name):
84
+ raise ValueError(
85
+ f"the given start_node `{start_node.__qualname__}` is no component of this connection")
86
+ if end_node is not None:
87
+ if end_node not in (self.from_node_name, self.to_node_name):
88
+ raise ValueError(
89
+ f"the given end_node `{end_node.__qualname__}` is no component of this connection")
90
+ else:
91
+ end_node = self.from_node_name if start_node == self.to_node_name else self.to_node_name
92
+
93
+ if self.is_bidirectional():
94
+ return True
95
+
96
+ if start_node == self.from_node_name and end_node == self.to_node_name:
97
+ return True
98
+
99
+ return False
100
+
101
+ def is_bidirectional(self) -> bool:
102
+ """returns true if the gateway works in both directional"""
103
+ return self._bidirectional
File without changes
File without changes