baldertest 0.1.0b8__py3-none-any.whl → 0.1.0b10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. _balder/_version.py +1 -1
  2. _balder/balder_session.py +5 -3
  3. _balder/collector.py +117 -8
  4. _balder/console/balder.py +1 -1
  5. _balder/controllers/device_controller.py +1 -1
  6. _balder/controllers/feature_controller.py +38 -17
  7. _balder/controllers/normal_scenario_setup_controller.py +5 -12
  8. _balder/controllers/scenario_controller.py +91 -1
  9. _balder/decorator_fixture.py +4 -7
  10. _balder/decorator_for_vdevice.py +4 -6
  11. _balder/decorator_parametrize.py +31 -0
  12. _balder/decorator_parametrize_by_feature.py +36 -0
  13. _balder/exceptions.py +6 -0
  14. _balder/executor/basic_executable_executor.py +126 -0
  15. _balder/executor/basic_executor.py +6 -95
  16. _balder/executor/executor_tree.py +22 -8
  17. _balder/executor/parametrized_testcase_executor.py +52 -0
  18. _balder/executor/scenario_executor.py +10 -2
  19. _balder/executor/setup_executor.py +40 -2
  20. _balder/executor/testcase_executor.py +42 -9
  21. _balder/executor/unresolved_parametrized_testcase_executor.py +209 -0
  22. _balder/executor/variation_executor.py +30 -51
  23. _balder/fixture_definition_scope.py +19 -0
  24. _balder/fixture_execution_level.py +22 -0
  25. _balder/fixture_manager.py +169 -182
  26. _balder/fixture_metadata.py +26 -0
  27. _balder/parametrization.py +75 -0
  28. _balder/solver.py +51 -31
  29. _balder/testresult.py +38 -6
  30. balder/__init__.py +6 -0
  31. balder/exceptions.py +4 -3
  32. balder/parametrization.py +8 -0
  33. {baldertest-0.1.0b8.dist-info → baldertest-0.1.0b10.dist-info}/METADATA +1 -1
  34. {baldertest-0.1.0b8.dist-info → baldertest-0.1.0b10.dist-info}/RECORD +38 -28
  35. {baldertest-0.1.0b8.dist-info → baldertest-0.1.0b10.dist-info}/WHEEL +1 -1
  36. {baldertest-0.1.0b8.dist-info → baldertest-0.1.0b10.dist-info}/LICENSE +0 -0
  37. {baldertest-0.1.0b8.dist-info → baldertest-0.1.0b10.dist-info}/entry_points.txt +0 -0
  38. {baldertest-0.1.0b8.dist-info → baldertest-0.1.0b10.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,209 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ from typing import List, Type, Any, Dict, Iterable, TYPE_CHECKING
5
+ import types
6
+ from graphlib import TopologicalSorter
7
+ from collections import OrderedDict
8
+
9
+ from _balder.executor.basic_executor import BasicExecutor
10
+ from _balder.executor.parametrized_testcase_executor import ParametrizedTestcaseExecutor
11
+ from _balder.parametrization import Parameter
12
+ from _balder.previous_executor_mark import PreviousExecutorMark
13
+ from _balder.testresult import BranchBodyResult
14
+
15
+ if TYPE_CHECKING:
16
+ from _balder.executor.variation_executor import VariationExecutor
17
+ from _balder.scenario import Scenario
18
+ from _balder.setup import Setup
19
+
20
+
21
+ class UnresolvedParametrizedTestcaseExecutor(BasicExecutor):
22
+ """
23
+ This executor class represents a group of dynamically parametrized tests.
24
+ """
25
+
26
+ def __init__(
27
+ self,
28
+ testcase: callable,
29
+ parent: VariationExecutor,
30
+ static_parameters: Dict[str, Any] = None,
31
+ ) -> None:
32
+ super().__init__()
33
+
34
+ self._base_testcase_callable = testcase
35
+ self._parent_executor = parent
36
+
37
+ # holds the specific static parameters for this unresolved group
38
+ self._static_parameters = static_parameters if static_parameters is not None else {}
39
+
40
+ # holds the dynamically created testcase executors as soon as this executor is entered
41
+ self._testcase_executors = None
42
+
43
+ # contains the result object for the BODY part of this branch
44
+ self.body_result = BranchBodyResult(self)
45
+
46
+ @property
47
+ def parent_executor(self) -> VariationExecutor:
48
+ return self._parent_executor
49
+
50
+ @property
51
+ def base_testcase_callable(self) -> callable:
52
+ """returns the testcase function"""
53
+ return self._base_testcase_callable
54
+
55
+ @property
56
+ def all_child_executors(self) -> List[ParametrizedTestcaseExecutor]:
57
+ return self.get_testcase_executors()
58
+
59
+ @property
60
+ def base_testcase_obj(self) -> Scenario:
61
+ """
62
+ return the testcase Scenario this testcase is defined in
63
+ """
64
+ return self.parent_executor.cur_scenario_class
65
+
66
+ @property
67
+ def base_instance(self) -> object:
68
+ """
69
+ returns the base class instance to which this executor instance belongs"""
70
+ return self._base_testcase_callable
71
+
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)
86
+
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
95
+
96
+ def get_all_base_instances_of_this_branch(
97
+ self, with_type: Type[Setup] | Type[Scenario] | Type[types.FunctionType],
98
+ only_runnable_elements: bool = True) -> List[Setup | Scenario | object]:
99
+ """
100
+ returns itself if the type matches, otherwise an empty list
101
+ """
102
+ # todo
103
+ if isinstance(self.base_instance, with_type):
104
+ return [self.base_instance]
105
+ return []
106
+
107
+ 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]
120
+ return all_covered_by_data
121
+
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):
129
+ """
130
+ resolves the dynamic parametrization - should be called when setup features are active in the scenario
131
+ """
132
+ self._testcase_executors = []
133
+
134
+ 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
+ )
144
+ )
145
+
146
+ def get_parametrization(self) -> List[OrderedDict[str, Any]] | None:
147
+ """
148
+ returns all parametrization elements that belongs to this group executor
149
+ """
150
+ scenario_controller = self.parent_executor.parent_executor.base_scenario_controller
151
+ dynamic_parametrization = scenario_controller.get_parametrization_for(self._base_testcase_callable,
152
+ static=False)
153
+ if not dynamic_parametrization:
154
+ raise ValueError('can not determine dynamic parametrization, because there are no dynamic parameters')
155
+
156
+ # sort attributes according their Parameter - using TopologicalSorter
157
+ graph = {attr: [param.name for param in config.get_parameters(of_type=Parameter).values()]
158
+ for attr, config in dynamic_parametrization.items()}
159
+ # also add all elements from static parameters
160
+ graph.update({param: [] for param in self._static_parameters})
161
+
162
+ ts = TopologicalSorter(graph)
163
+ resolvable_order_of_attribues = ts.static_order()
164
+ resolvable_dynamic_attribues = [attr for attr in resolvable_order_of_attribues
165
+ if attr in dynamic_parametrization.keys()]
166
+
167
+ def get_variations_for(
168
+ resolved_parameters: Dict[str, Any],
169
+ remaining_attributes: Iterable[str]
170
+ ) -> List[Dict[str, Any]]:
171
+ result = []
172
+ remaining_attributes = list(remaining_attributes).copy()
173
+ attr = remaining_attributes.pop(0)
174
+ feature_access_selector = dynamic_parametrization[attr]
175
+ # get value for this attribute
176
+ attr_value_list = feature_access_selector.get_value(resolved_parameters)
177
+ if not isinstance(attr_value_list, Iterable):
178
+ raise TypeError(
179
+ f'feature parametrizing not possible, because `{feature_access_selector.device.__qualname__}'
180
+ f'.{feature_access_selector.device_property_name}.{feature_access_selector.feature_property_name}` '
181
+ f'does not return Iterable')
182
+
183
+ for cur_value in attr_value_list:
184
+ parameters_with_cur_attr_value = resolved_parameters.copy()
185
+ parameters_with_cur_attr_value[attr] = cur_value
186
+ if len(remaining_attributes) > 0:
187
+ result.extend(get_variations_for(parameters_with_cur_attr_value, remaining_attributes))
188
+ else:
189
+ result.append(parameters_with_cur_attr_value)
190
+
191
+ return result
192
+
193
+ resolved_parameters = self._static_parameters.copy()
194
+
195
+ all_full_parametrization = get_variations_for(resolved_parameters, resolvable_dynamic_attribues)
196
+
197
+ # get combined parametrization
198
+ result = []
199
+ for cur_full_parametrization in all_full_parametrization:
200
+ cur_parameter_set = OrderedDict()
201
+ for cur_arg in inspect.getfullargspec(self._base_testcase_callable).args:
202
+ # only if this is a parametrization value
203
+ if cur_arg in cur_full_parametrization:
204
+ cur_parameter_set[cur_arg] = cur_full_parametrization[cur_arg]
205
+ result.append(cur_parameter_set)
206
+ return result
207
+
208
+ def cleanup_empty_executor_branches(self, consider_discarded=False):
209
+ pass
@@ -5,9 +5,11 @@ import inspect
5
5
  import logging
6
6
  from _balder.device import Device
7
7
  from _balder.connection import Connection
8
- from _balder.testresult import ResultState, BranchBodyResult
9
- from _balder.executor.basic_executor import BasicExecutor
8
+ from _balder.fixture_execution_level import FixtureExecutionLevel
9
+ from _balder.testresult import ResultState, BranchBodyResult, ResultSummary
10
+ from _balder.executor.basic_executable_executor import BasicExecutableExecutor
10
11
  from _balder.executor.testcase_executor import TestcaseExecutor
12
+ from _balder.executor.unresolved_parametrized_testcase_executor import UnresolvedParametrizedTestcaseExecutor
11
13
  from _balder.previous_executor_mark import PreviousExecutorMark
12
14
  from _balder.routing_path import RoutingPath
13
15
  from _balder.unmapped_vdevice import UnmappedVDevice
@@ -26,10 +28,11 @@ if TYPE_CHECKING:
26
28
  logger = logging.getLogger(__file__)
27
29
 
28
30
 
29
- class VariationExecutor(BasicExecutor):
31
+ class VariationExecutor(BasicExecutableExecutor):
30
32
  """
31
33
  A VariationExecutor only contains :meth:`TestcaseExecutor` children.
32
34
  """
35
+ fixture_execution_level = FixtureExecutionLevel.VARIATION
33
36
 
34
37
  def __init__(self, device_mapping: Dict[Type[Device], Type[Device]], parent: ScenarioExecutor):
35
38
  super().__init__()
@@ -107,7 +110,7 @@ class VariationExecutor(BasicExecutor):
107
110
  return self._base_device_mapping
108
111
 
109
112
  @property
110
- def all_child_executors(self) -> List[TestcaseExecutor]:
113
+ def all_child_executors(self) -> List[TestcaseExecutor | UnresolvedParametrizedTestcaseExecutor]:
111
114
  return self._testcase_executors
112
115
 
113
116
  @property
@@ -150,10 +153,10 @@ class VariationExecutor(BasicExecutor):
150
153
  self.determine_abs_variation_connections()
151
154
  self.update_scenario_device_feature_instances()
152
155
  self.update_active_vdevice_device_mappings_in_all_features()
153
- self.update_inner_referenced_feature_instances()
154
156
  self.exchange_unmapped_vdevice_references()
155
157
  self.update_vdevice_referenced_feature_instances()
156
158
  self.set_conn_dependent_methods()
159
+ self.resolve_possible_parametrization()
157
160
 
158
161
  def _body_execution(self, show_discarded):
159
162
  if show_discarded and not self.can_be_applied():
@@ -283,28 +286,29 @@ class VariationExecutor(BasicExecutor):
283
286
 
284
287
  # ---------------------------------- METHODS -----------------------------------------------------------------------
285
288
 
286
- def testsummary(self):
289
+ def testsummary(self) -> ResultSummary:
287
290
  if self.can_be_applied():
288
291
  return super().testsummary()
289
- return {
290
- ResultState.NOT_RUN: 0,
291
- ResultState.FAILURE: 0,
292
- ResultState.ERROR: 0,
293
- ResultState.SUCCESS: 0,
294
- ResultState.SKIP: 0,
295
- ResultState.COVERED_BY: 0
296
- }
292
+ return ResultSummary()
297
293
 
298
294
  def get_testcase_executors(self) -> List[TestcaseExecutor]:
299
295
  """returns all sub testcase executors that belongs to this variation-executor"""
300
- return self._testcase_executors
296
+ result = []
297
+ for cur_executor in self._testcase_executors:
298
+ if (isinstance(cur_executor, UnresolvedParametrizedTestcaseExecutor) and
299
+ cur_executor.parametrization_has_been_resolved):
300
+ result += cur_executor.get_testcase_executors()
301
+ else:
302
+ result.append(cur_executor)
303
+ return result
301
304
 
302
- def add_testcase_executor(self, testcase_executor: TestcaseExecutor):
305
+ def add_testcase_executor(self, testcase_executor: TestcaseExecutor | UnresolvedParametrizedTestcaseExecutor):
303
306
  """
304
307
  This method adds a new TestcaseExecutor to the child element list of this object branch
305
308
  """
306
- if not isinstance(testcase_executor, TestcaseExecutor):
307
- raise TypeError("the given object `testcase_executor` must be of type type `TestcaseExecutor`")
309
+ if not isinstance(testcase_executor, (TestcaseExecutor, UnresolvedParametrizedTestcaseExecutor)):
310
+ raise TypeError("the given object `testcase_executor` must be of type type `TestcaseExecutor` or "
311
+ "`UnresolvedParametrizedTestcaseExecutor`")
308
312
  if testcase_executor in self._testcase_executors:
309
313
  raise ValueError("the given object `testcase_executor` already exists in child list")
310
314
  self._testcase_executors.append(testcase_executor)
@@ -469,7 +473,7 @@ class VariationExecutor(BasicExecutor):
469
473
  raise KeyError("the requested setup device exists more than one time in `base_device_mapping`")
470
474
  return [cur_key for cur_key, cur_value in self.base_device_mapping.items() if cur_value == setup_device][0]
471
475
 
472
- def get_executor_for_testcase(self, testcase: callable) -> Union[TestcaseExecutor, None]:
476
+ def get_executor_for_testcase(self, testcase: callable) -> TestcaseExecutor | None:
473
477
  """
474
478
  This method searches for a TestcaseExecutor in the internal list for which the given testcase method is
475
479
  contained in
@@ -570,34 +574,6 @@ class VariationExecutor(BasicExecutor):
570
574
  for cur_feature, cur_original_mapping in cur_feature_mapping_dict.items():
571
575
  cur_feature.active_vdevices = cur_original_mapping
572
576
 
573
- def update_inner_referenced_feature_instances(self):
574
- """
575
- This method ensures that all inner referenced feature instances of the used feature object, will be replaced
576
- with the related feature instances of the device object itself.
577
-
578
- .. note::
579
- Note that this method expects that the true defined scenario features are already replaced with the real
580
- setup features. So the method requires that the method `update_scenario_device_feature_instances()` was
581
- called before.
582
- """
583
- for scenario_device, _ in self._base_device_mapping.items():
584
- # these features are subclasses of the real defined one (because they are already the replaced ones)
585
- all_device_features = DeviceController.get_for(scenario_device).get_all_instantiated_feature_objects()
586
- all_instantiated_feature_objs = [cur_feature for _, cur_feature in all_device_features.items()]
587
- for _, cur_feature in all_device_features.items():
588
- cur_feature_controller = FeatureController.get_for(cur_feature.__class__)
589
- # now check the inner referenced features of this feature and check if that exists in the device
590
- for cur_ref_feature_name, cur_ref_feature in \
591
- cur_feature_controller.get_inner_referenced_features().items():
592
- potential_candidates = [candidate_feature for candidate_feature in all_instantiated_feature_objs
593
- if isinstance(candidate_feature, cur_ref_feature.__class__)]
594
- if len(potential_candidates) != 1:
595
- raise RuntimeError("found none or more than one potential replacing candidates")
596
- replacing_candidate = potential_candidates[0]
597
- # because `cur_feature` is only the object instance, the value will be overwritten only for this
598
- # object
599
- setattr(cur_feature, cur_ref_feature_name, replacing_candidate)
600
-
601
577
  def exchange_unmapped_vdevice_references(self):
602
578
  """
603
579
  This method exchanges all :class:`VDevice` references to an instance of :class:`UnmappedVDevice` if the
@@ -722,10 +698,7 @@ class VariationExecutor(BasicExecutor):
722
698
  if cur_scenario_device not in abs_var_scenario_device_cnns.keys():
723
699
  abs_var_scenario_device_cnns[cur_scenario_device] = {}
724
700
 
725
- if cur_cnn.from_device == cur_scenario_device:
726
- cur_to_device = cur_cnn.to_device
727
- else:
728
- cur_to_device = cur_cnn.from_device
701
+ cur_to_device, _ = cur_cnn.get_conn_partner_of(cur_scenario_device)
729
702
 
730
703
  if cur_to_device not in abs_var_scenario_device_cnns[cur_scenario_device].keys():
731
704
  abs_var_scenario_device_cnns[cur_scenario_device][cur_to_device] = []
@@ -915,3 +888,9 @@ class VariationExecutor(BasicExecutor):
915
888
  (mapped_vdevice, absolute_feature_method_var_cnn, cur_method_variation)
916
889
 
917
890
  cur_setup_feature_controller.set_active_method_variation(method_selection=method_var_selection)
891
+
892
+ def resolve_possible_parametrization(self):
893
+ """resolves the parametrization if there are any :class:`UnresolvedParametrizedTestcaseExecutor` in the tree"""
894
+ for cur_child in self._testcase_executors:
895
+ if isinstance(cur_child, UnresolvedParametrizedTestcaseExecutor):
896
+ cur_child.resolve_dynamic_parametrization()
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+ from typing import List
3
+ from enum import Enum
4
+
5
+
6
+ class FixtureDefinitionScope(Enum):
7
+ """
8
+ This enum describes the definition scope of a fixture. A definition scope is the position the fixture was defined.
9
+ If the fixture was defined within the balderglob.py file, it has the definition-scope `GLOB`. If it is defined
10
+ within a scenario or setup it has the equivalent SCENARIO or SETUP scope.
11
+ """
12
+ GLOB = 'glob'
13
+ SETUP = 'setup'
14
+ SCENARIO = 'scenario'
15
+
16
+ @classmethod
17
+ def get_order(cls) -> List[FixtureDefinitionScope]:
18
+ """returns the priority order of the fixture definition scope"""
19
+ return [cls.GLOB, cls.SETUP, cls.SCENARIO]
@@ -0,0 +1,22 @@
1
+ from __future__ import annotations
2
+ from typing import List
3
+ from enum import Enum
4
+
5
+
6
+ class FixtureExecutionLevel(Enum):
7
+ """
8
+ This enum describes the fixture-execution-level of a fixture. It describes when the fixture should be executed. This
9
+ level will be set in the fixture decorator.
10
+ """
11
+ SESSION = 'session'
12
+ SETUP = 'setup'
13
+ SCENARIO = 'scenario'
14
+ VARIATION = 'variation'
15
+ TESTCASE = 'testcase'
16
+
17
+ @classmethod
18
+ def get_order(cls) -> List[FixtureExecutionLevel]:
19
+ """
20
+ returns the execution order of fixtures
21
+ """
22
+ return [cls.SESSION, cls.SETUP, cls.SCENARIO, cls.VARIATION, cls.TESTCASE]