baldertest 0.1.0__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 (89) hide show
  1. _balder/__init__.py +12 -0
  2. _balder/_version.py +34 -0
  3. _balder/balder_plugin.py +73 -0
  4. _balder/balder_session.py +341 -0
  5. _balder/balder_settings.py +15 -0
  6. _balder/cnnrelations/__init__.py +7 -0
  7. _balder/cnnrelations/and_connection_relation.py +176 -0
  8. _balder/cnnrelations/base_connection_relation.py +270 -0
  9. _balder/cnnrelations/or_connection_relation.py +65 -0
  10. _balder/collector.py +874 -0
  11. _balder/connection.py +863 -0
  12. _balder/connection_metadata.py +255 -0
  13. _balder/console/__init__.py +0 -0
  14. _balder/console/balder.py +58 -0
  15. _balder/controllers/__init__.py +12 -0
  16. _balder/controllers/base_device_controller.py +72 -0
  17. _balder/controllers/controller.py +29 -0
  18. _balder/controllers/device_controller.py +446 -0
  19. _balder/controllers/feature_controller.py +715 -0
  20. _balder/controllers/normal_scenario_setup_controller.py +402 -0
  21. _balder/controllers/scenario_controller.py +524 -0
  22. _balder/controllers/setup_controller.py +134 -0
  23. _balder/controllers/vdevice_controller.py +95 -0
  24. _balder/decorator_connect.py +104 -0
  25. _balder/decorator_covered_by.py +74 -0
  26. _balder/decorator_fixture.py +29 -0
  27. _balder/decorator_for_vdevice.py +118 -0
  28. _balder/decorator_gateway.py +34 -0
  29. _balder/decorator_insert_into_tree.py +52 -0
  30. _balder/decorator_parametrize.py +31 -0
  31. _balder/decorator_parametrize_by_feature.py +36 -0
  32. _balder/device.py +18 -0
  33. _balder/exceptions.py +182 -0
  34. _balder/executor/__init__.py +0 -0
  35. _balder/executor/basic_executable_executor.py +133 -0
  36. _balder/executor/basic_executor.py +205 -0
  37. _balder/executor/executor_tree.py +217 -0
  38. _balder/executor/parametrized_testcase_executor.py +52 -0
  39. _balder/executor/scenario_executor.py +169 -0
  40. _balder/executor/setup_executor.py +163 -0
  41. _balder/executor/testcase_executor.py +203 -0
  42. _balder/executor/unresolved_parametrized_testcase_executor.py +184 -0
  43. _balder/executor/variation_executor.py +882 -0
  44. _balder/exit_code.py +19 -0
  45. _balder/feature.py +74 -0
  46. _balder/feature_replacement_mapping.py +107 -0
  47. _balder/feature_vdevice_mapping.py +88 -0
  48. _balder/fixture_definition_scope.py +19 -0
  49. _balder/fixture_execution_level.py +22 -0
  50. _balder/fixture_manager.py +483 -0
  51. _balder/fixture_metadata.py +26 -0
  52. _balder/node_gateway.py +103 -0
  53. _balder/objects/__init__.py +0 -0
  54. _balder/objects/connections/__init__.py +0 -0
  55. _balder/objects/connections/osi_1_physical.py +116 -0
  56. _balder/objects/connections/osi_2_datalink.py +35 -0
  57. _balder/objects/connections/osi_3_network.py +47 -0
  58. _balder/objects/connections/osi_4_transport.py +40 -0
  59. _balder/objects/connections/osi_5_session.py +13 -0
  60. _balder/objects/connections/osi_6_presentation.py +13 -0
  61. _balder/objects/connections/osi_7_application.py +83 -0
  62. _balder/objects/devices/__init__.py +0 -0
  63. _balder/objects/devices/this_device.py +12 -0
  64. _balder/parametrization.py +75 -0
  65. _balder/plugin_manager.py +138 -0
  66. _balder/previous_executor_mark.py +23 -0
  67. _balder/routing_path.py +335 -0
  68. _balder/scenario.py +20 -0
  69. _balder/setup.py +18 -0
  70. _balder/solver.py +246 -0
  71. _balder/testresult.py +163 -0
  72. _balder/unmapped_vdevice.py +13 -0
  73. _balder/utils/__init__.py +0 -0
  74. _balder/utils/functions.py +103 -0
  75. _balder/utils/inner_device_managing_metaclass.py +14 -0
  76. _balder/utils/mixin_can_be_covered_by_executor.py +24 -0
  77. _balder/utils/typings.py +4 -0
  78. _balder/vdevice.py +9 -0
  79. balder/__init__.py +56 -0
  80. balder/connections.py +43 -0
  81. balder/devices.py +9 -0
  82. balder/exceptions.py +44 -0
  83. balder/parametrization.py +8 -0
  84. baldertest-0.1.0.dist-info/METADATA +356 -0
  85. baldertest-0.1.0.dist-info/RECORD +89 -0
  86. baldertest-0.1.0.dist-info/WHEEL +5 -0
  87. baldertest-0.1.0.dist-info/entry_points.txt +2 -0
  88. baldertest-0.1.0.dist-info/licenses/LICENSE +21 -0
  89. baldertest-0.1.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,203 @@
1
+ from __future__ import annotations
2
+ from typing import List, Union, TYPE_CHECKING
3
+
4
+ import sys
5
+ import time
6
+ import traceback
7
+
8
+ from _balder.executor.basic_executable_executor import BasicExecutableExecutor
9
+ from _balder.fixture_execution_level import FixtureExecutionLevel
10
+ from _balder.previous_executor_mark import PreviousExecutorMark
11
+ from _balder.testresult import ResultState, TestcaseResult
12
+ from _balder.utils.functions import get_method_type
13
+ from _balder.utils.mixin_can_be_covered_by_executor import MixinCanBeCoveredByExecutor
14
+
15
+ if TYPE_CHECKING:
16
+ from _balder.executor.scenario_executor import ScenarioExecutor
17
+ from _balder.executor.variation_executor import VariationExecutor
18
+ from _balder.fixture_manager import FixtureManager
19
+ from _balder.scenario import Scenario
20
+
21
+
22
+ class TestcaseExecutor(BasicExecutableExecutor, MixinCanBeCoveredByExecutor):
23
+ """
24
+ A TestcaseExecutor class represents an actual single test that can be executed. It therefore references exactly to a
25
+ test method of a scenario that can be executed on the specific setup this executor belongs to.
26
+ """
27
+ fixture_execution_level = FixtureExecutionLevel.TESTCASE
28
+
29
+ def __init__(
30
+ self,
31
+ testcase: callable,
32
+ parent: VariationExecutor,
33
+ ) -> None:
34
+ super().__init__()
35
+
36
+ self._base_testcase_callable = testcase
37
+ self._parent_executor = parent
38
+ self._fixture_manager: FixtureManager = parent.fixture_manager
39
+
40
+ # if there exist executors that cover this item, they are contained as list in this property
41
+ self.covered_by_executors = None
42
+
43
+ # contains the result object for the BODY part of this branch
44
+ self.body_result = TestcaseResult(self)
45
+
46
+ # holds the raw test execution time in seconds
47
+ self.test_execution_time_sec = 0
48
+
49
+ # determine prev_mark IGNORE/SKIP for the testcase
50
+ if self.should_be_skipped():
51
+ self.prev_mark = PreviousExecutorMark.SKIP
52
+ # always overwrite if it should be ignored
53
+ if self.should_be_ignored():
54
+ self.prev_mark = PreviousExecutorMark.IGNORE
55
+
56
+ # ---------------------------------- STATIC METHODS ----------------------------------------------------------------
57
+
58
+ # ---------------------------------- CLASS METHODS ----------------------------------------------------------------
59
+
60
+ # ---------------------------------- PROPERTIES --------------------------------------------------------------------
61
+
62
+ @property
63
+ def all_child_executors(self) -> None:
64
+ return None
65
+
66
+ @property
67
+ def parent_executor(self) -> VariationExecutor:
68
+ return self._parent_executor
69
+
70
+ @property
71
+ def scenario_executor(self) -> ScenarioExecutor:
72
+ return self.parent_executor.parent_executor
73
+
74
+ @property
75
+ def base_instance(self) -> object:
76
+ """
77
+ returns the base class instance to which this executor instance belongs"""
78
+ return self._base_testcase_callable
79
+
80
+ @property
81
+ def base_testcase_callable(self) -> callable:
82
+ """returns the testcase function"""
83
+ return self._base_testcase_callable
84
+
85
+ @property
86
+ def base_testcase_obj(self) -> Scenario:
87
+ """
88
+ return the testcase Scenario this testcase is defined in
89
+ """
90
+ return self._parent_executor.cur_scenario_class
91
+
92
+ @property
93
+ def fixture_manager(self):
94
+ """returns the current active fixture manager"""
95
+ return self._fixture_manager
96
+
97
+ @property
98
+ def full_test_name_str(self) -> str:
99
+ """
100
+ returns the name of the test method, that should be used in output
101
+ """
102
+ return self._base_testcase_callable.__qualname__
103
+
104
+ # ---------------------------------- PROTECTED METHODS -------------------------------------------------------------
105
+
106
+ def _prepare_execution(self, show_discarded):
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
+
112
+ if self.should_be_skipped():
113
+ self.body_result.set_result(ResultState.SKIP)
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
121
+
122
+ start_time = time.perf_counter()
123
+ try:
124
+ func_type = get_method_type(self.base_testcase_obj.__class__, self.base_testcase_callable)
125
+ all_args = self.get_all_test_method_args()
126
+ if func_type == "staticmethod":
127
+ # testcase is a staticmethod - no special first attribute
128
+ self.base_testcase_callable(**all_args)
129
+ elif func_type == "classmethod":
130
+ self.base_testcase_callable(self=self.base_testcase_obj.__class__, **all_args)
131
+ elif func_type == "instancemethod":
132
+ self.base_testcase_callable(self=self.base_testcase_obj, **all_args)
133
+ else:
134
+ # `function` is not allowed here!
135
+ raise ValueError(f"found illegal value for func_type `{func_type}` for test "
136
+ f"`{self.base_testcase_callable.__name__}`")
137
+
138
+ self.body_result.set_result(ResultState.SUCCESS)
139
+ except Exception as exc: # pylint: disable=broad-exception-caught
140
+ # this has to be a test error
141
+ traceback.print_exception(*sys.exc_info())
142
+ self.body_result.set_result(ResultState.FAILURE, exc)
143
+ self.test_execution_time_sec = time.perf_counter() - start_time
144
+
145
+ def _cleanup_execution(self, show_discarded):
146
+ print(f"[{self.body_result.get_result_as_char()}]")
147
+
148
+ # ---------------------------------- METHODS -----------------------------------------------------------------------
149
+
150
+ def should_run(self):
151
+ """returns true if the testcase should be executed (defined in scenario)"""
152
+ if self.base_testcase_callable in self.parent_executor.parent_executor.all_run_tests:
153
+ return True
154
+ return False
155
+
156
+ def should_be_skipped(self):
157
+ """returns true if the testcase should be skipped (defined in scenario)"""
158
+ if self.base_testcase_callable in self.parent_executor.parent_executor.all_skip_tests:
159
+ return True
160
+ return False
161
+
162
+ def should_be_ignored(self):
163
+ """returns true if the testcase should be ignored (defined in scenario)"""
164
+ if self.base_testcase_callable in self.parent_executor.parent_executor.all_ignore_tests:
165
+ return True
166
+ return False
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
+
178
+ def cleanup_empty_executor_branches(self, consider_discarded=False):
179
+ """
180
+ This method searches the whole tree and removes branches where an executor item has no own children. It can
181
+ remove these branches, because they have no valid matchings.
182
+
183
+ This method implementation of the :class:`TestcaseExecutor` does nothing.
184
+ """
185
+
186
+ def get_covered_by_element(self) -> List[Union[Scenario, callable]]:
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, []))
191
+ return all_covered_by_data
192
+
193
+ def get_all_test_method_args(self):
194
+ """
195
+ returns all kwargs values for the test method
196
+ """
197
+ func_type = get_method_type(self.base_testcase_obj.__class__, self.base_testcase_callable)
198
+ return self.fixture_manager.get_all_attribute_values(
199
+ self,
200
+ self.base_testcase_obj.__class__,
201
+ self.base_testcase_callable,
202
+ func_type
203
+ )
@@ -0,0 +1,184 @@
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
+ from _balder.utils.mixin_can_be_covered_by_executor import MixinCanBeCoveredByExecutor
15
+
16
+ if TYPE_CHECKING:
17
+ from _balder.executor.scenario_executor import ScenarioExecutor
18
+ from _balder.executor.variation_executor import VariationExecutor
19
+ from _balder.scenario import Scenario
20
+ from _balder.setup import Setup
21
+
22
+
23
+ class UnresolvedParametrizedTestcaseExecutor(BasicExecutor, MixinCanBeCoveredByExecutor):
24
+ """
25
+ This executor class represents a group of dynamically parametrized tests.
26
+ """
27
+
28
+ def __init__(
29
+ self,
30
+ testcase: callable,
31
+ parent: VariationExecutor,
32
+ static_parameters: Dict[str, Any] = None,
33
+ ) -> None:
34
+ super().__init__()
35
+
36
+ self._base_testcase_callable = testcase
37
+ self._parent_executor = parent
38
+
39
+ # holds the specific static parameters for this unresolved group
40
+ self._static_parameters = static_parameters if static_parameters is not None else {}
41
+
42
+ # contains the result object for the BODY part of this branch
43
+ self.body_result = BranchBodyResult(self)
44
+
45
+ @property
46
+ def parent_executor(self) -> VariationExecutor:
47
+ return self._parent_executor
48
+
49
+ @property
50
+ def scenario_executor(self) -> ScenarioExecutor:
51
+ return self.parent_executor.parent_executor
52
+
53
+ @property
54
+ def base_testcase_callable(self) -> callable:
55
+ """returns the testcase function"""
56
+ return self._base_testcase_callable
57
+
58
+ @property
59
+ def all_child_executors(self) -> None:
60
+ return None
61
+
62
+ @property
63
+ def base_testcase_obj(self) -> Scenario:
64
+ """
65
+ return the testcase Scenario this testcase is defined in
66
+ """
67
+ return self.parent_executor.cur_scenario_class
68
+
69
+ @property
70
+ def base_instance(self) -> object:
71
+ """
72
+ returns the base class instance to which this executor instance belongs"""
73
+ return self._base_testcase_callable
74
+
75
+ def has_skipped_tests(self) -> bool:
76
+ return self.prev_mark == PreviousExecutorMark.SKIP
77
+
78
+ def has_covered_by_tests(self) -> bool:
79
+ return self.prev_mark == PreviousExecutorMark.COVERED_BY
80
+
81
+ def get_all_base_instances_of_this_branch(
82
+ self, with_type: Type[Setup] | Type[Scenario] | Type[types.FunctionType],
83
+ only_runnable_elements: bool = True) -> List[Setup | Scenario | object]:
84
+ """
85
+ returns itself if the type matches, otherwise an empty list
86
+ """
87
+ # todo
88
+ if isinstance(self.base_instance, with_type):
89
+ return [self.base_instance]
90
+ return []
91
+
92
+ def get_covered_by_element(self) -> List[Scenario | callable]:
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, []))
97
+ return all_covered_by_data
98
+
99
+ def get_resolved_parametrized_testcase_executors(self):
100
+ """
101
+ resolves the dynamic parametrization - should be called when setup features are active in the scenario
102
+ """
103
+ executors = []
104
+
105
+ parametrization = self.get_parametrization()
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
117
+ )
118
+ )
119
+ return executors
120
+
121
+ def get_parametrization(self) -> List[OrderedDict[str, Any]] | None:
122
+ """
123
+ returns all parametrization elements that belongs to this group executor
124
+ """
125
+ scenario_controller = self.parent_executor.parent_executor.base_scenario_controller
126
+ dynamic_parametrization = scenario_controller.get_parametrization_for(self._base_testcase_callable,
127
+ static=False)
128
+ if not dynamic_parametrization:
129
+ raise ValueError('can not determine dynamic parametrization, because there are no dynamic parameters')
130
+
131
+ # sort attributes according their Parameter - using TopologicalSorter
132
+ graph = {attr: [param.name for param in config.get_parameters(of_type=Parameter).values()]
133
+ for attr, config in dynamic_parametrization.items()}
134
+ # also add all elements from static parameters
135
+ graph.update({param: [] for param in self._static_parameters})
136
+
137
+ ts = TopologicalSorter(graph)
138
+ resolvable_order_of_attribues = ts.static_order()
139
+ resolvable_dynamic_attribues = [attr for attr in resolvable_order_of_attribues
140
+ if attr in dynamic_parametrization.keys()]
141
+
142
+ def get_variations_for(
143
+ resolved_parameters: Dict[str, Any],
144
+ remaining_attributes: Iterable[str]
145
+ ) -> List[Dict[str, Any]]:
146
+ result = []
147
+ remaining_attributes = list(remaining_attributes).copy()
148
+ attr = remaining_attributes.pop(0)
149
+ feature_access_selector = dynamic_parametrization[attr]
150
+ # get value for this attribute
151
+ attr_value_list = feature_access_selector.get_value(resolved_parameters)
152
+ if not isinstance(attr_value_list, Iterable):
153
+ raise TypeError(
154
+ f'feature parametrizing not possible, because `{feature_access_selector.device.__qualname__}'
155
+ f'.{feature_access_selector.device_property_name}.{feature_access_selector.feature_property_name}` '
156
+ f'does not return Iterable')
157
+
158
+ for cur_value in attr_value_list:
159
+ parameters_with_cur_attr_value = resolved_parameters.copy()
160
+ parameters_with_cur_attr_value[attr] = cur_value
161
+ if len(remaining_attributes) > 0:
162
+ result.extend(get_variations_for(parameters_with_cur_attr_value, remaining_attributes))
163
+ else:
164
+ result.append(parameters_with_cur_attr_value)
165
+
166
+ return result
167
+
168
+ resolved_parameters = self._static_parameters.copy()
169
+
170
+ all_full_parametrization = get_variations_for(resolved_parameters, resolvable_dynamic_attribues)
171
+
172
+ # get combined parametrization
173
+ result = []
174
+ for cur_full_parametrization in all_full_parametrization:
175
+ cur_parameter_set = OrderedDict()
176
+ for cur_arg in inspect.getfullargspec(self._base_testcase_callable).args:
177
+ # only if this is a parametrization value
178
+ if cur_arg in cur_full_parametrization:
179
+ cur_parameter_set[cur_arg] = cur_full_parametrization[cur_arg]
180
+ result.append(cur_parameter_set)
181
+ return result
182
+
183
+ def cleanup_empty_executor_branches(self, consider_discarded=False):
184
+ pass