baldertest 0.1.0b9__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.
- _balder/_version.py +1 -1
- _balder/balder_session.py +4 -2
- _balder/collector.py +115 -7
- _balder/console/balder.py +1 -1
- _balder/controllers/device_controller.py +1 -1
- _balder/controllers/normal_scenario_setup_controller.py +2 -2
- _balder/controllers/scenario_controller.py +91 -1
- _balder/decorator_fixture.py +4 -7
- _balder/decorator_for_vdevice.py +4 -6
- _balder/decorator_parametrize.py +31 -0
- _balder/decorator_parametrize_by_feature.py +36 -0
- _balder/exceptions.py +6 -0
- _balder/executor/basic_executable_executor.py +126 -0
- _balder/executor/basic_executor.py +1 -78
- _balder/executor/executor_tree.py +7 -5
- _balder/executor/parametrized_testcase_executor.py +52 -0
- _balder/executor/scenario_executor.py +5 -2
- _balder/executor/setup_executor.py +5 -2
- _balder/executor/testcase_executor.py +42 -9
- _balder/executor/unresolved_parametrized_testcase_executor.py +209 -0
- _balder/executor/variation_executor.py +26 -8
- _balder/fixture_definition_scope.py +19 -0
- _balder/fixture_execution_level.py +22 -0
- _balder/fixture_manager.py +169 -182
- _balder/fixture_metadata.py +26 -0
- _balder/parametrization.py +75 -0
- _balder/solver.py +51 -31
- balder/__init__.py +6 -0
- balder/exceptions.py +4 -3
- balder/parametrization.py +8 -0
- {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b10.dist-info}/METADATA +1 -1
- {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b10.dist-info}/RECORD +36 -26
- {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b10.dist-info}/WHEEL +1 -1
- {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b10.dist-info}/LICENSE +0 -0
- {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b10.dist-info}/entry_points.txt +0 -0
- {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b10.dist-info}/top_level.txt +0 -0
_balder/fixture_manager.py
CHANGED
|
@@ -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__(
|
|
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
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
self.fixtures: Dict[
|
|
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
|
-
#
|
|
37
|
-
|
|
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
|
|
57
|
+
for cur_level in FixtureExecutionLevel:
|
|
79
58
|
if cur_level in self.current_tree_fixtures.keys():
|
|
80
59
|
complete_list_in_order += [
|
|
81
|
-
|
|
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[
|
|
169
|
-
fixture_callable: Callable, arguments: List[str], cur_execution_level:
|
|
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)
|
|
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(
|
|
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[
|
|
212
|
-
|
|
213
|
-
-> List[Tuple[Union[
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
358
|
-
self.current_tree_fixtures[
|
|
359
|
-
self.current_tree_fixtures[
|
|
360
|
-
(cur_scope_namespace_type, cur_fixture_func_type,
|
|
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
|
-
|
|
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[
|
|
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
|
|
310
|
+
for cur_fixture_metadata in current_tree_fixtures_reversed:
|
|
382
311
|
try:
|
|
383
|
-
next(
|
|
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[
|
|
321
|
+
del self.current_tree_fixtures[branch.fixture_execution_level]
|
|
393
322
|
|
|
394
323
|
if exception:
|
|
395
324
|
raise exception
|
|
396
325
|
|
|
397
|
-
def
|
|
398
|
-
self,
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
|
413
|
-
|
|
414
|
-
|
|
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
|
-
|
|
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[
|
|
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.
|
|
458
|
-
all_fixtures[
|
|
459
|
-
all_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[
|
|
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.
|
|
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[
|
|
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[
|
|
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.
|
|
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[
|
|
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[
|
|
480
|
-
fixture_namespace_dict=all_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[
|
|
485
|
-
ordered_fixtures[
|
|
486
|
-
fixture_namespace_dict=all_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[
|
|
491
|
-
[cur_fixture for _, _, cur_fixture in ordered_fixtures[
|
|
492
|
-
ordered_fixtures[
|
|
493
|
-
fixture_namespace_dict=all_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
|