baldertest 0.1.0b12__py3-none-any.whl → 0.1.0b13__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 +8 -3
- _balder/cnnrelations/and_connection_relation.py +28 -1
- _balder/cnnrelations/base_connection_relation.py +1 -1
- _balder/collector.py +101 -113
- _balder/connection.py +76 -80
- _balder/controllers/device_controller.py +5 -24
- _balder/controllers/feature_controller.py +2 -2
- _balder/controllers/normal_scenario_setup_controller.py +1 -2
- _balder/controllers/scenario_controller.py +121 -0
- _balder/controllers/vdevice_controller.py +2 -23
- _balder/decorator_connect.py +3 -3
- _balder/decorator_covered_by.py +28 -36
- _balder/executor/basic_executable_executor.py +13 -6
- _balder/executor/basic_executor.py +31 -4
- _balder/executor/executor_tree.py +5 -4
- _balder/executor/parametrized_testcase_executor.py +2 -2
- _balder/executor/scenario_executor.py +12 -71
- _balder/executor/setup_executor.py +4 -3
- _balder/executor/testcase_executor.py +35 -19
- _balder/executor/unresolved_parametrized_testcase_executor.py +32 -57
- _balder/executor/variation_executor.py +21 -48
- _balder/feature.py +2 -1
- _balder/feature_replacement_mapping.py +39 -1
- _balder/fixture_manager.py +1 -1
- _balder/fixture_metadata.py +1 -1
- _balder/routing_path.py +18 -10
- _balder/scenario.py +2 -2
- _balder/setup.py +2 -1
- _balder/testresult.py +4 -3
- _balder/utils/__init__.py +0 -0
- _balder/{utils.py → utils/functions.py} +29 -31
- _balder/utils/inner_device_managing_metaclass.py +14 -0
- _balder/utils/mixin_can_be_covered_by_executor.py +24 -0
- _balder/utils/typings.py +4 -0
- {baldertest-0.1.0b12.dist-info → baldertest-0.1.0b13.dist-info}/METADATA +3 -2
- {baldertest-0.1.0b12.dist-info → baldertest-0.1.0b13.dist-info}/RECORD +40 -36
- {baldertest-0.1.0b12.dist-info → baldertest-0.1.0b13.dist-info}/WHEEL +1 -1
- {baldertest-0.1.0b12.dist-info → baldertest-0.1.0b13.dist-info}/entry_points.txt +0 -0
- {baldertest-0.1.0b12.dist-info → baldertest-0.1.0b13.dist-info/licenses}/LICENSE +0 -0
- {baldertest-0.1.0b12.dist-info → baldertest-0.1.0b13.dist-info}/top_level.txt +0 -0
|
@@ -72,8 +72,7 @@ class NormalScenarioSetupController(Controller, ABC):
|
|
|
72
72
|
if not issubclass(cur_class, Device):
|
|
73
73
|
# filter all classes and make sure that only the child classes of :meth:`Device` remain
|
|
74
74
|
continue
|
|
75
|
-
|
|
76
|
-
if outer_class_name != self.related_cls.__name__:
|
|
75
|
+
if DeviceController.get_for(cur_class).get_outer_class() != self.related_cls:
|
|
77
76
|
# filter all classes that do not match the setup name in __qualname__
|
|
78
77
|
continue
|
|
79
78
|
# otherwise, add this candidate
|
|
@@ -14,6 +14,7 @@ from _balder.controllers.normal_scenario_setup_controller import NormalScenarioS
|
|
|
14
14
|
from _balder.parametrization import FeatureAccessSelector, Parameter
|
|
15
15
|
from _balder.exceptions import UnclearAssignableFeatureConnectionError, ConnectionIntersectionError, \
|
|
16
16
|
MultiInheritanceError
|
|
17
|
+
from _balder.utils.functions import get_scenario_inheritance_list_of
|
|
17
18
|
|
|
18
19
|
logger = logging.getLogger(__file__)
|
|
19
20
|
|
|
@@ -36,6 +37,9 @@ class ScenarioController(NormalScenarioSetupController):
|
|
|
36
37
|
# describes if the current controller is for setups or for scenarios (has to be set in child controller)
|
|
37
38
|
self._related_type = Scenario
|
|
38
39
|
|
|
40
|
+
# holds covered-by configuration
|
|
41
|
+
self._covered_by = {}
|
|
42
|
+
|
|
39
43
|
# this helps to make this constructor only possible inside the controller object
|
|
40
44
|
if _priv_instantiate_key != ScenarioController.__priv_instantiate_key:
|
|
41
45
|
raise RuntimeError('it is not allowed to instantiate a controller manually -> use the static method '
|
|
@@ -126,6 +130,73 @@ class ScenarioController(NormalScenarioSetupController):
|
|
|
126
130
|
ordered_dict[cur_arg] = params[cur_arg]
|
|
127
131
|
return ordered_dict
|
|
128
132
|
|
|
133
|
+
def register_covered_by_for(self, meth: Union[str, None], covered_by: Union[Scenario, Callable, None]) -> None:
|
|
134
|
+
"""
|
|
135
|
+
This method registers a covered-by statement for this Scenario. If `meth` is provided, the statement is for the
|
|
136
|
+
specific test method of the scenario, otherwise it is for the whole setup. The item provided in `covered_by`
|
|
137
|
+
describes the test object that covers this scenario (method).
|
|
138
|
+
|
|
139
|
+
:param meth: if provided this attribute describes the test method that should be registered, otherwise the whole
|
|
140
|
+
scenario will be registered
|
|
141
|
+
:param covered_by: describes the test object that covers this scenario (method)
|
|
142
|
+
"""
|
|
143
|
+
if not (meth is None or isinstance(meth, str)):
|
|
144
|
+
raise TypeError('meth needs to be None or a string')
|
|
145
|
+
if meth is not None:
|
|
146
|
+
if not meth.startswith('test_'):
|
|
147
|
+
raise TypeError(
|
|
148
|
+
f"the use of the `@covered_by` decorator is only allowed for `Scenario` objects and test methods "
|
|
149
|
+
f"of `Scenario` objects - the method `{self.related_cls.__name__}.{meth}` does not start with "
|
|
150
|
+
f"`test_` and is not a valid test method")
|
|
151
|
+
if not hasattr(self.related_cls, meth):
|
|
152
|
+
raise ValueError(
|
|
153
|
+
f"the provided test method `{meth}` does not exist in scenario `{self.related_cls.__name__}`"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
if meth not in self._covered_by.keys():
|
|
157
|
+
self._covered_by[meth] = []
|
|
158
|
+
if covered_by is None:
|
|
159
|
+
# reset it
|
|
160
|
+
# todo what if there are more than one decorator in one class
|
|
161
|
+
del self._covered_by[meth]
|
|
162
|
+
else:
|
|
163
|
+
self._covered_by[meth].append(covered_by)
|
|
164
|
+
|
|
165
|
+
def get_raw_covered_by_dict(self) -> Dict[Union[str, None], List[Union[Scenario, Callable]]]:
|
|
166
|
+
"""
|
|
167
|
+
:return: returns the internal covered-by dictionary
|
|
168
|
+
"""
|
|
169
|
+
return self._covered_by.copy()
|
|
170
|
+
|
|
171
|
+
def get_abs_covered_by_dict(self) -> Dict[Union[str, None], List[Union[Scenario, Callable]]]:
|
|
172
|
+
"""
|
|
173
|
+
This method resolves the covered-by statements over all inheritance levels. It automatically
|
|
174
|
+
cleans up every inheritance of the covered_by decorators for every parent class of this scenario.
|
|
175
|
+
"""
|
|
176
|
+
parent_classes = [p for p in self.related_cls.__bases__ if issubclass(p, Scenario) and p != Scenario]
|
|
177
|
+
if len(parent_classes) > 1:
|
|
178
|
+
raise MultiInheritanceError(
|
|
179
|
+
f'can not resolve classes for `{self.related_cls}` because there are more than one Scenario based '
|
|
180
|
+
f'parent classes'
|
|
181
|
+
)
|
|
182
|
+
# no more parent classes -> raw is absolute
|
|
183
|
+
if len(parent_classes) == 0:
|
|
184
|
+
return self.get_raw_covered_by_dict()
|
|
185
|
+
parent_controller = self.__class__.get_for(parent_classes[0])
|
|
186
|
+
self_raw_covered_by_dict = self.get_raw_covered_by_dict()
|
|
187
|
+
|
|
188
|
+
#: first fill result with data from parent controller
|
|
189
|
+
result = {
|
|
190
|
+
k if k is None else getattr(self.related_cls, k.__name__): v
|
|
191
|
+
for k, v in parent_controller.get_abs_covered_by_dict().items()
|
|
192
|
+
}
|
|
193
|
+
for cur_callable, cur_coveredby in self_raw_covered_by_dict.items():
|
|
194
|
+
if cur_callable in result.keys():
|
|
195
|
+
result[cur_callable].extend(cur_coveredby)
|
|
196
|
+
else:
|
|
197
|
+
result[cur_callable] = cur_coveredby
|
|
198
|
+
return result
|
|
199
|
+
|
|
129
200
|
def check_for_parameter_loop_in_dynamic_parametrization(self, cur_fn: Callable):
|
|
130
201
|
"""
|
|
131
202
|
This method checks for a parameter loop in all dynamic parametrization for a specific test method. If it detects
|
|
@@ -196,6 +267,56 @@ class ScenarioController(NormalScenarioSetupController):
|
|
|
196
267
|
|
|
197
268
|
return all_relevant_func
|
|
198
269
|
|
|
270
|
+
def get_ignore_test_methods(self) -> List[callable]:
|
|
271
|
+
"""
|
|
272
|
+
This method returns a list of all methods that have the IGNORE marker. It automatically resolves marker that
|
|
273
|
+
were provided on parent class scenarios.
|
|
274
|
+
"""
|
|
275
|
+
result = []
|
|
276
|
+
next_parent_class = get_scenario_inheritance_list_of(self.related_cls)[1]
|
|
277
|
+
if next_parent_class != Scenario:
|
|
278
|
+
next_parent_class_controller = ScenarioController.get_for(next_parent_class)
|
|
279
|
+
next_parent_ignore_meths = next_parent_class_controller.get_ignore_test_methods()
|
|
280
|
+
result.extend(next_parent_ignore_meths)
|
|
281
|
+
for cur_ignore_meth_as_str in self.related_cls.IGNORE:
|
|
282
|
+
cur_ignore_meth = getattr(self.related_cls, cur_ignore_meth_as_str)
|
|
283
|
+
result.append(cur_ignore_meth)
|
|
284
|
+
return list(set(result))
|
|
285
|
+
|
|
286
|
+
def get_skip_test_methods(self) -> List[callable]:
|
|
287
|
+
"""
|
|
288
|
+
This method returns a list of all methods that have the SKIP marker. It automatically resolves marker that were
|
|
289
|
+
provided on parent class scenarios.
|
|
290
|
+
"""
|
|
291
|
+
result = []
|
|
292
|
+
next_parent_class = get_scenario_inheritance_list_of(self.related_cls)[1]
|
|
293
|
+
next_parent_ignore_meths = []
|
|
294
|
+
|
|
295
|
+
if next_parent_class != Scenario:
|
|
296
|
+
next_parent_class_controller = ScenarioController.get_for(next_parent_class)
|
|
297
|
+
next_parent_ignore_meths = next_parent_class_controller.get_ignore_test_methods()
|
|
298
|
+
next_parent_skip_meths = next_parent_class_controller.get_skip_test_methods()
|
|
299
|
+
result.extend(next_parent_skip_meths)
|
|
300
|
+
|
|
301
|
+
for cur_skip_meth_as_str in self.related_cls.SKIP:
|
|
302
|
+
cur_skip_meth = getattr(self.related_cls, cur_skip_meth_as_str)
|
|
303
|
+
if cur_skip_meth in next_parent_ignore_meths:
|
|
304
|
+
raise ValueError(f'found skip method `{cur_skip_meth}` defined in `{self.related_cls}.SKIP`, but was '
|
|
305
|
+
f'already added to IGNORE in parent class')
|
|
306
|
+
result.append(cur_skip_meth)
|
|
307
|
+
|
|
308
|
+
return list(set(result))
|
|
309
|
+
|
|
310
|
+
def get_run_test_methods(self) -> List[callable]:
|
|
311
|
+
"""
|
|
312
|
+
This method returns a list of all methods that should run in this scenario. It automatically resolves
|
|
313
|
+
SKIP/IGNORE marker that were provided on parent class scenarios.
|
|
314
|
+
"""
|
|
315
|
+
result = (set(self.get_all_test_methods())
|
|
316
|
+
- set(self.get_skip_test_methods())
|
|
317
|
+
- set(self.get_ignore_test_methods()))
|
|
318
|
+
return list(result)
|
|
319
|
+
|
|
199
320
|
def validate_feature_clearance_for_parallel_connections(self):
|
|
200
321
|
"""
|
|
201
322
|
This method validates for every active class-based feature (only the ones that have an active VDevice<->Device
|
|
@@ -2,11 +2,10 @@ from __future__ import annotations
|
|
|
2
2
|
from typing import Type, Dict, Union
|
|
3
3
|
|
|
4
4
|
import logging
|
|
5
|
-
import inspect
|
|
6
5
|
from _balder.vdevice import VDevice
|
|
7
6
|
from _balder.feature import Feature
|
|
8
7
|
from _balder.controllers.base_device_controller import BaseDeviceController
|
|
9
|
-
from _balder.exceptions import
|
|
8
|
+
from _balder.exceptions import VDeviceResolvingError
|
|
10
9
|
|
|
11
10
|
logger = logging.getLogger(__file__)
|
|
12
11
|
|
|
@@ -69,27 +68,7 @@ class VDeviceController(BaseDeviceController):
|
|
|
69
68
|
"""
|
|
70
69
|
This method delivers the outer class of this device. In Balder, this has to be a :class:`Feature`.
|
|
71
70
|
"""
|
|
72
|
-
|
|
73
|
-
if self.related_cls.__qualname__.count('.') == 0:
|
|
74
|
-
raise DeviceScopeError("the current device class is no inner class")
|
|
75
|
-
all_splits = self.related_cls.__qualname__.split('.')
|
|
76
|
-
|
|
77
|
-
outer_class = [cur_class for cur_name, cur_class in inspect.getmembers(
|
|
78
|
-
inspect.getmodule(self.related_cls), inspect.isclass) if cur_name == all_splits[0]][0]
|
|
79
|
-
# only get the next outer class (not higher)
|
|
80
|
-
for cur_class_name in all_splits[1:-1]:
|
|
81
|
-
outer_class = getattr(outer_class, cur_class_name)
|
|
82
|
-
# instantiate it to get the real type (not the decorator class of `@for_vdevice`)
|
|
83
|
-
# we can instantiate it, because we do nothing in it
|
|
84
|
-
outer_class = outer_class().__class__
|
|
85
|
-
|
|
86
|
-
all_inner_classes = [cur_inner_class for _, cur_inner_class in inspect.getmembers(outer_class, inspect.isclass)]
|
|
87
|
-
if self.related_cls in all_inner_classes:
|
|
88
|
-
if not issubclass(outer_class, Feature):
|
|
89
|
-
raise TypeError(f"the found outer class is of type `{outer_class.__name__}` - this is a type which is "
|
|
90
|
-
f"not allowed (outer class has to be a `Feature` class)")
|
|
91
|
-
return outer_class
|
|
92
|
-
raise RuntimeError(f"can not find the outer class of this vDevice `{self.related_cls.__qualname__}`")
|
|
71
|
+
return getattr(self.related_cls, '_outer_balder_class', None)
|
|
93
72
|
|
|
94
73
|
def get_next_parent_vdevice(self) -> Union[Type[VDevice], None]:
|
|
95
74
|
"""
|
_balder/decorator_connect.py
CHANGED
|
@@ -69,10 +69,10 @@ def connect(
|
|
|
69
69
|
nonlocal self_node_name
|
|
70
70
|
nonlocal dest_node_name
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
this_outer_ref = decorated_cls.__qualname__.split('.')[:-1]
|
|
73
73
|
if not isinstance(with_device, str):
|
|
74
|
-
|
|
75
|
-
if
|
|
74
|
+
other_outer_ref = with_device.__qualname__.split('.')[:-1]
|
|
75
|
+
if this_outer_ref != other_outer_ref:
|
|
76
76
|
raise ValueError(
|
|
77
77
|
f"the given device is not mentioned in this setup/scenario - please create a new "
|
|
78
78
|
f"direct inner device class, it can be inherited from `{with_device.__qualname__}`")
|
_balder/decorator_covered_by.py
CHANGED
|
@@ -2,11 +2,12 @@ from __future__ import annotations
|
|
|
2
2
|
from typing import Type, Union
|
|
3
3
|
|
|
4
4
|
import inspect
|
|
5
|
+
|
|
6
|
+
from _balder.controllers import ScenarioController
|
|
5
7
|
from _balder.scenario import Scenario
|
|
6
|
-
from _balder.utils import get_class_that_defines_method
|
|
7
8
|
|
|
8
9
|
|
|
9
|
-
def covered_by(item: Union[Type[Scenario], callable, None]):
|
|
10
|
+
def covered_by(item: Union[Type[Scenario], str, callable, None]):
|
|
10
11
|
"""
|
|
11
12
|
This decorator defines that there exists another Scenario class or test method item that has a similar
|
|
12
13
|
implementation like the decorated :class:`Scenario` class or the decorated test method.
|
|
@@ -16,14 +17,15 @@ def covered_by(item: Union[Type[Scenario], callable, None]):
|
|
|
16
17
|
|
|
17
18
|
if item is None:
|
|
18
19
|
pass
|
|
19
|
-
elif
|
|
20
|
-
issubclass(get_class_that_defines_method(item), Scenario):
|
|
20
|
+
elif isinstance(item, str) and item.startswith("test_"):
|
|
21
21
|
pass
|
|
22
|
-
elif
|
|
22
|
+
elif callable(item) and inspect.isfunction(item) and item.__name__.startswith("test_"):
|
|
23
23
|
pass
|
|
24
|
+
elif isinstance(item, type) and issubclass(item, Scenario):
|
|
25
|
+
raise NotImplementedError('The covered-by other scenario classes is not supported yet')
|
|
24
26
|
else:
|
|
25
|
-
raise TypeError("the given element for `item` must be a
|
|
26
|
-
"
|
|
27
|
+
raise TypeError("the given element for `item` must be a test method of a scenario class (has to start with "
|
|
28
|
+
"`test_`)")
|
|
27
29
|
|
|
28
30
|
class CoveredByDecorator:
|
|
29
31
|
"""decorator class for `@covered_by` decorator"""
|
|
@@ -36,46 +38,36 @@ def covered_by(item: Union[Type[Scenario], callable, None]):
|
|
|
36
38
|
raise TypeError(f"The decorator `@covered_by` may only be used for `Scenario` objects or for test "
|
|
37
39
|
f"methods of one `Scenario` object. This is not possible for the applied class "
|
|
38
40
|
f"`{func.__name__}`.")
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
# reset it
|
|
45
|
-
func._covered_by[func] = []
|
|
46
|
-
elif item not in func._covered_by[func]:
|
|
47
|
-
func._covered_by[func].append(item)
|
|
48
|
-
elif inspect.isfunction(func):
|
|
41
|
+
raise NotImplementedError('The covered-by decoration of other scenario classes is not supported yet')
|
|
42
|
+
# scenario_controller = ScenarioController.get_for(func)
|
|
43
|
+
# register for the whole class
|
|
44
|
+
# scenario_controller.register_covered_by_for(meth=None, covered_by=item)
|
|
45
|
+
if inspect.isfunction(func):
|
|
49
46
|
# work will done in `__set_name__`
|
|
50
47
|
pass
|
|
51
48
|
else:
|
|
52
49
|
raise TypeError(f"The use of the `@covered_by` decorator is not allowed for the `{str(func)}` element. "
|
|
53
|
-
f"You should only use this decorator for
|
|
54
|
-
f"of a `Scenario` object")
|
|
50
|
+
f"You should only use this decorator for test method elements of a `Scenario` object")
|
|
55
51
|
|
|
56
52
|
def __set_name__(self, owner, name):
|
|
57
53
|
if issubclass(owner, Scenario):
|
|
58
54
|
if not inspect.isfunction(self.func):
|
|
59
|
-
raise TypeError("the use of the `@covered_by` decorator is only allowed for
|
|
60
|
-
"
|
|
55
|
+
raise TypeError("the use of the `@covered_by` decorator is only allowed for test methods of "
|
|
56
|
+
"`Scenario` objects")
|
|
61
57
|
if not name.startswith('test_'):
|
|
62
|
-
raise TypeError(f"the use of the `@covered_by` decorator is only allowed for
|
|
63
|
-
f"
|
|
64
|
-
f"
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
# reset it
|
|
72
|
-
owner._covered_by[self.func] = []
|
|
73
|
-
elif item not in owner._covered_by[self.func]:
|
|
74
|
-
owner._covered_by[self.func].append(item)
|
|
58
|
+
raise TypeError(f"the use of the `@covered_by` decorator is only allowed for test methods of "
|
|
59
|
+
f"`Scenario` objects - the method `{owner.__name__}.{name}` does not start with "
|
|
60
|
+
f"`test_` and is not a valid test method")
|
|
61
|
+
# if item is a string - resolve method
|
|
62
|
+
cleared_item = item
|
|
63
|
+
if isinstance(item, str):
|
|
64
|
+
cleared_item = getattr(owner, item)
|
|
65
|
+
scenario_controller = ScenarioController.get_for(owner)
|
|
66
|
+
scenario_controller.register_covered_by_for(meth=name, covered_by=cleared_item)
|
|
75
67
|
else:
|
|
76
68
|
raise TypeError(f"The use of the `@covered_by` decorator is not allowed for methods of a "
|
|
77
|
-
f"`{owner.__name__}`. You should only use this decorator for
|
|
78
|
-
f"
|
|
69
|
+
f"`{owner.__name__}`. You should only use this decorator for valid test methods of a "
|
|
70
|
+
f"`Scenario` object")
|
|
79
71
|
|
|
80
72
|
setattr(owner, name, self.func)
|
|
81
73
|
|
|
@@ -9,7 +9,7 @@ import traceback
|
|
|
9
9
|
from abc import ABC, abstractmethod
|
|
10
10
|
|
|
11
11
|
from _balder.executor.basic_executor import BasicExecutor
|
|
12
|
-
from _balder.testresult import ResultState
|
|
12
|
+
from _balder.testresult import ResultState
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
15
15
|
from _balder.fixture_execution_level import FixtureExecutionLevel
|
|
@@ -73,9 +73,11 @@ class BasicExecutableExecutor(BasicExecutor, ABC):
|
|
|
73
73
|
"""
|
|
74
74
|
if value not in (ResultState.SKIP, ResultState.COVERED_BY, ResultState.NOT_RUN):
|
|
75
75
|
raise ValueError("can not set a state that is not NOT_RUN, SKIP or COVERED_BY for a whole branch")
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
if self.all_child_executors is None:
|
|
77
|
+
self.body_result.set_result(result=value, exception=None)
|
|
78
|
+
else:
|
|
79
|
+
for cur_child_executor in self.all_child_executors:
|
|
80
|
+
cur_child_executor.set_result_for_whole_branch(value)
|
|
79
81
|
|
|
80
82
|
@abstractmethod
|
|
81
83
|
def cleanup_empty_executor_branches(self, consider_discarded=False):
|
|
@@ -90,8 +92,9 @@ class BasicExecutableExecutor(BasicExecutor, ABC):
|
|
|
90
92
|
"""
|
|
91
93
|
This method calls all user defined filters that are to be applied to the executor tree.
|
|
92
94
|
"""
|
|
93
|
-
|
|
94
|
-
cur_child_executor.
|
|
95
|
+
if self.all_child_executors:
|
|
96
|
+
for cur_child_executor in self.all_child_executors:
|
|
97
|
+
cur_child_executor.filter_tree_for_user_filters()
|
|
95
98
|
|
|
96
99
|
def execute(self, show_discarded=False):
|
|
97
100
|
"""
|
|
@@ -105,6 +108,8 @@ class BasicExecutableExecutor(BasicExecutor, ABC):
|
|
|
105
108
|
if self.has_runnable_tests():
|
|
106
109
|
self.fixture_manager.enter(self)
|
|
107
110
|
self.construct_result.set_result(ResultState.SUCCESS)
|
|
111
|
+
else:
|
|
112
|
+
self.construct_result.set_result(ResultState.NOT_RUN)
|
|
108
113
|
|
|
109
114
|
self._body_execution(show_discarded=show_discarded)
|
|
110
115
|
except Exception as exc: # pylint: disable=broad-exception-caught
|
|
@@ -116,6 +121,8 @@ class BasicExecutableExecutor(BasicExecutor, ABC):
|
|
|
116
121
|
if self.fixture_manager.is_allowed_to_leave(self):
|
|
117
122
|
self.fixture_manager.leave(self)
|
|
118
123
|
self.teardown_result.set_result(ResultState.SUCCESS)
|
|
124
|
+
else:
|
|
125
|
+
self.teardown_result.set_result(ResultState.NOT_RUN)
|
|
119
126
|
except Exception as exc: # pylint: disable=broad-exception-caught
|
|
120
127
|
# this has to be a teardown fixture error
|
|
121
128
|
traceback.print_exception(*sys.exc_info())
|
|
@@ -38,7 +38,7 @@ class BasicExecutor(ABC):
|
|
|
38
38
|
|
|
39
39
|
@property
|
|
40
40
|
@abstractmethod
|
|
41
|
-
def all_child_executors(self) ->
|
|
41
|
+
def all_child_executors(self) -> list[BasicExecutor] | None:
|
|
42
42
|
"""
|
|
43
43
|
returns all child executors of this object or None if no child executors can exist (this element is a leaf)
|
|
44
44
|
"""
|
|
@@ -115,6 +115,32 @@ class BasicExecutor(ABC):
|
|
|
115
115
|
return False
|
|
116
116
|
return True
|
|
117
117
|
|
|
118
|
+
def has_skipped_tests(self) -> bool:
|
|
119
|
+
"""
|
|
120
|
+
This method returns true if this executor element has at least one test that is marked to be skipped. The method
|
|
121
|
+
returns true if minimum one of its children has `prev_mark=SKIP`.
|
|
122
|
+
"""
|
|
123
|
+
if self.all_child_executors is not None:
|
|
124
|
+
# the executor has child executors -> check them
|
|
125
|
+
for cur_child in self.all_child_executors:
|
|
126
|
+
if cur_child.has_skipped_tests():
|
|
127
|
+
return True
|
|
128
|
+
return False
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
def has_covered_by_tests(self) -> bool:
|
|
132
|
+
"""
|
|
133
|
+
This method returns true if this executor element has at least one test that is marked as covered-by. The method
|
|
134
|
+
returns true if minimum one of its children has `prev_mark=COVERED_BY`.
|
|
135
|
+
"""
|
|
136
|
+
if self.all_child_executors is not None:
|
|
137
|
+
# the executor has child executors -> check them
|
|
138
|
+
for cur_child in self.all_child_executors:
|
|
139
|
+
if cur_child.has_covered_by_tests():
|
|
140
|
+
return True
|
|
141
|
+
return False
|
|
142
|
+
return False
|
|
143
|
+
|
|
118
144
|
def get_all_base_instances_of_this_branch(
|
|
119
145
|
self, with_type: Union[Type[Setup], Type[Scenario], Type[types.FunctionType]],
|
|
120
146
|
only_runnable_elements: bool = True) -> List[Union[Setup, Scenario, object]]:
|
|
@@ -159,8 +185,9 @@ class BasicExecutor(ABC):
|
|
|
159
185
|
"""
|
|
160
186
|
This method calls all user defined filters that are to be applied to the executor tree.
|
|
161
187
|
"""
|
|
162
|
-
|
|
163
|
-
cur_child_executor.
|
|
188
|
+
if self.all_child_executors:
|
|
189
|
+
for cur_child_executor in self.all_child_executors:
|
|
190
|
+
cur_child_executor.filter_tree_for_user_filters()
|
|
164
191
|
|
|
165
192
|
def testsummary(self) -> ResultSummary:
|
|
166
193
|
"""
|
|
@@ -172,7 +199,7 @@ class BasicExecutor(ABC):
|
|
|
172
199
|
|
|
173
200
|
if isinstance(self.body_result, TestcaseResult):
|
|
174
201
|
setattr(summary, self.executor_result.value, 1)
|
|
175
|
-
|
|
202
|
+
elif self.all_child_executors:
|
|
176
203
|
for cur_child_exec in self.all_child_executors:
|
|
177
204
|
summary += cur_child_exec.testsummary()
|
|
178
205
|
return summary
|
|
@@ -38,7 +38,7 @@ class ExecutorTree(BasicExecutableExecutor):
|
|
|
38
38
|
# ---------------------------------- PROPERTIES --------------------------------------------------------------------
|
|
39
39
|
|
|
40
40
|
@property
|
|
41
|
-
def all_child_executors(self) -> List[BasicExecutableExecutor]:
|
|
41
|
+
def all_child_executors(self) -> List[BasicExecutableExecutor] | None:
|
|
42
42
|
return self._setup_executors
|
|
43
43
|
|
|
44
44
|
@property
|
|
@@ -63,11 +63,12 @@ class ExecutorTree(BasicExecutableExecutor):
|
|
|
63
63
|
|
|
64
64
|
def _body_execution(self, show_discarded):
|
|
65
65
|
for cur_setup_executor in self.get_setup_executors():
|
|
66
|
-
|
|
66
|
+
prev_mark = cur_setup_executor.prev_mark
|
|
67
|
+
if cur_setup_executor.has_runnable_tests(show_discarded) or cur_setup_executor.has_skipped_tests():
|
|
67
68
|
cur_setup_executor.execute(show_discarded=show_discarded)
|
|
68
|
-
elif
|
|
69
|
+
elif prev_mark == PreviousExecutorMark.SKIP:
|
|
69
70
|
cur_setup_executor.set_result_for_whole_branch(ResultState.SKIP)
|
|
70
|
-
elif
|
|
71
|
+
elif prev_mark == PreviousExecutorMark.COVERED_BY:
|
|
71
72
|
cur_setup_executor.set_result_for_whole_branch(ResultState.COVERED_BY)
|
|
72
73
|
else:
|
|
73
74
|
cur_setup_executor.set_result_for_whole_branch(ResultState.NOT_RUN)
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
from typing import Any, TYPE_CHECKING
|
|
3
3
|
|
|
4
4
|
from collections import OrderedDict
|
|
5
|
-
from _balder.utils import
|
|
5
|
+
from _balder.utils.functions import get_method_type
|
|
6
6
|
from .testcase_executor import TestcaseExecutor
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
@@ -39,7 +39,7 @@ class ParametrizedTestcaseExecutor(TestcaseExecutor):
|
|
|
39
39
|
return super().full_test_name_str
|
|
40
40
|
|
|
41
41
|
def get_all_test_method_args(self):
|
|
42
|
-
|
|
42
|
+
func_type = get_method_type(self.base_testcase_obj.__class__, self.base_testcase_callable)
|
|
43
43
|
all_kwargs = self.fixture_manager.get_all_attribute_values(
|
|
44
44
|
self,
|
|
45
45
|
self.base_testcase_obj.__class__,
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
from typing import Type, Union, List,
|
|
2
|
+
from typing import Type, Union, List, TYPE_CHECKING
|
|
3
3
|
|
|
4
4
|
from _balder.fixture_execution_level import FixtureExecutionLevel
|
|
5
5
|
from _balder.testresult import ResultState, BranchBodyResult
|
|
6
|
-
from _balder.utils import get_class_that_defines_method
|
|
7
6
|
from _balder.executor.basic_executable_executor import BasicExecutableExecutor
|
|
8
7
|
from _balder.executor.variation_executor import VariationExecutor
|
|
9
8
|
from _balder.previous_executor_mark import PreviousExecutorMark
|
|
@@ -78,19 +77,19 @@ class ScenarioExecutor(BasicExecutableExecutor):
|
|
|
78
77
|
return self._fixture_manager
|
|
79
78
|
|
|
80
79
|
@property
|
|
81
|
-
def all_run_tests(self):
|
|
80
|
+
def all_run_tests(self) -> List[callable]:
|
|
82
81
|
"""returns a list of all test methods that are declared to `RUN` in their base :class:`Scenario` class"""
|
|
83
|
-
return self.
|
|
82
|
+
return self.base_scenario_controller.get_run_test_methods()
|
|
84
83
|
|
|
85
84
|
@property
|
|
86
|
-
def all_skip_tests(self):
|
|
85
|
+
def all_skip_tests(self) -> List[callable]:
|
|
87
86
|
"""returns a list of all test methods that are declared to `SKIP` in their base :class:`Scenario` class"""
|
|
88
|
-
return self.
|
|
87
|
+
return self.base_scenario_controller.get_skip_test_methods()
|
|
89
88
|
|
|
90
89
|
@property
|
|
91
|
-
def all_ignore_tests(self):
|
|
90
|
+
def all_ignore_tests(self) -> List[callable]:
|
|
92
91
|
"""returns a list of all test methods that are declared to `IGNORE` in their base :class:`Scenario` class"""
|
|
93
|
-
return self.
|
|
92
|
+
return self.base_scenario_controller.get_ignore_test_methods()
|
|
94
93
|
|
|
95
94
|
# ---------------------------------- PROTECTED METHODS -------------------------------------------------------------
|
|
96
95
|
|
|
@@ -99,11 +98,12 @@ class ScenarioExecutor(BasicExecutableExecutor):
|
|
|
99
98
|
|
|
100
99
|
def _body_execution(self, show_discarded):
|
|
101
100
|
for cur_variation_executor in self.get_variation_executors(return_discarded=show_discarded):
|
|
102
|
-
|
|
101
|
+
prev_mark = cur_variation_executor.prev_mark
|
|
102
|
+
if cur_variation_executor.has_runnable_tests() or cur_variation_executor.has_skipped_tests():
|
|
103
103
|
cur_variation_executor.execute(show_discarded=show_discarded)
|
|
104
|
-
elif
|
|
104
|
+
elif prev_mark == PreviousExecutorMark.SKIP:
|
|
105
105
|
cur_variation_executor.set_result_for_whole_branch(ResultState.SKIP)
|
|
106
|
-
elif
|
|
106
|
+
elif prev_mark == PreviousExecutorMark.COVERED_BY:
|
|
107
107
|
cur_variation_executor.set_result_for_whole_branch(ResultState.COVERED_BY)
|
|
108
108
|
else:
|
|
109
109
|
cur_variation_executor.set_result_for_whole_branch(ResultState.NOT_RUN)
|
|
@@ -136,71 +136,12 @@ class ScenarioExecutor(BasicExecutableExecutor):
|
|
|
136
136
|
for cur_variation_executor in to_remove_executor:
|
|
137
137
|
self._variation_executors.remove(cur_variation_executor)
|
|
138
138
|
|
|
139
|
-
def get_covered_by_dict(self) -> Dict[Union[Type[Scenario], callable], List[Union[Type[Scenario], callable]]]:
|
|
140
|
-
"""
|
|
141
|
-
This method returns the complete resolved ``@covered_by`` dictionary for this scenario. It automatically
|
|
142
|
-
cleans up every inheritance of the covered_by decorators for every parent class of our scenario.
|
|
143
|
-
"""
|
|
144
|
-
def determine_most_inherited_class(class_list):
|
|
145
|
-
for cur_candidate in class_list:
|
|
146
|
-
candidate_is_valid = True
|
|
147
|
-
for cur_other_candidate in class_list:
|
|
148
|
-
if cur_candidate == cur_other_candidate:
|
|
149
|
-
pass
|
|
150
|
-
if not issubclass(cur_candidate, cur_other_candidate):
|
|
151
|
-
candidate_is_valid = False
|
|
152
|
-
break
|
|
153
|
-
if candidate_is_valid:
|
|
154
|
-
return cur_candidate
|
|
155
|
-
return None
|
|
156
|
-
|
|
157
|
-
# all data will be inherited while ``@covered_by`` overwrites elements only if there is a new decorator at the
|
|
158
|
-
# overwritten method
|
|
159
|
-
# -> we have to filter the dictionary and only return the value given for highest overwritten method
|
|
160
|
-
relative_covered_by_dict = {}
|
|
161
|
-
if hasattr(self.base_scenario_class, '_covered_by'):
|
|
162
|
-
function_name_mapping = {}
|
|
163
|
-
classes = []
|
|
164
|
-
for cur_key in self.base_scenario_class._covered_by.keys():
|
|
165
|
-
if issubclass(cur_key, Scenario):
|
|
166
|
-
# this is a covered_by definition for the whole class
|
|
167
|
-
classes.append(cur_key)
|
|
168
|
-
else:
|
|
169
|
-
# this is a covered_by definition for one test method
|
|
170
|
-
if cur_key.__name__ in function_name_mapping.keys():
|
|
171
|
-
function_name_mapping[cur_key.__name__] = [cur_key]
|
|
172
|
-
else:
|
|
173
|
-
function_name_mapping[cur_key.__name__].append(cur_key)
|
|
174
|
-
|
|
175
|
-
# determine the highest definition for class statement (only if necessary)
|
|
176
|
-
if len(classes) > 0:
|
|
177
|
-
most_inherited_class = determine_most_inherited_class(classes)
|
|
178
|
-
# this is the most inherited child -> add this definition
|
|
179
|
-
relative_covered_by_dict[most_inherited_class] = \
|
|
180
|
-
self.base_scenario_class._covered_by[most_inherited_class]
|
|
181
|
-
|
|
182
|
-
# determine the highest definition for every test method
|
|
183
|
-
for cur_function_name, cur_possible_candidates in function_name_mapping.items():
|
|
184
|
-
classes = [get_class_that_defines_method(meth) for meth in cur_possible_candidates]
|
|
185
|
-
most_inherited_class = determine_most_inherited_class(classes)
|
|
186
|
-
most_inherited_test_method = cur_possible_candidates[classes.index(most_inherited_class)]
|
|
187
|
-
# this is the most inherited test method -> add the definition of this one and replace the method with
|
|
188
|
-
# this Scenario's one
|
|
189
|
-
relative_covered_by_dict[getattr(self.base_scenario_class, cur_function_name)] = \
|
|
190
|
-
self.base_scenario_class._covered_by[most_inherited_test_method]
|
|
191
|
-
else:
|
|
192
|
-
pass
|
|
193
|
-
return relative_covered_by_dict
|
|
194
|
-
|
|
195
139
|
def get_covered_by_element(self) -> List[Union[Scenario, callable]]:
|
|
196
140
|
"""
|
|
197
141
|
This method returns a list of elements where the whole scenario is covered from. This means, that the whole
|
|
198
142
|
test methods in this scenario are already be covered from one of the elements in the list.
|
|
199
143
|
"""
|
|
200
|
-
|
|
201
|
-
if self in covered_by_dict_resolved.keys():
|
|
202
|
-
return covered_by_dict_resolved[self]
|
|
203
|
-
return []
|
|
144
|
+
return self.base_scenario_controller.get_abs_covered_by_dict().get(None, [])
|
|
204
145
|
|
|
205
146
|
def add_variation_executor(self, variation_executor: VariationExecutor):
|
|
206
147
|
"""
|
|
@@ -80,11 +80,12 @@ class SetupExecutor(BasicExecutableExecutor):
|
|
|
80
80
|
|
|
81
81
|
def _body_execution(self, show_discarded):
|
|
82
82
|
for cur_scenario_executor in self.get_scenario_executors():
|
|
83
|
-
|
|
83
|
+
prev_mark = cur_scenario_executor.prev_mark
|
|
84
|
+
if cur_scenario_executor.has_runnable_tests(show_discarded) or cur_scenario_executor.has_skipped_tests():
|
|
84
85
|
cur_scenario_executor.execute(show_discarded=show_discarded)
|
|
85
|
-
elif
|
|
86
|
+
elif prev_mark == PreviousExecutorMark.SKIP:
|
|
86
87
|
cur_scenario_executor.set_result_for_whole_branch(ResultState.SKIP)
|
|
87
|
-
elif
|
|
88
|
+
elif prev_mark == PreviousExecutorMark.COVERED_BY:
|
|
88
89
|
cur_scenario_executor.set_result_for_whole_branch(ResultState.COVERED_BY)
|
|
89
90
|
else:
|
|
90
91
|
cur_scenario_executor.set_result_for_whole_branch(ResultState.NOT_RUN)
|