baldertest 0.1.0b9__py3-none-any.whl → 0.1.0b11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- _balder/_version.py +1 -1
- _balder/balder_session.py +4 -2
- _balder/cnnrelations/__init__.py +7 -0
- _balder/cnnrelations/and_connection_relation.py +149 -0
- _balder/cnnrelations/base_connection_relation.py +270 -0
- _balder/cnnrelations/or_connection_relation.py +65 -0
- _balder/collector.py +116 -14
- _balder/connection.py +400 -881
- _balder/connection_metadata.py +255 -0
- _balder/console/balder.py +1 -1
- _balder/controllers/device_controller.py +26 -12
- _balder/controllers/feature_controller.py +63 -99
- _balder/controllers/normal_scenario_setup_controller.py +7 -7
- _balder/controllers/scenario_controller.py +97 -7
- _balder/controllers/setup_controller.py +2 -3
- _balder/decorator_connect.py +12 -10
- _balder/decorator_fixture.py +4 -7
- _balder/decorator_for_vdevice.py +21 -31
- _balder/decorator_gateway.py +3 -3
- _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 +41 -9
- _balder/executor/unresolved_parametrized_testcase_executor.py +209 -0
- _balder/executor/variation_executor.py +148 -123
- _balder/feature.py +1 -1
- _balder/fixture_definition_scope.py +19 -0
- _balder/fixture_execution_level.py +22 -0
- _balder/fixture_manager.py +170 -182
- _balder/fixture_metadata.py +26 -0
- _balder/objects/connections/osi_3_network.py +2 -2
- _balder/objects/connections/osi_4_transport.py +2 -2
- _balder/parametrization.py +75 -0
- _balder/routing_path.py +18 -25
- _balder/solver.py +52 -32
- _balder/testresult.py +1 -1
- _balder/utils.py +27 -1
- balder/__init__.py +6 -0
- balder/exceptions.py +4 -3
- balder/parametrization.py +8 -0
- {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b11.dist-info}/METADATA +2 -2
- baldertest-0.1.0b11.dist-info/RECORD +83 -0
- {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b11.dist-info}/WHEEL +1 -1
- baldertest-0.1.0b9.dist-info/RECORD +0 -68
- {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b11.dist-info}/LICENSE +0 -0
- {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b11.dist-info}/entry_points.txt +0 -0
- {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b11.dist-info}/top_level.txt +0 -0
_balder/fixture_manager.py
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
import itertools
|
|
4
|
+
from typing import List, Tuple, Generator, Dict, Union, Type, Callable, Iterable, TYPE_CHECKING
|
|
3
5
|
|
|
4
6
|
import inspect
|
|
5
7
|
from graphlib import TopologicalSorter
|
|
6
8
|
from _balder.executor.testcase_executor import TestcaseExecutor
|
|
7
9
|
from _balder.scenario import Scenario
|
|
8
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
|
|
9
14
|
from _balder.executor.basic_executor import BasicExecutor
|
|
10
15
|
from _balder.executor.setup_executor import SetupExecutor
|
|
11
16
|
from _balder.executor.scenario_executor import ScenarioExecutor
|
|
@@ -22,22 +27,22 @@ class FixtureManager:
|
|
|
22
27
|
"""
|
|
23
28
|
This class is the global fixture manager. It provides various methods to manage fixtures in the balder system.
|
|
24
29
|
"""
|
|
25
|
-
#: the ordering for the execution levels
|
|
26
|
-
EXECUTION_LEVEL_ORDER = ['session', 'setup', 'scenario', 'variation', 'testcase']
|
|
27
30
|
|
|
28
|
-
def __init__(
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
fixtures: Dict[FixtureExecutionLevel,
|
|
34
|
+
Dict[Union[None, Type[Scenario], Type[Setup]], List[Tuple[MethodLiteralType, Callable]]]]):
|
|
29
35
|
|
|
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[
|
|
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
|
|
34
42
|
|
|
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]]] = {}
|
|
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]] = {}
|
|
41
46
|
|
|
42
47
|
# ---------------------------------- STATIC METHODS ----------------------------------------------------------------
|
|
43
48
|
|
|
@@ -45,128 +50,23 @@ class FixtureManager:
|
|
|
45
50
|
|
|
46
51
|
# ---------------------------------- PROPERTIES --------------------------------------------------------------------
|
|
47
52
|
|
|
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
53
|
@property
|
|
73
54
|
def all_already_run_fixtures(self) -> List[Callable]:
|
|
74
55
|
"""
|
|
75
56
|
returns a list of all fixtures that have already been run
|
|
76
57
|
"""
|
|
77
58
|
complete_list_in_order = []
|
|
78
|
-
for cur_level in
|
|
59
|
+
for cur_level in FixtureExecutionLevel:
|
|
79
60
|
if cur_level in self.current_tree_fixtures.keys():
|
|
80
61
|
complete_list_in_order += [
|
|
81
|
-
|
|
62
|
+
cur_fixture_metadata.callable for cur_fixture_metadata in self.current_tree_fixtures[cur_level]]
|
|
82
63
|
return complete_list_in_order
|
|
83
64
|
|
|
84
65
|
# ---------------------------------- PROTECTED METHODS -------------------------------------------------------------
|
|
85
66
|
|
|
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
67
|
def _validate_for_unclear_setup_scoped_fixture_reference(
|
|
168
|
-
self, fixture_callable_namespace: Union[
|
|
169
|
-
fixture_callable: Callable, arguments: List[str], cur_execution_level:
|
|
68
|
+
self, fixture_callable_namespace: Union[None, Type[Scenario], Type[Setup]],
|
|
69
|
+
fixture_callable: Callable, arguments: List[str], cur_execution_level: FixtureExecutionLevel):
|
|
170
70
|
"""
|
|
171
71
|
This helper method validates a given fixture reference for the unclear setup scoped fixture reference.
|
|
172
72
|
|
|
@@ -184,7 +84,7 @@ class FixtureManager:
|
|
|
184
84
|
The error will be thrown if a scenario scoped fixture with the execution level SESSION references a fixture
|
|
185
85
|
name that exists in one or more setup scoped fixtures with the execution level SESSION.
|
|
186
86
|
|
|
187
|
-
:param fixture_callable_namespace: the namespace of the current fixture
|
|
87
|
+
:param fixture_callable_namespace: the namespace of the current fixture or None if it is a `balderglob.py` file
|
|
188
88
|
|
|
189
89
|
:param fixture_callable: the callable with the arguments
|
|
190
90
|
|
|
@@ -194,10 +94,11 @@ class FixtureManager:
|
|
|
194
94
|
"""
|
|
195
95
|
# this method is only interested in fixtures with execution level SESSION!
|
|
196
96
|
|
|
197
|
-
if isinstance(fixture_callable_namespace, Scenario)
|
|
97
|
+
if (fixture_callable_namespace is not None and isinstance(fixture_callable_namespace, Scenario)
|
|
98
|
+
and cur_execution_level == FixtureExecutionLevel.SESSION):
|
|
198
99
|
all_setup_scoped_fixtures = []
|
|
199
100
|
|
|
200
|
-
for cur_namespace_type, fixtures in self.fixtures.get(
|
|
101
|
+
for cur_namespace_type, fixtures in self.fixtures.get(FixtureExecutionLevel.SESSION, {}).items():
|
|
201
102
|
if cur_namespace_type is not None and issubclass(cur_namespace_type, Setup):
|
|
202
103
|
all_setup_scoped_fixtures += [cur_fixt.__name__ for _, cur_fixt in fixtures]
|
|
203
104
|
for cur_arg in arguments:
|
|
@@ -208,9 +109,9 @@ class FixtureManager:
|
|
|
208
109
|
f"fixtures with the definition scope that have the same name - please rename them!")
|
|
209
110
|
|
|
210
111
|
def _sort_fixture_list_of_same_definition_scope(
|
|
211
|
-
self, fixture_namespace_dict: Dict[
|
|
212
|
-
|
|
213
|
-
-> List[Tuple[Union[
|
|
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]]:
|
|
214
115
|
"""
|
|
215
116
|
This is a helper method that allows to sort the fixtures given in `fixture_namespace_dict`, depending on their
|
|
216
117
|
arguments.
|
|
@@ -233,7 +134,7 @@ class FixtureManager:
|
|
|
233
134
|
|
|
234
135
|
# returns the func_type for the given fixture
|
|
235
136
|
fixture_func_types = {}
|
|
236
|
-
for
|
|
137
|
+
for _, cur_fixture_list in fixture_namespace_dict.items():
|
|
237
138
|
for cur_func_type, cur_fixture in cur_fixture_list:
|
|
238
139
|
fixture_func_types[cur_fixture] = cur_func_type
|
|
239
140
|
|
|
@@ -279,6 +180,39 @@ class FixtureManager:
|
|
|
279
180
|
return [(cur_definition_scope, fixture_func_types[cur_func], cur_func)
|
|
280
181
|
for cur_definition_scope, cur_func in sorter.static_order()]
|
|
281
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
|
+
|
|
282
216
|
# ---------------------------------- METHODS -----------------------------------------------------------------------
|
|
283
217
|
|
|
284
218
|
def is_allowed_to_enter(
|
|
@@ -287,8 +221,7 @@ class FixtureManager:
|
|
|
287
221
|
"""
|
|
288
222
|
This method return true if the given branch can be entered, otherwise false
|
|
289
223
|
"""
|
|
290
|
-
|
|
291
|
-
return execution_level not in self.current_tree_fixtures.keys()
|
|
224
|
+
return branch.fixture_execution_level not in self.current_tree_fixtures.keys()
|
|
292
225
|
|
|
293
226
|
def is_allowed_to_leave(
|
|
294
227
|
self, branch: Union[BasicExecutor, ExecutorTree, SetupExecutor, ScenarioExecutor, VariationExecutor,
|
|
@@ -298,8 +231,7 @@ class FixtureManager:
|
|
|
298
231
|
This method returns true if the given branch can be left now (there exist entries from earlier run enter()
|
|
299
232
|
for this branch), otherwise false
|
|
300
233
|
"""
|
|
301
|
-
|
|
302
|
-
return execution_level in self.current_tree_fixtures.keys()
|
|
234
|
+
return branch.fixture_execution_level in self.current_tree_fixtures.keys()
|
|
303
235
|
|
|
304
236
|
def enter(self, branch: Union[BasicExecutor, ExecutorTree, SetupExecutor, ScenarioExecutor, VariationExecutor,
|
|
305
237
|
TestcaseExecutor]):
|
|
@@ -311,7 +243,6 @@ class FixtureManager:
|
|
|
311
243
|
|
|
312
244
|
:raise BalderFixtureException: is thrown if an error occurs while executing a user fixture
|
|
313
245
|
"""
|
|
314
|
-
execution_level = self.resolve_type_level[branch.__class__]
|
|
315
246
|
|
|
316
247
|
if not self.is_allowed_to_enter(branch):
|
|
317
248
|
raise LostInExecutorTreeException(
|
|
@@ -321,7 +252,7 @@ class FixtureManager:
|
|
|
321
252
|
yield None
|
|
322
253
|
# now iterate over all fixtures that should be executed in this enter() call
|
|
323
254
|
# -> collect them with all different DEFINITION-SCOPES
|
|
324
|
-
for cur_definition_scope in
|
|
255
|
+
for cur_definition_scope in FixtureDefinitionScope:
|
|
325
256
|
cur_fixture_list = self.get_all_fixtures_for_current_level(branch=branch).get(cur_definition_scope)
|
|
326
257
|
for cur_scope_namespace_type, cur_fixture_func_type, cur_fixture in cur_fixture_list:
|
|
327
258
|
try:
|
|
@@ -354,10 +285,11 @@ class FixtureManager:
|
|
|
354
285
|
cur_generator = empty()
|
|
355
286
|
next(cur_generator)
|
|
356
287
|
# 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,
|
|
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))
|
|
361
293
|
except StopIteration:
|
|
362
294
|
pass
|
|
363
295
|
# every other exception that is thrown, will be recognized and rethrown
|
|
@@ -371,36 +303,95 @@ class FixtureManager:
|
|
|
371
303
|
:param branch: specifies the element of the ExecutorTree that should be left (note that the current position
|
|
372
304
|
is very important here)
|
|
373
305
|
"""
|
|
374
|
-
|
|
375
|
-
if execution_level not in self.current_tree_fixtures.keys():
|
|
306
|
+
if branch.fixture_execution_level not in self.current_tree_fixtures.keys():
|
|
376
307
|
raise LostInExecutorTreeException("can not leave the current branch, because it was not entered before")
|
|
377
308
|
|
|
378
|
-
current_tree_fixtures_reversed = self.current_tree_fixtures[
|
|
309
|
+
current_tree_fixtures_reversed = self.current_tree_fixtures[branch.fixture_execution_level]
|
|
379
310
|
current_tree_fixtures_reversed.reverse()
|
|
380
311
|
exception = None
|
|
381
|
-
for
|
|
312
|
+
for cur_fixture_metadata in current_tree_fixtures_reversed:
|
|
382
313
|
try:
|
|
383
|
-
next(
|
|
314
|
+
next(cur_fixture_metadata.generator)
|
|
384
315
|
except StopIteration:
|
|
385
316
|
pass
|
|
386
|
-
except Exception as exc:
|
|
317
|
+
except Exception as exc: # pylint: disable=broad-exception-caught
|
|
387
318
|
if not exception:
|
|
388
319
|
# only save the first exception
|
|
389
320
|
exception = exc
|
|
390
321
|
|
|
391
322
|
# reset the left location
|
|
392
|
-
del self.current_tree_fixtures[
|
|
323
|
+
del self.current_tree_fixtures[branch.fixture_execution_level]
|
|
393
324
|
|
|
394
325
|
if exception:
|
|
395
326
|
raise exception
|
|
396
327
|
|
|
397
|
-
def
|
|
398
|
-
self,
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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]]:
|
|
404
395
|
"""
|
|
405
396
|
This method returns all classes of a specific Setup/Scenario class for a specific execution-level.
|
|
406
397
|
|
|
@@ -409,10 +400,9 @@ class FixtureManager:
|
|
|
409
400
|
:param parent_classes: true if the method should look for fixtures in parent classes too
|
|
410
401
|
:return: list with all fixtures that are matching search criteria
|
|
411
402
|
"""
|
|
412
|
-
# current relevant
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
fixtures_of_exec_level = self.fixtures.get(cur_execution_level, {})
|
|
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, {})
|
|
416
406
|
if setup_or_scenario_class is not None and parent_classes:
|
|
417
407
|
all_fixtures = []
|
|
418
408
|
for cur_parent_class in inspect.getmro(setup_or_scenario_class):
|
|
@@ -426,13 +416,11 @@ class FixtureManager:
|
|
|
426
416
|
_added_fixtures.append(cur_fixture_tuple[1].__name__)
|
|
427
417
|
remaining_fixtures.append(cur_fixture_tuple)
|
|
428
418
|
return remaining_fixtures
|
|
429
|
-
|
|
430
|
-
return fixtures_of_exec_level.get(setup_or_scenario_class, [])
|
|
419
|
+
return fixtures_of_exec_level.get(setup_or_scenario_class, [])
|
|
431
420
|
|
|
432
421
|
def get_all_fixtures_for_current_level(
|
|
433
422
|
self, branch: Union[ExecutorTree, SetupExecutor, ScenarioExecutor, VariationExecutor, TestcaseExecutor]) \
|
|
434
|
-
-> Dict[Union[
|
|
435
|
-
List[Tuple[Union[Type[ExecutorTree], Type[Scenario], Type[Setup]], str, object]]]:
|
|
423
|
+
-> Dict[FixtureDefinitionScope, List[Tuple[Union[None, Type[Scenario], Type[Setup]], str, object]]]:
|
|
436
424
|
"""
|
|
437
425
|
This method delivers all fixtures which should be executed for the given branch of the executor tree.
|
|
438
426
|
|
|
@@ -450,46 +438,46 @@ class FixtureManager:
|
|
|
450
438
|
or :class:`Setup`) as first argument, the fixture func_type as second and the fixture callable as third
|
|
451
439
|
argument (this list is ordered after the call hierarchy)
|
|
452
440
|
"""
|
|
453
|
-
from _balder.executor.executor_tree import ExecutorTree
|
|
454
|
-
|
|
455
441
|
all_fixtures = {}
|
|
456
442
|
# 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[
|
|
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
|
|
460
446
|
# get all relevant fixtures with definition scope "setup"
|
|
461
|
-
all_fixtures[
|
|
447
|
+
all_fixtures[FixtureDefinitionScope.SETUP] = {}
|
|
462
448
|
for cur_setup in branch.get_all_base_instances_of_this_branch(Setup, only_runnable_elements=True):
|
|
463
449
|
# check if there exists fixtures for the current setup
|
|
464
|
-
cur_setup_fixtures = self.get_fixture_for_class(branch.
|
|
450
|
+
cur_setup_fixtures = self.get_fixture_for_class(branch.fixture_execution_level, cur_setup.__class__)
|
|
465
451
|
if cur_setup_fixtures:
|
|
466
|
-
all_fixtures[
|
|
452
|
+
all_fixtures[FixtureDefinitionScope.SETUP][cur_setup.__class__] = cur_setup_fixtures
|
|
467
453
|
|
|
468
454
|
# get all relevant fixtures with definition scope "scenario"
|
|
469
|
-
all_fixtures[
|
|
455
|
+
all_fixtures[FixtureDefinitionScope.SCENARIO] = {}
|
|
470
456
|
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.
|
|
457
|
+
cur_scenario_fixtures = self.get_fixture_for_class(branch.fixture_execution_level, cur_scenario.__class__)
|
|
472
458
|
if cur_scenario_fixtures:
|
|
473
|
-
all_fixtures[
|
|
459
|
+
all_fixtures[FixtureDefinitionScope.SCENARIO][cur_scenario.__class__] = cur_scenario_fixtures
|
|
474
460
|
|
|
475
461
|
ordered_fixtures = {}
|
|
476
462
|
# Now the basic order is: [All of ExecutorTree] -> [All of Setup] -> [All of Scenario]
|
|
477
463
|
# but the order within these DEFINITION SCOPES has to be determined now!
|
|
478
464
|
outer_scope_fixtures = self.all_already_run_fixtures
|
|
479
|
-
ordered_fixtures[
|
|
480
|
-
fixture_namespace_dict=all_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)
|
|
481
467
|
|
|
482
468
|
outer_scope_fixtures = \
|
|
483
469
|
self.all_already_run_fixtures + \
|
|
484
|
-
[cur_fixture for _, _, cur_fixture in ordered_fixtures[
|
|
485
|
-
ordered_fixtures[
|
|
486
|
-
fixture_namespace_dict=all_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)
|
|
487
474
|
|
|
488
475
|
outer_scope_fixtures = \
|
|
489
476
|
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[
|
|
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)
|
|
494
482
|
|
|
495
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 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
|
|
@@ -20,7 +20,7 @@ class IPv6Connection(Connection):
|
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
IPConnection = Connection.based_on(IPv4Connection
|
|
23
|
+
IPConnection = Connection.based_on(IPv4Connection | IPv6Connection)
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
@insert_into_tree(parents=[IPv4Connection, IPv6Connection])
|
|
@@ -44,4 +44,4 @@ class ICMPv6Connection(Connection):
|
|
|
44
44
|
"""
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
ICMPConnection = Connection.based_on(ICMPv4Connection
|
|
47
|
+
ICMPConnection = Connection.based_on(ICMPv4Connection | ICMPv6Connection)
|
|
@@ -20,7 +20,7 @@ class TcpIPv6Connection(Connection):
|
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
TcpConnection = Connection.based_on(TcpIPv4Connection
|
|
23
|
+
TcpConnection = Connection.based_on(TcpIPv4Connection | TcpIPv6Connection)
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
@insert_into_tree(parents=[osi_3_network.IPv4Connection])
|
|
@@ -37,4 +37,4 @@ class UdpIPv6Connection(Connection):
|
|
|
37
37
|
"""
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
UdpConnection = Connection.based_on(UdpIPv4Connection
|
|
40
|
+
UdpConnection = Connection.based_on(UdpIPv4Connection | UdpIPv6Connection)
|