baldertest 0.1.0b8__py3-none-any.whl → 0.1.0b10__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 (38) hide show
  1. _balder/_version.py +1 -1
  2. _balder/balder_session.py +5 -3
  3. _balder/collector.py +117 -8
  4. _balder/console/balder.py +1 -1
  5. _balder/controllers/device_controller.py +1 -1
  6. _balder/controllers/feature_controller.py +38 -17
  7. _balder/controllers/normal_scenario_setup_controller.py +5 -12
  8. _balder/controllers/scenario_controller.py +91 -1
  9. _balder/decorator_fixture.py +4 -7
  10. _balder/decorator_for_vdevice.py +4 -6
  11. _balder/decorator_parametrize.py +31 -0
  12. _balder/decorator_parametrize_by_feature.py +36 -0
  13. _balder/exceptions.py +6 -0
  14. _balder/executor/basic_executable_executor.py +126 -0
  15. _balder/executor/basic_executor.py +6 -95
  16. _balder/executor/executor_tree.py +22 -8
  17. _balder/executor/parametrized_testcase_executor.py +52 -0
  18. _balder/executor/scenario_executor.py +10 -2
  19. _balder/executor/setup_executor.py +40 -2
  20. _balder/executor/testcase_executor.py +42 -9
  21. _balder/executor/unresolved_parametrized_testcase_executor.py +209 -0
  22. _balder/executor/variation_executor.py +30 -51
  23. _balder/fixture_definition_scope.py +19 -0
  24. _balder/fixture_execution_level.py +22 -0
  25. _balder/fixture_manager.py +169 -182
  26. _balder/fixture_metadata.py +26 -0
  27. _balder/parametrization.py +75 -0
  28. _balder/solver.py +51 -31
  29. _balder/testresult.py +38 -6
  30. balder/__init__.py +6 -0
  31. balder/exceptions.py +4 -3
  32. balder/parametrization.py +8 -0
  33. {baldertest-0.1.0b8.dist-info → baldertest-0.1.0b10.dist-info}/METADATA +1 -1
  34. {baldertest-0.1.0b8.dist-info → baldertest-0.1.0b10.dist-info}/RECORD +38 -28
  35. {baldertest-0.1.0b8.dist-info → baldertest-0.1.0b10.dist-info}/WHEEL +1 -1
  36. {baldertest-0.1.0b8.dist-info → baldertest-0.1.0b10.dist-info}/LICENSE +0 -0
  37. {baldertest-0.1.0b8.dist-info → baldertest-0.1.0b10.dist-info}/entry_points.txt +0 -0
  38. {baldertest-0.1.0b8.dist-info → baldertest-0.1.0b10.dist-info}/top_level.txt +0 -0
@@ -1,11 +1,14 @@
1
1
  from __future__ import annotations
2
- from typing import List, Tuple, Generator, Dict, Union, Type, Callable, TYPE_CHECKING
2
+ from typing import List, Tuple, Generator, Dict, Union, Type, Callable, Iterable, TYPE_CHECKING
3
3
 
4
4
  import inspect
5
5
  from graphlib import TopologicalSorter
6
6
  from _balder.executor.testcase_executor import TestcaseExecutor
7
7
  from _balder.scenario import Scenario
8
8
  from _balder.setup import Setup
9
+ from _balder.fixture_definition_scope import FixtureDefinitionScope
10
+ from _balder.fixture_execution_level import FixtureExecutionLevel
11
+ from _balder.fixture_metadata import FixtureMetadata
9
12
  from _balder.executor.basic_executor import BasicExecutor
10
13
  from _balder.executor.setup_executor import SetupExecutor
11
14
  from _balder.executor.scenario_executor import ScenarioExecutor
@@ -22,22 +25,22 @@ class FixtureManager:
22
25
  """
23
26
  This class is the global fixture manager. It provides various methods to manage fixtures in the balder system.
24
27
  """
25
- #: the ordering for the execution levels
26
- EXECUTION_LEVEL_ORDER = ['session', 'setup', 'scenario', 'variation', 'testcase']
27
28
 
28
- def __init__(self, fixtures: Dict[str, Dict[Union[type, None], List[Tuple[MethodLiteralType, Callable]]]]):
29
+ def __init__(
30
+ self,
31
+ fixtures: Dict[FixtureExecutionLevel,
32
+ Dict[Union[None, Type[Scenario], Type[Setup]], List[Tuple[MethodLiteralType, Callable]]]]):
29
33
 
30
- # The first key is the fixture level, the second key is the namespace in which the fixture is defined (for
31
- # example the scenario class), which describes the definition-scope. As value a list with tuples is returned.
32
- # The first element is the type of the method/function and the second is the callable itself.
33
- self.fixtures: Dict[str, Dict[Union[type, None], List[Tuple[MethodLiteralType, Callable]]]] = fixtures
34
+ # The first key is the fixture level, the second key is the namespace in which the fixture is defined. As value
35
+ # a list with tuples is returned. The first element is the type of the method/function and the second is the
36
+ # callable itself.
37
+ self.fixtures: Dict[FixtureExecutionLevel,
38
+ Dict[Union[None, Type[Scenario], Type[Setup]], List[Tuple[MethodLiteralType, Callable]]]] \
39
+ = fixtures
34
40
 
35
- # contains all active fixtures with their namespace, their func_type, their callable, the generator object
36
- # (otherwise an empty generator, if the fixture is not a generator) and the result according to the fixture's
37
- # construction code (will be cleaned after it leaves a level)
38
- self.current_tree_fixtures: \
39
- Dict[str, List[
40
- Tuple[Union[Type[ExecutorTree], Type[Setup], Type[Scenario]], str, Callable, Generator, object]]] = {}
41
+ # contains all active fixtures with their namespace, their func_type, their callable, the generator object and
42
+ # the result according to the fixture's construction code (will be cleaned after it leaves a level)
43
+ self.current_tree_fixtures: Dict[FixtureExecutionLevel, List[FixtureMetadata]] = {}
41
44
 
42
45
  # ---------------------------------- STATIC METHODS ----------------------------------------------------------------
43
46
 
@@ -45,128 +48,23 @@ class FixtureManager:
45
48
 
46
49
  # ---------------------------------- PROPERTIES --------------------------------------------------------------------
47
50
 
48
- @property
49
- def resolve_type_level(self):
50
- """
51
- returns a dictionary that holds the executor class as key and the fixture definition that belongs to this
52
- executor class as key
53
- """
54
- from _balder.executor.executor_tree import ExecutorTree
55
-
56
- return {
57
- ExecutorTree: 'session',
58
- SetupExecutor: 'setup',
59
- ScenarioExecutor: 'scenario',
60
- VariationExecutor: 'variation',
61
- TestcaseExecutor: 'testcase'
62
- }
63
-
64
- @property
65
- def definition_scope_order(self):
66
- """
67
- returns a list with the definition scope objects in the priority order (ExecutorTree stands for global fixtures)
68
- """
69
- from _balder.executor.executor_tree import ExecutorTree
70
- return [ExecutorTree, Setup, Scenario]
71
-
72
51
  @property
73
52
  def all_already_run_fixtures(self) -> List[Callable]:
74
53
  """
75
54
  returns a list of all fixtures that have already been run
76
55
  """
77
56
  complete_list_in_order = []
78
- for cur_level in self.EXECUTION_LEVEL_ORDER:
57
+ for cur_level in FixtureExecutionLevel:
79
58
  if cur_level in self.current_tree_fixtures.keys():
80
59
  complete_list_in_order += [
81
- cur_fixture for _, _, cur_fixture, _, _ in self.current_tree_fixtures[cur_level]]
60
+ cur_fixture_metadata.callable for cur_fixture_metadata in self.current_tree_fixtures[cur_level]]
82
61
  return complete_list_in_order
83
62
 
84
63
  # ---------------------------------- PROTECTED METHODS -------------------------------------------------------------
85
64
 
86
- def get_all_attribute_values(
87
- self, branch: Union[ExecutorTree, SetupExecutor, ScenarioExecutor, VariationExecutor, TestcaseExecutor],
88
- callable_func_namespace: Union[Type[ExecutorTree], Type[Scenario], Type[Setup]], callable_func: Callable,
89
- func_type: str) -> Dict[str, object]:
90
- """
91
- This method tries to fill the unresolved function/method arguments of the given fixture callable. For this it
92
- searches the return values of all already executed fixtures and supplies the argument values of the
93
- given ``fixture`` callable in a dictionary.
94
-
95
- It automatically manages `self` / `cls` references for func_type `instancemethod` or `classmethod`. It
96
- only returns the fixture references and ignores `self` / `cls`
97
-
98
- First the method tries to find the arguments in all fixtures that are in the same namespace. If it does not find
99
- any references, it will look in the next higher scope. It only uses fixture that has run before!
100
-
101
- :param branch: the current active branch
102
-
103
- :param callable_func_namespace: the namespace of the current fixture
104
-
105
- :param callable_func: the callable with the arguments
106
-
107
- :param func_type: returns the func_type of the fixture - depending on this value the first argument will be
108
- ignored, because it has to be `cls` for `classmethod` and `self` for `instancemethod`
109
-
110
- :return: the method returns a dictionary with the attribute name as key and the return value as value
111
- """
112
- from _balder.executor.executor_tree import ExecutorTree
113
-
114
- arguments = inspect.getfullargspec(callable_func).args
115
- result_dict = {}
116
-
117
- if func_type in ["classmethod", "instancemethod"]:
118
- arguments = arguments[1:]
119
-
120
- self._validate_for_unclear_setup_scoped_fixture_reference(
121
- callable_func_namespace, callable_func, arguments,
122
- cur_execution_level=self.resolve_type_level[branch.__class__])
123
- all_possible_namespaces = [ExecutorTree]
124
- # determine namespaces (in the correct order)
125
- setup_type = None
126
- scenario_type = None
127
- if isinstance(branch, SetupExecutor):
128
- setup_type = branch.base_setup_class.__class__
129
- # normally the scenario is None - only if the current namespace is a scenario we can use it
130
- if issubclass(callable_func_namespace, Scenario):
131
- scenario_type = callable_func_namespace
132
- else:
133
- scenario_type = None
134
- elif isinstance(branch, ScenarioExecutor):
135
- setup_type = branch.parent_executor.base_setup_class.__class__
136
- scenario_type = branch.base_scenario_class.__class__
137
- elif isinstance(branch, VariationExecutor):
138
- setup_type = branch.cur_setup_class.__class__
139
- scenario_type = branch.cur_scenario_class.__class__
140
- elif isinstance(branch, TestcaseExecutor):
141
- setup_type = branch.parent_executor.cur_setup_class.__class__
142
- scenario_type = branch.parent_executor.cur_scenario_class.__class__
143
-
144
- # add to possible namespaces only if the namespace of the current fixture allows this
145
- if (issubclass(callable_func_namespace, Setup) or issubclass(callable_func_namespace, Scenario)) \
146
- and setup_type is not None:
147
- all_possible_namespaces.append(setup_type)
148
- if issubclass(callable_func_namespace, Scenario) and scenario_type is not None:
149
- all_possible_namespaces.append(scenario_type)
150
-
151
- for cur_arg in arguments:
152
- # go to the most specific fixture, because more specific ones overwrite the more global ones
153
- for cur_possible_namespace in all_possible_namespaces:
154
- for cur_level in self.EXECUTION_LEVEL_ORDER:
155
- if cur_level in self.current_tree_fixtures.keys():
156
- # filter only these fixtures that have the same namespace
157
- for cur_fixt_namespace, _, cur_fixt, _, cur_fixt_retval in \
158
- self.current_tree_fixtures[cur_level]:
159
- if cur_fixt_namespace == cur_possible_namespace:
160
- if cur_fixt.__name__ == cur_arg:
161
- result_dict[cur_arg] = cur_fixt_retval
162
- if cur_arg not in result_dict.keys():
163
- raise FixtureReferenceError(
164
- f"the argument `{cur_arg}` in fixture `{callable_func.__qualname__}` could not be resolved")
165
- return result_dict
166
-
167
65
  def _validate_for_unclear_setup_scoped_fixture_reference(
168
- self, fixture_callable_namespace: Union[Type[ExecutorTree], Type[Scenario], Type[Setup]],
169
- fixture_callable: Callable, arguments: List[str], cur_execution_level: str):
66
+ self, fixture_callable_namespace: Union[None, Type[Scenario], Type[Setup]],
67
+ fixture_callable: Callable, arguments: List[str], cur_execution_level: FixtureExecutionLevel):
170
68
  """
171
69
  This helper method validates a given fixture reference for the unclear setup scoped fixture reference.
172
70
 
@@ -184,7 +82,7 @@ class FixtureManager:
184
82
  The error will be thrown if a scenario scoped fixture with the execution level SESSION references a fixture
185
83
  name that exists in one or more setup scoped fixtures with the execution level SESSION.
186
84
 
187
- :param fixture_callable_namespace: the namespace of the current fixture
85
+ :param fixture_callable_namespace: the namespace of the current fixture or None if it is a `balderglob.py` file
188
86
 
189
87
  :param fixture_callable: the callable with the arguments
190
88
 
@@ -194,10 +92,11 @@ class FixtureManager:
194
92
  """
195
93
  # this method is only interested in fixtures with execution level SESSION!
196
94
 
197
- if isinstance(fixture_callable_namespace, Scenario) and cur_execution_level == "session":
95
+ if (fixture_callable_namespace is not None and isinstance(fixture_callable_namespace, Scenario)
96
+ and cur_execution_level == FixtureExecutionLevel.SESSION):
198
97
  all_setup_scoped_fixtures = []
199
98
 
200
- for cur_namespace_type, fixtures in self.fixtures.get('session', {}).items():
99
+ for cur_namespace_type, fixtures in self.fixtures.get(FixtureExecutionLevel.SESSION, {}).items():
201
100
  if cur_namespace_type is not None and issubclass(cur_namespace_type, Setup):
202
101
  all_setup_scoped_fixtures += [cur_fixt.__name__ for _, cur_fixt in fixtures]
203
102
  for cur_arg in arguments:
@@ -208,9 +107,9 @@ class FixtureManager:
208
107
  f"fixtures with the definition scope that have the same name - please rename them!")
209
108
 
210
109
  def _sort_fixture_list_of_same_definition_scope(
211
- self, fixture_namespace_dict: Dict[Tuple[str, Union[Type[ExecutorTree], Type[Scenario], Type[Setup]]],
212
- List[object]], outer_scope_fixtures: List[object]) \
213
- -> List[Tuple[Union[Type[ExecutorTree], Type[Scenario], Type[Setup]], str, Callable]]:
110
+ self, fixture_namespace_dict: Dict[Union[None, Type[Scenario], Type[Setup]], List[object]],
111
+ outer_scope_fixtures: List[object]) \
112
+ -> List[Tuple[Union[None, Type[Scenario], Type[Setup]], str, Callable]]:
214
113
  """
215
114
  This is a helper method that allows to sort the fixtures given in `fixture_namespace_dict`, depending on their
216
115
  arguments.
@@ -233,7 +132,7 @@ class FixtureManager:
233
132
 
234
133
  # returns the func_type for the given fixture
235
134
  fixture_func_types = {}
236
- for cur_namespace_type, cur_fixture_list in fixture_namespace_dict.items():
135
+ for _, cur_fixture_list in fixture_namespace_dict.items():
237
136
  for cur_func_type, cur_fixture in cur_fixture_list:
238
137
  fixture_func_types[cur_fixture] = cur_func_type
239
138
 
@@ -279,6 +178,39 @@ class FixtureManager:
279
178
  return [(cur_definition_scope, fixture_func_types[cur_func], cur_func)
280
179
  for cur_definition_scope, cur_func in sorter.static_order()]
281
180
 
181
+ def _determine_setup_and_scenario_type(
182
+ self,
183
+ from_branch: Union[ExecutorTree, SetupExecutor, ScenarioExecutor, VariationExecutor, TestcaseExecutor],
184
+ callable_func_namespace: Union[None, Type[Scenario], Type[Setup]]) \
185
+ -> Tuple[Union[None, Type[Setup]], Union[None, Type[Scenario]]]:
186
+ """
187
+ determines the setup and scenario type for a specific fixture based on the branch the system is in
188
+
189
+ :param from_branch: the branch for which the types should be determined
190
+ :param callable_func_namespace: the namespace of the current fixture or `None` if it is defined in balderglob
191
+ file
192
+ """
193
+ # determine namespaces (in the correct order)
194
+ setup_type = None
195
+ scenario_type = None
196
+ if isinstance(from_branch, SetupExecutor):
197
+ setup_type = from_branch.base_setup_class.__class__
198
+ # normally the scenario is None - only if the current namespace is a scenario we can use it
199
+ if callable_func_namespace is not None and issubclass(callable_func_namespace, Scenario):
200
+ scenario_type = callable_func_namespace
201
+ else:
202
+ scenario_type = None
203
+ elif isinstance(from_branch, ScenarioExecutor):
204
+ setup_type = from_branch.parent_executor.base_setup_class.__class__
205
+ scenario_type = from_branch.base_scenario_class.__class__
206
+ elif isinstance(from_branch, VariationExecutor):
207
+ setup_type = from_branch.cur_setup_class.__class__
208
+ scenario_type = from_branch.cur_scenario_class.__class__
209
+ elif isinstance(from_branch, TestcaseExecutor):
210
+ setup_type = from_branch.parent_executor.cur_setup_class.__class__
211
+ scenario_type = from_branch.parent_executor.cur_scenario_class.__class__
212
+ return setup_type, scenario_type
213
+
282
214
  # ---------------------------------- METHODS -----------------------------------------------------------------------
283
215
 
284
216
  def is_allowed_to_enter(
@@ -287,8 +219,7 @@ class FixtureManager:
287
219
  """
288
220
  This method return true if the given branch can be entered, otherwise false
289
221
  """
290
- execution_level = self.resolve_type_level[branch.__class__]
291
- return execution_level not in self.current_tree_fixtures.keys()
222
+ return branch.fixture_execution_level not in self.current_tree_fixtures.keys()
292
223
 
293
224
  def is_allowed_to_leave(
294
225
  self, branch: Union[BasicExecutor, ExecutorTree, SetupExecutor, ScenarioExecutor, VariationExecutor,
@@ -298,8 +229,7 @@ class FixtureManager:
298
229
  This method returns true if the given branch can be left now (there exist entries from earlier run enter()
299
230
  for this branch), otherwise false
300
231
  """
301
- execution_level = self.resolve_type_level[branch.__class__]
302
- return execution_level in self.current_tree_fixtures.keys()
232
+ return branch.fixture_execution_level in self.current_tree_fixtures.keys()
303
233
 
304
234
  def enter(self, branch: Union[BasicExecutor, ExecutorTree, SetupExecutor, ScenarioExecutor, VariationExecutor,
305
235
  TestcaseExecutor]):
@@ -311,7 +241,6 @@ class FixtureManager:
311
241
 
312
242
  :raise BalderFixtureException: is thrown if an error occurs while executing a user fixture
313
243
  """
314
- execution_level = self.resolve_type_level[branch.__class__]
315
244
 
316
245
  if not self.is_allowed_to_enter(branch):
317
246
  raise LostInExecutorTreeException(
@@ -321,7 +250,7 @@ class FixtureManager:
321
250
  yield None
322
251
  # now iterate over all fixtures that should be executed in this enter() call
323
252
  # -> collect them with all different DEFINITION-SCOPES
324
- for cur_definition_scope in self.definition_scope_order:
253
+ for cur_definition_scope in FixtureDefinitionScope:
325
254
  cur_fixture_list = self.get_all_fixtures_for_current_level(branch=branch).get(cur_definition_scope)
326
255
  for cur_scope_namespace_type, cur_fixture_func_type, cur_fixture in cur_fixture_list:
327
256
  try:
@@ -354,10 +283,11 @@ class FixtureManager:
354
283
  cur_generator = empty()
355
284
  next(cur_generator)
356
285
  # add the executed fixtures to global reference
357
- if execution_level not in self.current_tree_fixtures.keys():
358
- self.current_tree_fixtures[execution_level] = []
359
- self.current_tree_fixtures[execution_level].append(
360
- (cur_scope_namespace_type, cur_fixture_func_type, cur_fixture, cur_generator, cur_retvalue))
286
+ if branch.fixture_execution_level not in self.current_tree_fixtures.keys():
287
+ self.current_tree_fixtures[branch.fixture_execution_level] = []
288
+ self.current_tree_fixtures[branch.fixture_execution_level].append(
289
+ FixtureMetadata(namespace=cur_scope_namespace_type, function_type=cur_fixture_func_type,
290
+ callable=cur_fixture, generator=cur_generator, retval=cur_retvalue))
361
291
  except StopIteration:
362
292
  pass
363
293
  # every other exception that is thrown, will be recognized and rethrown
@@ -371,36 +301,96 @@ class FixtureManager:
371
301
  :param branch: specifies the element of the ExecutorTree that should be left (note that the current position
372
302
  is very important here)
373
303
  """
374
- execution_level = self.resolve_type_level[branch.__class__]
375
- if execution_level not in self.current_tree_fixtures.keys():
304
+ if branch.fixture_execution_level not in self.current_tree_fixtures.keys():
376
305
  raise LostInExecutorTreeException("can not leave the current branch, because it was not entered before")
377
306
 
378
- current_tree_fixtures_reversed = self.current_tree_fixtures[execution_level]
307
+ current_tree_fixtures_reversed = self.current_tree_fixtures[branch.fixture_execution_level]
379
308
  current_tree_fixtures_reversed.reverse()
380
309
  exception = None
381
- for _, _, _, cur_generator, _ in current_tree_fixtures_reversed:
310
+ for cur_fixture_metadata in current_tree_fixtures_reversed:
382
311
  try:
383
- next(cur_generator)
312
+ next(cur_fixture_metadata.generator)
384
313
  except StopIteration:
385
314
  pass
386
- except Exception as exc:
315
+ except Exception as exc: # pylint: disable=broad-exception-caught
387
316
  if not exception:
388
317
  # only save the first exception
389
318
  exception = exc
390
319
 
391
320
  # reset the left location
392
- del self.current_tree_fixtures[execution_level]
321
+ del self.current_tree_fixtures[branch.fixture_execution_level]
393
322
 
394
323
  if exception:
395
324
  raise exception
396
325
 
397
- def get_fixture_for_class(
398
- self,
399
- execution_level: Union[Type[ExecutorTree], Type[SetupExecutor], Type[ScenarioExecutor],
400
- Type[VariationExecutor], Type[TestcaseExecutor]],
401
- setup_or_scenario_class: Union[None, Type[Setup], Type[Scenario]],
402
- parent_classes: bool = True
403
- ) -> List[Tuple[MethodLiteralType, Callable]]:
326
+ def get_all_attribute_values(
327
+ self, branch: Union[ExecutorTree, SetupExecutor, ScenarioExecutor, VariationExecutor, TestcaseExecutor],
328
+ callable_func_namespace: Union[None, Type[Scenario], Type[Setup]], callable_func: Callable,
329
+ func_type: str, ignore_attributes: Iterable[str] = None) -> Dict[str, object]:
330
+ """
331
+ This method tries to fill the unresolved function/method arguments of the given fixture callable. For this it
332
+ searches the return values of all already executed fixtures and supplies the argument values of the
333
+ given ``fixture`` callable in a dictionary.
334
+
335
+ It automatically manages `self` / `cls` references for func_type `instancemethod` or `classmethod`. It
336
+ only returns the fixture references and ignores `self` / `cls`
337
+
338
+ First the method tries to find the arguments in all fixtures that are in the same namespace. If it does not find
339
+ any references, it will look in the next higher scope. It only uses fixture that has run before!
340
+
341
+ :param branch: the current active branch
342
+ :param callable_func_namespace: the namespace of the current fixture or `None` if it is defined in balderglob
343
+ file
344
+ :param callable_func: the callable with the arguments
345
+ :param func_type: returns the func_type of the fixture - depending on this value the first argument will be
346
+ ignored, because it has to be `cls` for `classmethod` and `self` for `instancemethod`
347
+ :param ignore_attributes: holds a list of attributes in the test method that should be ignored
348
+ :return: the method returns a dictionary with the attribute name as key and the return value as value
349
+
350
+ """
351
+ arguments = inspect.getfullargspec(callable_func).args
352
+ result_dict = {}
353
+
354
+ if func_type in ["classmethod", "instancemethod"]:
355
+ arguments = arguments[1:]
356
+ if ignore_attributes is None:
357
+ ignore_attributes = []
358
+
359
+ self._validate_for_unclear_setup_scoped_fixture_reference(
360
+ callable_func_namespace, callable_func, arguments, cur_execution_level=branch.fixture_execution_level)
361
+ all_possible_namespaces = [None]
362
+ setup_type, scenario_type = self._determine_setup_and_scenario_type(
363
+ from_branch=branch, callable_func_namespace=callable_func_namespace)
364
+
365
+ # add to possible namespaces only if the namespace of the current fixture allows this
366
+ if callable_func_namespace is not None:
367
+ if (issubclass(callable_func_namespace, Setup) or issubclass(callable_func_namespace, Scenario)) \
368
+ and setup_type is not None:
369
+ all_possible_namespaces.append(setup_type)
370
+ if issubclass(callable_func_namespace, Scenario) and scenario_type is not None:
371
+ all_possible_namespaces.append(scenario_type)
372
+
373
+ for cur_arg in arguments:
374
+ if cur_arg in ignore_attributes:
375
+ continue
376
+ # go to the most specific fixture, because more specific ones overwrite the more global ones
377
+ for cur_possible_namespace in all_possible_namespaces:
378
+ for cur_level in FixtureExecutionLevel:
379
+ if cur_level not in self.current_tree_fixtures.keys():
380
+ continue
381
+ # filter only these fixtures that have the same namespace
382
+ for cur_fixture_metadata in self.current_tree_fixtures[cur_level]:
383
+ if (cur_fixture_metadata.namespace == cur_possible_namespace
384
+ and cur_fixture_metadata.callable.__name__ == cur_arg):
385
+ result_dict[cur_arg] = cur_fixture_metadata.retval
386
+ if cur_arg not in result_dict.keys():
387
+ raise FixtureReferenceError(
388
+ f"the argument `{cur_arg}` in fixture `{callable_func.__qualname__}` could not be resolved")
389
+ return result_dict
390
+
391
+ def get_fixture_for_class(self, execution_level: FixtureExecutionLevel,
392
+ setup_or_scenario_class: Union[None, Type[Setup], Type[Scenario]],
393
+ parent_classes: bool = True) -> List[Tuple[MethodLiteralType, Callable]]:
404
394
  """
405
395
  This method returns all classes of a specific Setup/Scenario class for a specific execution-level.
406
396
 
@@ -409,10 +399,9 @@ class FixtureManager:
409
399
  :param parent_classes: true if the method should look for fixtures in parent classes too
410
400
  :return: list with all fixtures that are matching search criteria
411
401
  """
412
- # current relevant EXECUTION LEVEL - all other levels are not relevant for this call
413
- cur_execution_level = self.resolve_type_level[execution_level]
414
- # get all fixtures of the current relevant level
415
- fixtures_of_exec_level = self.fixtures.get(cur_execution_level, {})
402
+ # get all fixtures of the current relevant level (only `execution_level` is relevant - all other levels are
403
+ # not relevant for this call)
404
+ fixtures_of_exec_level = self.fixtures.get(execution_level, {})
416
405
  if setup_or_scenario_class is not None and parent_classes:
417
406
  all_fixtures = []
418
407
  for cur_parent_class in inspect.getmro(setup_or_scenario_class):
@@ -426,13 +415,11 @@ class FixtureManager:
426
415
  _added_fixtures.append(cur_fixture_tuple[1].__name__)
427
416
  remaining_fixtures.append(cur_fixture_tuple)
428
417
  return remaining_fixtures
429
- else:
430
- return fixtures_of_exec_level.get(setup_or_scenario_class, [])
418
+ return fixtures_of_exec_level.get(setup_or_scenario_class, [])
431
419
 
432
420
  def get_all_fixtures_for_current_level(
433
421
  self, branch: Union[ExecutorTree, SetupExecutor, ScenarioExecutor, VariationExecutor, TestcaseExecutor]) \
434
- -> Dict[Union[Type[ExecutorTree], Type[Scenario], Type[Setup]],
435
- List[Tuple[Union[Type[ExecutorTree], Type[Scenario], Type[Setup]], str, object]]]:
422
+ -> Dict[FixtureDefinitionScope, List[Tuple[Union[None, Type[Scenario], Type[Setup]], str, object]]]:
436
423
  """
437
424
  This method delivers all fixtures which should be executed for the given branch of the executor tree.
438
425
 
@@ -450,46 +437,46 @@ class FixtureManager:
450
437
  or :class:`Setup`) as first argument, the fixture func_type as second and the fixture callable as third
451
438
  argument (this list is ordered after the call hierarchy)
452
439
  """
453
- from _balder.executor.executor_tree import ExecutorTree
454
-
455
440
  all_fixtures = {}
456
441
  # get all relevant fixtures of `balderglob.py` (None is key for balderglob fixtures)
457
- glob_fixtures = self.get_fixture_for_class(branch.__class__, None)
458
- all_fixtures[ExecutorTree] = {}
459
- all_fixtures[ExecutorTree][ExecutorTree] = glob_fixtures
442
+ glob_fixtures = self.get_fixture_for_class(branch.fixture_execution_level, None)
443
+ all_fixtures[FixtureDefinitionScope.GLOB] = {}
444
+ all_fixtures[FixtureDefinitionScope.GLOB][None] = glob_fixtures
460
445
  # get all relevant fixtures with definition scope "setup"
461
- all_fixtures[Setup] = {}
446
+ all_fixtures[FixtureDefinitionScope.SETUP] = {}
462
447
  for cur_setup in branch.get_all_base_instances_of_this_branch(Setup, only_runnable_elements=True):
463
448
  # check if there exists fixtures for the current setup
464
- cur_setup_fixtures = self.get_fixture_for_class(branch.__class__, cur_setup.__class__)
449
+ cur_setup_fixtures = self.get_fixture_for_class(branch.fixture_execution_level, cur_setup.__class__)
465
450
  if cur_setup_fixtures:
466
- all_fixtures[Setup][cur_setup.__class__] = cur_setup_fixtures
451
+ all_fixtures[FixtureDefinitionScope.SETUP][cur_setup.__class__] = cur_setup_fixtures
467
452
 
468
453
  # get all relevant fixtures with definition scope "scenario"
469
- all_fixtures[Scenario] = {}
454
+ all_fixtures[FixtureDefinitionScope.SCENARIO] = {}
470
455
  for cur_scenario in branch.get_all_base_instances_of_this_branch(Scenario, only_runnable_elements=True):
471
- cur_scenario_fixtures = self.get_fixture_for_class(branch.__class__, cur_scenario.__class__)
456
+ cur_scenario_fixtures = self.get_fixture_for_class(branch.fixture_execution_level, cur_scenario.__class__)
472
457
  if cur_scenario_fixtures:
473
- all_fixtures[Scenario][cur_scenario.__class__] = cur_scenario_fixtures
458
+ all_fixtures[FixtureDefinitionScope.SCENARIO][cur_scenario.__class__] = cur_scenario_fixtures
474
459
 
475
460
  ordered_fixtures = {}
476
461
  # Now the basic order is: [All of ExecutorTree] -> [All of Setup] -> [All of Scenario]
477
462
  # but the order within these DEFINITION SCOPES has to be determined now!
478
463
  outer_scope_fixtures = self.all_already_run_fixtures
479
- ordered_fixtures[ExecutorTree] = self._sort_fixture_list_of_same_definition_scope(
480
- fixture_namespace_dict=all_fixtures[ExecutorTree], outer_scope_fixtures=outer_scope_fixtures)
464
+ ordered_fixtures[FixtureDefinitionScope.GLOB] = self._sort_fixture_list_of_same_definition_scope(
465
+ fixture_namespace_dict=all_fixtures[FixtureDefinitionScope.GLOB], outer_scope_fixtures=outer_scope_fixtures)
481
466
 
482
467
  outer_scope_fixtures = \
483
468
  self.all_already_run_fixtures + \
484
- [cur_fixture for _, _, cur_fixture in ordered_fixtures[ExecutorTree]]
485
- ordered_fixtures[Setup] = self._sort_fixture_list_of_same_definition_scope(
486
- fixture_namespace_dict=all_fixtures[Setup], outer_scope_fixtures=outer_scope_fixtures)
469
+ [cur_fixture for _, _, cur_fixture in ordered_fixtures[FixtureDefinitionScope.GLOB]]
470
+ ordered_fixtures[FixtureDefinitionScope.SETUP] = self._sort_fixture_list_of_same_definition_scope(
471
+ fixture_namespace_dict=all_fixtures[FixtureDefinitionScope.SETUP],
472
+ outer_scope_fixtures=outer_scope_fixtures)
487
473
 
488
474
  outer_scope_fixtures = \
489
475
  self.all_already_run_fixtures + \
490
- [cur_fixture for _, _, cur_fixture in ordered_fixtures[ExecutorTree]] + \
491
- [cur_fixture for _, _, cur_fixture in ordered_fixtures[Setup]]
492
- ordered_fixtures[Scenario] = self._sort_fixture_list_of_same_definition_scope(
493
- fixture_namespace_dict=all_fixtures[Scenario], outer_scope_fixtures=outer_scope_fixtures)
476
+ [cur_fixture for _, _, cur_fixture in ordered_fixtures[FixtureDefinitionScope.GLOB]] + \
477
+ [cur_fixture for _, _, cur_fixture in ordered_fixtures[FixtureDefinitionScope.SETUP]]
478
+ ordered_fixtures[FixtureDefinitionScope.SCENARIO] = self._sort_fixture_list_of_same_definition_scope(
479
+ fixture_namespace_dict=all_fixtures[FixtureDefinitionScope.SCENARIO],
480
+ outer_scope_fixtures=outer_scope_fixtures)
494
481
 
495
482
  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 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,75 @@
1
+ from __future__ import annotations
2
+ from typing import Type, Dict, Any, TypeVar, List
3
+ import dataclasses
4
+
5
+ from _balder.device import Device
6
+
7
+ ValueTypeT = TypeVar("ValueTypeT")
8
+
9
+
10
+ @dataclasses.dataclass
11
+ class FeatureAccessSelector:
12
+ """helper object for dynamic parametrizing by feature method/property"""
13
+ device: Type[Device]
14
+ device_property_name: str
15
+ feature_property_name: str
16
+ parameters: Dict[str, FeatureAccessSelector | Value] = dataclasses.field(default_factory=dict)
17
+
18
+ def get_value(self, available_parameters: Dict[str, Any]) -> List[Any]:
19
+ """accesses the configured method/property"""
20
+ resolved_parameters = {}
21
+ for cur_key, cur_value in self.parameters.items():
22
+ if isinstance(cur_value, FeatureAccessSelector):
23
+ resolved_parameters[cur_key] = cur_value.get_value(available_parameters)
24
+ elif isinstance(cur_value, Parameter):
25
+ resolved_parameters[cur_key] = available_parameters[cur_value.name]
26
+ elif isinstance(cur_value, Value):
27
+ resolved_parameters[cur_key] = cur_value.value
28
+ else:
29
+ raise TypeError(f'go unexpected parameter type {type(cur_value)} in FeatureAccessSelector')
30
+
31
+ feature = getattr(self.device, self.device_property_name)
32
+
33
+ if isinstance(getattr(feature.__class__, self.feature_property_name), property):
34
+ return getattr(feature, self.feature_property_name)
35
+
36
+ return getattr(feature, self.feature_property_name)(**resolved_parameters)
37
+
38
+ def get_parameters(
39
+ self,
40
+ of_type: Type[FeatureAccessSelector | Parameter | Value] | None = None
41
+ ) -> Dict[str, FeatureAccessSelector | Parameter | Value]:
42
+ """
43
+ Returns the parameters of this access selector
44
+
45
+ :param of_type: allows to filter the parameters by the value's type
46
+ """
47
+ result = {}
48
+ for cur_attr, cur_value in self.parameters.items():
49
+ if of_type is None or isinstance(cur_value, of_type):
50
+ result[cur_attr] = cur_value
51
+ return result
52
+
53
+
54
+ class Parameter:
55
+ """allows to parametrize a parametrization by another parametrization value"""
56
+
57
+ def __init__(self, name: str) -> None:
58
+ self._name = name
59
+
60
+ @property
61
+ def name(self) -> str:
62
+ """returns the name of the parameter"""
63
+ return self._name
64
+
65
+
66
+ class Value:
67
+ """allows to parametrize a parametrization by a fix value"""
68
+
69
+ def __init__(self, value: ValueTypeT) -> None:
70
+ self._value = value
71
+
72
+ @property
73
+ def value(self) -> ValueTypeT:
74
+ """returns the value of the parametrization"""
75
+ return self._value