baldertest 0.1.0b12__py3-none-any.whl → 0.1.0b14__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 +9 -4
  2. _balder/cnnrelations/and_connection_relation.py +28 -1
  3. _balder/cnnrelations/base_connection_relation.py +1 -1
  4. _balder/collector.py +130 -119
  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.0b14.dist-info}/METADATA +3 -2
  36. {baldertest-0.1.0b12.dist-info → baldertest-0.1.0b14.dist-info}/RECORD +40 -36
  37. {baldertest-0.1.0b12.dist-info → baldertest-0.1.0b14.dist-info}/WHEEL +1 -1
  38. {baldertest-0.1.0b12.dist-info → baldertest-0.1.0b14.dist-info}/entry_points.txt +0 -0
  39. {baldertest-0.1.0b12.dist-info → baldertest-0.1.0b14.dist-info/licenses}/LICENSE +0 -0
  40. {baldertest-0.1.0b12.dist-info → baldertest-0.1.0b14.dist-info}/top_level.txt +0 -0
@@ -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)
@@ -9,15 +9,17 @@ from _balder.executor.basic_executable_executor import BasicExecutableExecutor
9
9
  from _balder.fixture_execution_level import FixtureExecutionLevel
10
10
  from _balder.previous_executor_mark import PreviousExecutorMark
11
11
  from _balder.testresult import ResultState, TestcaseResult
12
- from _balder.utils import inspect_method
12
+ from _balder.utils.functions import get_method_type
13
+ from _balder.utils.mixin_can_be_covered_by_executor import MixinCanBeCoveredByExecutor
13
14
 
14
15
  if TYPE_CHECKING:
16
+ from _balder.executor.scenario_executor import ScenarioExecutor
15
17
  from _balder.executor.variation_executor import VariationExecutor
16
18
  from _balder.fixture_manager import FixtureManager
17
19
  from _balder.scenario import Scenario
18
20
 
19
21
 
20
- class TestcaseExecutor(BasicExecutableExecutor):
22
+ class TestcaseExecutor(BasicExecutableExecutor, MixinCanBeCoveredByExecutor):
21
23
  """
22
24
  A TestcaseExecutor class represents an actual single test that can be executed. It therefore references exactly to a
23
25
  test method of a scenario that can be executed on the specific setup this executor belongs to.
@@ -65,6 +67,10 @@ class TestcaseExecutor(BasicExecutableExecutor):
65
67
  def parent_executor(self) -> VariationExecutor:
66
68
  return self._parent_executor
67
69
 
70
+ @property
71
+ def scenario_executor(self) -> ScenarioExecutor:
72
+ return self.parent_executor.parent_executor
73
+
68
74
  @property
69
75
  def base_instance(self) -> object:
70
76
  """
@@ -99,15 +105,23 @@ class TestcaseExecutor(BasicExecutableExecutor):
99
105
 
100
106
  def _prepare_execution(self, show_discarded):
101
107
  print(f" TEST {self.full_test_name_str} ", end='')
108
+
109
+ def _body_execution(self, show_discarded):
110
+ self.test_execution_time_sec = 0
111
+
102
112
  if self.should_be_skipped():
103
113
  self.body_result.set_result(ResultState.SKIP)
104
- self.execution_time_sec = 0
105
- print("[S]")
114
+ return
115
+ if self.should_be_ignored():
116
+ self.body_result.set_result(ResultState.NOT_RUN)
117
+ return
118
+ if self.is_covered_by():
119
+ self.body_result.set_result(ResultState.COVERED_BY)
120
+ return
106
121
 
107
- def _body_execution(self, show_discarded):
108
122
  start_time = time.perf_counter()
109
123
  try:
110
- _, func_type = inspect_method(self.base_testcase_callable)
124
+ func_type = get_method_type(self.base_testcase_obj.__class__, self.base_testcase_callable)
111
125
  all_args = self.get_all_test_method_args()
112
126
  if func_type == "staticmethod":
113
127
  # testcase is a staticmethod - no special first attribute
@@ -151,6 +165,16 @@ class TestcaseExecutor(BasicExecutableExecutor):
151
165
  return True
152
166
  return False
153
167
 
168
+ def is_covered_by(self):
169
+ """returns true if the testcase is covered-by"""
170
+ return self.prev_mark == PreviousExecutorMark.COVERED_BY
171
+
172
+ def has_skipped_tests(self) -> bool:
173
+ return self.prev_mark == PreviousExecutorMark.SKIP
174
+
175
+ def has_covered_by_tests(self) -> bool:
176
+ return self.prev_mark == PreviousExecutorMark.COVERED_BY
177
+
154
178
  def cleanup_empty_executor_branches(self, consider_discarded=False):
155
179
  """
156
180
  This method searches the whole tree and removes branches where an executor item has no own children. It can
@@ -160,25 +184,17 @@ class TestcaseExecutor(BasicExecutableExecutor):
160
184
  """
161
185
 
162
186
  def get_covered_by_element(self) -> List[Union[Scenario, callable]]:
163
- """
164
- This method returns a list of elements where the whole scenario is covered from. This means, that the whole
165
- test methods in this scenario are already be covered from every single element in the list.
166
- """
167
- all_covered_by_data = []
168
- scenario_executor = self.parent_executor.parent_executor
169
- scenario_class = scenario_executor.base_scenario_class
170
- covered_by_dict_resolved = scenario_executor.get_covered_by_dict()
171
- if self.base_testcase_callable in covered_by_dict_resolved.keys():
172
- all_covered_by_data += covered_by_dict_resolved[self.base_testcase_callable]
173
- if scenario_class in covered_by_dict_resolved.keys():
174
- all_covered_by_data += covered_by_dict_resolved[scenario_class]
187
+ covered_by_dict = self.scenario_executor.base_scenario_controller.get_abs_covered_by_dict()
188
+ all_covered_by_data = covered_by_dict.get(self.base_testcase_callable.__name__, [])
189
+ # also add all scenario specified covered-by elements
190
+ all_covered_by_data.extend(covered_by_dict.get(None, []))
175
191
  return all_covered_by_data
176
192
 
177
193
  def get_all_test_method_args(self):
178
194
  """
179
195
  returns all kwargs values for the test method
180
196
  """
181
- _, func_type = inspect_method(self.base_testcase_callable)
197
+ func_type = get_method_type(self.base_testcase_obj.__class__, self.base_testcase_callable)
182
198
  return self.fixture_manager.get_all_attribute_values(
183
199
  self,
184
200
  self.base_testcase_obj.__class__,
@@ -11,14 +11,16 @@ from _balder.executor.parametrized_testcase_executor import ParametrizedTestcase
11
11
  from _balder.parametrization import Parameter
12
12
  from _balder.previous_executor_mark import PreviousExecutorMark
13
13
  from _balder.testresult import BranchBodyResult
14
+ from _balder.utils.mixin_can_be_covered_by_executor import MixinCanBeCoveredByExecutor
14
15
 
15
16
  if TYPE_CHECKING:
17
+ from _balder.executor.scenario_executor import ScenarioExecutor
16
18
  from _balder.executor.variation_executor import VariationExecutor
17
19
  from _balder.scenario import Scenario
18
20
  from _balder.setup import Setup
19
21
 
20
22
 
21
- class UnresolvedParametrizedTestcaseExecutor(BasicExecutor):
23
+ class UnresolvedParametrizedTestcaseExecutor(BasicExecutor, MixinCanBeCoveredByExecutor):
22
24
  """
23
25
  This executor class represents a group of dynamically parametrized tests.
24
26
  """
@@ -37,9 +39,6 @@ class UnresolvedParametrizedTestcaseExecutor(BasicExecutor):
37
39
  # holds the specific static parameters for this unresolved group
38
40
  self._static_parameters = static_parameters if static_parameters is not None else {}
39
41
 
40
- # holds the dynamically created testcase executors as soon as this executor is entered
41
- self._testcase_executors = None
42
-
43
42
  # contains the result object for the BODY part of this branch
44
43
  self.body_result = BranchBodyResult(self)
45
44
 
@@ -47,14 +46,18 @@ class UnresolvedParametrizedTestcaseExecutor(BasicExecutor):
47
46
  def parent_executor(self) -> VariationExecutor:
48
47
  return self._parent_executor
49
48
 
49
+ @property
50
+ def scenario_executor(self) -> ScenarioExecutor:
51
+ return self.parent_executor.parent_executor
52
+
50
53
  @property
51
54
  def base_testcase_callable(self) -> callable:
52
55
  """returns the testcase function"""
53
56
  return self._base_testcase_callable
54
57
 
55
58
  @property
56
- def all_child_executors(self) -> List[ParametrizedTestcaseExecutor]:
57
- return self.get_testcase_executors()
59
+ def all_child_executors(self) -> None:
60
+ return None
58
61
 
59
62
  @property
60
63
  def base_testcase_obj(self) -> Scenario:
@@ -69,29 +72,11 @@ class UnresolvedParametrizedTestcaseExecutor(BasicExecutor):
69
72
  returns the base class instance to which this executor instance belongs"""
70
73
  return self._base_testcase_callable
71
74
 
72
- @property
73
- def parametrization_has_been_resolved(self) -> bool:
74
- """returns true if the parametrization has been resolved"""
75
- return self._testcase_executors is not None
76
-
77
- def has_runnable_tests(self, consider_discarded_too=False) -> bool:
78
- """
79
- This method returns true if this executor element is runnable. The method returns true if this element has
80
- `prev_mark=RUNNABLE` and minimum one of its children has `prev_mark=RUNNABLE` too.
81
-
82
- :param consider_discarded_too: True if the method allows DISCARDED elements too
83
- """
84
- if self.parametrization_has_been_resolved:
85
- return super().has_runnable_tests(consider_discarded_too=consider_discarded_too)
75
+ def has_skipped_tests(self) -> bool:
76
+ return self.prev_mark == PreviousExecutorMark.SKIP
86
77
 
87
- allowed_prev_marks = [PreviousExecutorMark.RUNNABLE]
88
-
89
- if consider_discarded_too:
90
- allowed_prev_marks.append(PreviousExecutorMark.DISCARDED)
91
-
92
- if self.prev_mark not in allowed_prev_marks:
93
- return False
94
- return True
78
+ def has_covered_by_tests(self) -> bool:
79
+ return self.prev_mark == PreviousExecutorMark.COVERED_BY
95
80
 
96
81
  def get_all_base_instances_of_this_branch(
97
82
  self, with_type: Type[Setup] | Type[Scenario] | Type[types.FunctionType],
@@ -105,43 +90,33 @@ class UnresolvedParametrizedTestcaseExecutor(BasicExecutor):
105
90
  return []
106
91
 
107
92
  def get_covered_by_element(self) -> List[Scenario | callable]:
108
- """
109
- This method returns a list of elements where the whole scenario is covered from. This means, that the whole
110
- test methods in this scenario are already be covered from every single element in the list.
111
- """
112
- all_covered_by_data = []
113
- scenario_executor = self.parent_executor.parent_executor
114
- scenario_class = scenario_executor.base_scenario_class
115
- covered_by_dict_resolved = scenario_executor.get_covered_by_dict()
116
- if self.base_testcase_callable in covered_by_dict_resolved.keys():
117
- all_covered_by_data += covered_by_dict_resolved[self.base_testcase_callable]
118
- if scenario_class in covered_by_dict_resolved.keys():
119
- all_covered_by_data += covered_by_dict_resolved[scenario_class]
93
+ covered_by_dict = self.scenario_executor.base_scenario_controller.get_abs_covered_by_dict()
94
+ all_covered_by_data = covered_by_dict.get(self.base_testcase_callable.__name__, [])
95
+ # also add all scenario specified covered-by elements
96
+ all_covered_by_data.extend(covered_by_dict.get(None, []))
120
97
  return all_covered_by_data
121
98
 
122
- def get_testcase_executors(self) -> List[ParametrizedTestcaseExecutor | UnresolvedParametrizedTestcaseExecutor]:
123
- """returns all sub testcase executors that belongs to this variation-executor"""
124
- if self._testcase_executors is None:
125
- return [self]
126
- return self._testcase_executors
127
-
128
- def resolve_dynamic_parametrization(self):
99
+ def get_resolved_parametrized_testcase_executors(self):
129
100
  """
130
101
  resolves the dynamic parametrization - should be called when setup features are active in the scenario
131
102
  """
132
- self._testcase_executors = []
103
+ executors = []
133
104
 
134
105
  parametrization = self.get_parametrization()
135
- if parametrization:
136
- for cur_parametrization in parametrization:
137
- self._testcase_executors.append(
138
- ParametrizedTestcaseExecutor(
139
- self._base_testcase_callable,
140
- parent=self.parent_executor,
141
- parametrization=cur_parametrization,
142
- unresolved_group_obj=self
143
- )
106
+
107
+ if not parametrization:
108
+ return []
109
+
110
+ for cur_parametrization in parametrization:
111
+ executors.append(
112
+ ParametrizedTestcaseExecutor(
113
+ self._base_testcase_callable,
114
+ parent=self.parent_executor,
115
+ parametrization=cur_parametrization,
116
+ unresolved_group_obj=self
144
117
  )
118
+ )
119
+ return executors
145
120
 
146
121
  def get_parametrization(self) -> List[OrderedDict[str, Any]] | None:
147
122
  """
@@ -171,7 +171,7 @@ class VariationExecutor(BasicExecutableExecutor):
171
171
  self.exchange_unmapped_vdevice_references()
172
172
  self.update_vdevice_referenced_feature_instances()
173
173
  self.set_conn_dependent_methods()
174
- self.resolve_possible_parametrization()
174
+ self.resolve_and_exchange_unresolved_parametrization()
175
175
 
176
176
  def _body_execution(self, show_discarded):
177
177
  if show_discarded and not self.can_be_applied():
@@ -179,13 +179,10 @@ class VariationExecutor(BasicExecutableExecutor):
179
179
  return
180
180
 
181
181
  for cur_testcase_executor in self.get_testcase_executors():
182
- if cur_testcase_executor.has_runnable_tests():
183
-
182
+ if (cur_testcase_executor.has_runnable_tests()
183
+ or cur_testcase_executor.has_skipped_tests()
184
+ or cur_testcase_executor.has_covered_by_tests()):
184
185
  cur_testcase_executor.execute()
185
- elif cur_testcase_executor.prev_mark == PreviousExecutorMark.SKIP:
186
- cur_testcase_executor.set_result_for_whole_branch(ResultState.SKIP)
187
- elif cur_testcase_executor.prev_mark == PreviousExecutorMark.COVERED_BY:
188
- cur_testcase_executor.set_result_for_whole_branch(ResultState.COVERED_BY)
189
186
  else:
190
187
  cur_testcase_executor.set_result_for_whole_branch(ResultState.NOT_RUN)
191
188
 
@@ -335,16 +332,9 @@ class VariationExecutor(BasicExecutableExecutor):
335
332
  return super().testsummary()
336
333
  return ResultSummary()
337
334
 
338
- def get_testcase_executors(self) -> List[TestcaseExecutor]:
335
+ def get_testcase_executors(self) -> List[TestcaseExecutor | UnresolvedParametrizedTestcaseExecutor]:
339
336
  """returns all sub testcase executors that belongs to this variation-executor"""
340
- result = []
341
- for cur_executor in self._testcase_executors:
342
- if (isinstance(cur_executor, UnresolvedParametrizedTestcaseExecutor) and
343
- cur_executor.parametrization_has_been_resolved):
344
- result += cur_executor.get_testcase_executors()
345
- else:
346
- result.append(cur_executor)
347
- return result
337
+ return self._testcase_executors.copy()
348
338
 
349
339
  def add_testcase_executor(self, testcase_executor: TestcaseExecutor | UnresolvedParametrizedTestcaseExecutor):
350
340
  """
@@ -388,12 +378,9 @@ class VariationExecutor(BasicExecutableExecutor):
388
378
  setup_dev: FeatureVDeviceMapping() for setup_dev in self.base_device_mapping.values()
389
379
  }
390
380
  for cur_scenario_device, cur_setup_device in self.base_device_mapping.items():
391
- cur_setup_features = DeviceController.get_for(cur_setup_device).get_all_instantiated_feature_objects()
392
381
 
393
- all_assigned_setup_features = []
394
- cur_scenario_device_features = \
395
- DeviceController.get_for(cur_scenario_device).get_all_instantiated_feature_objects()
396
- for cur_attr_name, cur_scenario_feature_obj in cur_scenario_device_features.items():
382
+ for cur_attr_name, cur_scenario_feature_obj in \
383
+ DeviceController.get_for(cur_scenario_device).get_all_instantiated_feature_objects().items():
397
384
  active_scenario_vdevice, mapped_scenario_device = cur_scenario_feature_obj.active_vdevice_device_mapping
398
385
 
399
386
  cur_setup_feature_objs = self._get_matching_setup_features_for(
@@ -407,13 +394,14 @@ class VariationExecutor(BasicExecutableExecutor):
407
394
  f'`{cur_scenario_device.__name__}`) in setup device `{cur_setup_device.__name__}`')
408
395
  cur_setup_feature_obj = cur_setup_feature_objs[0]
409
396
 
397
+ all_abs_inner_vdevs_of_setup = \
398
+ FeatureController.get_for(cur_setup_feature_obj.__class__).get_abs_inner_vdevice_classes()
399
+ used_setup_vdevice, mapped_setup_device = cur_setup_feature_obj.active_vdevice_device_mapping
400
+
410
401
  if mapped_scenario_device is None:
411
402
  # we have exactly one matching candidate, but also no vDevice mapping
412
403
  # check if the matching candidate has a vDevice mapping
413
- _, mapped_setup_device = cur_setup_feature_obj.active_vdevice_device_mapping
414
- cleanup_feature_controller = FeatureController.get_for(cur_setup_feature_obj.__class__)
415
- if mapped_setup_device is None \
416
- and len(cleanup_feature_controller.get_abs_inner_vdevice_classes()) > 0:
404
+ if mapped_setup_device is None and len(all_abs_inner_vdevs_of_setup) > 0:
417
405
  # there is no vDevice mapping on scenario and no vDevice mapping on setup level, but the
418
406
  # feature defined vDevices -> NOT APPLICABLE
419
407
  logger.warning(
@@ -427,19 +415,14 @@ class VariationExecutor(BasicExecutableExecutor):
427
415
  f'`{cur_scenario_feature_obj.__class__.__name__}` (used by scenario device '
428
416
  f'`{cur_scenario_device.__name__}`) in setup device `{cur_setup_device.__name__}`')
429
417
 
430
- all_assigned_setup_features.append(cur_setup_feature_obj)
431
418
  if cur_attr_name not in feature_replacement[cur_scenario_device].attr_names:
432
- cleanup_feature_controller = FeatureController.get_for(cur_setup_feature_obj.__class__)
433
-
434
- used_setup_vdevice, mapped_setup_device = cur_setup_feature_obj.active_vdevice_device_mapping
435
419
 
436
420
  # if there is a vDevice mapping on scenario level, but not on setup level, so update the
437
421
  # VDevice-Device-Mapping there
438
422
  if mapped_scenario_device is not None and mapped_setup_device is None:
439
423
  # search the equivalent vDevice on setup level and use this one (we had not to check it,
440
424
  # because check was already done in collector-stage)
441
- setup_vdevices = [cur_vdevice for cur_vdevice
442
- in cleanup_feature_controller.get_abs_inner_vdevice_classes()
425
+ setup_vdevices = [cur_vdevice for cur_vdevice in all_abs_inner_vdevs_of_setup
443
426
  if cur_vdevice.__name__ == active_scenario_vdevice.__name__]
444
427
  used_setup_vdevice = setup_vdevices[0]
445
428
  # set the mapping
@@ -463,21 +446,7 @@ class VariationExecutor(BasicExecutableExecutor):
463
446
  scenario_feature=cur_scenario_feature_obj,
464
447
  setup_feature=cur_setup_feature_obj)
465
448
 
466
- # also add all setup features that are not assigned as autonomous features
467
- for cur_setup_feature in cur_setup_features.values():
468
- if cur_setup_feature not in all_assigned_setup_features:
469
- # determine free name
470
- idx = 0
471
- autonomous_name = None
472
- while (autonomous_name is None
473
- or autonomous_name in feature_replacement[cur_scenario_device].attr_names):
474
- autonomous_name = f"_autonomous_feat_{idx}"
475
- idx += 1
476
- feature_replacement[cur_scenario_device].add(
477
- attr_name=autonomous_name,
478
- scenario_feature=None,
479
- setup_feature=cur_setup_feature
480
- )
449
+ feature_replacement[cur_scenario_device].add_remaining_setup_features_as_autonomous(cur_setup_device)
481
450
 
482
451
  # set the result to internal properties
483
452
  self._feature_replacement = feature_replacement
@@ -902,8 +871,12 @@ class VariationExecutor(BasicExecutableExecutor):
902
871
 
903
872
  cur_setup_feature_controller.set_active_method_variation(method_selection=method_var_selection)
904
873
 
905
- def resolve_possible_parametrization(self):
874
+ def resolve_and_exchange_unresolved_parametrization(self):
906
875
  """resolves the parametrization if there are any :class:`UnresolvedParametrizedTestcaseExecutor` in the tree"""
876
+ replaced_executors = []
907
877
  for cur_child in self._testcase_executors:
908
878
  if isinstance(cur_child, UnresolvedParametrizedTestcaseExecutor):
909
- cur_child.resolve_dynamic_parametrization()
879
+ replaced_executors.extend(cur_child.get_resolved_parametrized_testcase_executors())
880
+ else:
881
+ replaced_executors.append(cur_child)
882
+ self._testcase_executors = replaced_executors
_balder/feature.py CHANGED
@@ -3,9 +3,10 @@ from typing import Type, Dict, Tuple, Union
3
3
 
4
4
  from _balder.device import Device
5
5
  from _balder.vdevice import VDevice
6
+ from _balder.utils.inner_device_managing_metaclass import InnerDeviceManagingMetaclass
6
7
 
7
8
 
8
- class Feature:
9
+ class Feature(metaclass=InnerDeviceManagingMetaclass):
9
10
  """
10
11
  This is the basic feature class. It represents an abstract class that should not be used directly. It is the base
11
12
  class for all feature elements.
@@ -1,6 +1,8 @@
1
- from typing import Union
1
+ from typing import Union, Type
2
2
  import dataclasses
3
+ from .device import Device
3
4
  from .feature import Feature
5
+ from .controllers import DeviceController
4
6
 
5
7
 
6
8
  class FeatureReplacementMapping:
@@ -37,6 +39,20 @@ class FeatureReplacementMapping:
37
39
  """
38
40
  return [mapping.attr_name for mapping in self._mappings]
39
41
 
42
+ @property
43
+ def all_scenario_features(self) -> list[Feature]:
44
+ """
45
+ returns all scenario features
46
+ """
47
+ return [mapping.scenario_feature for mapping in self._mappings]
48
+
49
+ @property
50
+ def all_setup_features(self) -> list[Feature]:
51
+ """
52
+ returns all setup features
53
+ """
54
+ return [mapping.setup_feature for mapping in self._mappings]
55
+
40
56
  def add(self, attr_name: str, scenario_feature: Union[Feature, None], setup_feature: Feature):
41
57
  """
42
58
  adds a new mapping
@@ -67,3 +83,25 @@ class FeatureReplacementMapping:
67
83
  if mapping.setup_feature == setup_feature:
68
84
  return mapping.scenario_feature
69
85
  raise KeyError(f'cannot find setup feature for {setup_feature}')
86
+
87
+ def add_remaining_setup_features_as_autonomous(self, cur_setup_device: Type[Device]):
88
+ """
89
+ Adds all features that were not added as autonomous features.
90
+ """
91
+ cur_setup_features = DeviceController.get_for(cur_setup_device).get_all_instantiated_feature_objects()
92
+
93
+ # also add all setup features that are not assigned as autonomous features
94
+ for cur_setup_feature in cur_setup_features.values():
95
+ if cur_setup_feature not in self.all_setup_features:
96
+ # determine free name
97
+ idx = 0
98
+ autonomous_name = None
99
+ while (autonomous_name is None
100
+ or autonomous_name in self.attr_names):
101
+ autonomous_name = f"_autonomous_feat_{idx}"
102
+ idx += 1
103
+ self.add(
104
+ attr_name=autonomous_name,
105
+ scenario_feature=None,
106
+ setup_feature=cur_setup_feature
107
+ )
@@ -19,7 +19,7 @@ from _balder.exceptions import LostInExecutorTreeException, FixtureReferenceErro
19
19
  UnclearUniqueClassReference
20
20
 
21
21
  if TYPE_CHECKING:
22
- from _balder.utils import MethodLiteralType
22
+ from _balder.utils.functions import MethodLiteralType
23
23
  from _balder.executor.executor_tree import ExecutorTree
24
24
 
25
25
 
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
  from typing import Union, Type, Callable, Generator, TYPE_CHECKING
3
3
  import dataclasses
4
4
 
5
- from .utils import MethodLiteralType
5
+ from .utils.typings import MethodLiteralType
6
6
 
7
7
  if TYPE_CHECKING:
8
8
  from _balder.scenario import Scenario