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.
Files changed (40) hide show
  1. _balder/_version.py +8 -3
  2. _balder/cnnrelations/and_connection_relation.py +28 -1
  3. _balder/cnnrelations/base_connection_relation.py +1 -1
  4. _balder/collector.py +101 -113
  5. _balder/connection.py +76 -80
  6. _balder/controllers/device_controller.py +5 -24
  7. _balder/controllers/feature_controller.py +2 -2
  8. _balder/controllers/normal_scenario_setup_controller.py +1 -2
  9. _balder/controllers/scenario_controller.py +121 -0
  10. _balder/controllers/vdevice_controller.py +2 -23
  11. _balder/decorator_connect.py +3 -3
  12. _balder/decorator_covered_by.py +28 -36
  13. _balder/executor/basic_executable_executor.py +13 -6
  14. _balder/executor/basic_executor.py +31 -4
  15. _balder/executor/executor_tree.py +5 -4
  16. _balder/executor/parametrized_testcase_executor.py +2 -2
  17. _balder/executor/scenario_executor.py +12 -71
  18. _balder/executor/setup_executor.py +4 -3
  19. _balder/executor/testcase_executor.py +35 -19
  20. _balder/executor/unresolved_parametrized_testcase_executor.py +32 -57
  21. _balder/executor/variation_executor.py +21 -48
  22. _balder/feature.py +2 -1
  23. _balder/feature_replacement_mapping.py +39 -1
  24. _balder/fixture_manager.py +1 -1
  25. _balder/fixture_metadata.py +1 -1
  26. _balder/routing_path.py +18 -10
  27. _balder/scenario.py +2 -2
  28. _balder/setup.py +2 -1
  29. _balder/testresult.py +4 -3
  30. _balder/utils/__init__.py +0 -0
  31. _balder/{utils.py → utils/functions.py} +29 -31
  32. _balder/utils/inner_device_managing_metaclass.py +14 -0
  33. _balder/utils/mixin_can_be_covered_by_executor.py +24 -0
  34. _balder/utils/typings.py +4 -0
  35. {baldertest-0.1.0b12.dist-info → baldertest-0.1.0b13.dist-info}/METADATA +3 -2
  36. {baldertest-0.1.0b12.dist-info → baldertest-0.1.0b13.dist-info}/RECORD +40 -36
  37. {baldertest-0.1.0b12.dist-info → baldertest-0.1.0b13.dist-info}/WHEEL +1 -1
  38. {baldertest-0.1.0b12.dist-info → baldertest-0.1.0b13.dist-info}/entry_points.txt +0 -0
  39. {baldertest-0.1.0b12.dist-info → baldertest-0.1.0b13.dist-info/licenses}/LICENSE +0 -0
  40. {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
- outer_class_name, _ = cur_class.__qualname__.split('.')
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 DeviceScopeError, VDeviceResolvingError
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
  """
@@ -69,10 +69,10 @@ def connect(
69
69
  nonlocal self_node_name
70
70
  nonlocal dest_node_name
71
71
 
72
- this_outer_class_name, _ = decorated_cls.__qualname__.split('.')
72
+ this_outer_ref = decorated_cls.__qualname__.split('.')[:-1]
73
73
  if not isinstance(with_device, str):
74
- other_outer_class_name, _ = with_device.__qualname__.split('.')
75
- if this_outer_class_name != other_outer_class_name:
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__}`")
@@ -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 callable(item) and inspect.isfunction(item) and item.__name__.startswith("test_") and \
20
- issubclass(get_class_that_defines_method(item), Scenario):
20
+ elif isinstance(item, str) and item.startswith("test_"):
21
21
  pass
22
- elif isinstance(item, type) and issubclass(item, Scenario):
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 `Scenario` (or a subclass thereof) or a test method of "
26
- "a scenario class (has to start with `test_`)")
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
- if not hasattr(func, '_covered_by'):
40
- func._covered_by = {}
41
- if func not in func._covered_by.keys():
42
- func._covered_by[func] = []
43
- if item is None:
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 `Scenario` elements or test method elements "
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 `Scenario` objects and "
60
- "test methods of `Scenario` objects")
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 `Scenario` objects "
63
- f"and test methods of `Scenario` objects - the method `{owner.__name__}.{name}` "
64
- f"does not start with `test_` and is not a valid test method")
65
-
66
- if not hasattr(owner, '_covered_by'):
67
- owner._covered_by = {}
68
- if self.func not in owner._covered_by.keys():
69
- owner._covered_by[self.func] = []
70
- if item is None:
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 `Scenario` objects or "
78
- f"valid test methods of a `Scenario` object")
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, TestcaseResult
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
- for cur_child_executor in self.all_child_executors:
77
- if isinstance(cur_child_executor.body_result, TestcaseResult):
78
- cur_child_executor.body_result.set_result(result=value, exception=None)
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
- for cur_child_executor in self.all_child_executors:
94
- cur_child_executor.filter_tree_for_user_filters()
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) -> List[BasicExecutor]:
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
- for cur_child_executor in self.all_child_executors:
163
- cur_child_executor.filter_tree_for_user_filters()
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
- else:
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
- if cur_setup_executor.has_runnable_tests(consider_discarded_too=show_discarded):
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 cur_setup_executor.prev_mark == PreviousExecutorMark.SKIP:
69
+ elif prev_mark == PreviousExecutorMark.SKIP:
69
70
  cur_setup_executor.set_result_for_whole_branch(ResultState.SKIP)
70
- elif cur_setup_executor.prev_mark == PreviousExecutorMark.COVERED_BY:
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 inspect_method
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
- _, func_type = inspect_method(self.base_testcase_callable)
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, Dict, TYPE_CHECKING
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._base_scenario_class.RUN
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._base_scenario_class.SKIP
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._base_scenario_class.IGNORE
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
- if cur_variation_executor.has_runnable_tests(show_discarded):
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 cur_variation_executor.prev_mark == PreviousExecutorMark.SKIP:
104
+ elif prev_mark == PreviousExecutorMark.SKIP:
105
105
  cur_variation_executor.set_result_for_whole_branch(ResultState.SKIP)
106
- elif cur_variation_executor.prev_mark == PreviousExecutorMark.COVERED_BY:
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
- covered_by_dict_resolved = self.get_covered_by_dict()
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
- if cur_scenario_executor.has_runnable_tests(consider_discarded_too=show_discarded):
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 cur_scenario_executor.prev_mark == PreviousExecutorMark.SKIP:
86
+ elif prev_mark == PreviousExecutorMark.SKIP:
86
87
  cur_scenario_executor.set_result_for_whole_branch(ResultState.SKIP)
87
- elif cur_scenario_executor.prev_mark == PreviousExecutorMark.COVERED_BY:
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)