baldertest 0.1.0b9__py3-none-any.whl → 0.1.0b11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- _balder/_version.py +1 -1
- _balder/balder_session.py +4 -2
- _balder/cnnrelations/__init__.py +7 -0
- _balder/cnnrelations/and_connection_relation.py +149 -0
- _balder/cnnrelations/base_connection_relation.py +270 -0
- _balder/cnnrelations/or_connection_relation.py +65 -0
- _balder/collector.py +116 -14
- _balder/connection.py +400 -881
- _balder/connection_metadata.py +255 -0
- _balder/console/balder.py +1 -1
- _balder/controllers/device_controller.py +26 -12
- _balder/controllers/feature_controller.py +63 -99
- _balder/controllers/normal_scenario_setup_controller.py +7 -7
- _balder/controllers/scenario_controller.py +97 -7
- _balder/controllers/setup_controller.py +2 -3
- _balder/decorator_connect.py +12 -10
- _balder/decorator_fixture.py +4 -7
- _balder/decorator_for_vdevice.py +21 -31
- _balder/decorator_gateway.py +3 -3
- _balder/decorator_parametrize.py +31 -0
- _balder/decorator_parametrize_by_feature.py +36 -0
- _balder/exceptions.py +6 -0
- _balder/executor/basic_executable_executor.py +126 -0
- _balder/executor/basic_executor.py +1 -78
- _balder/executor/executor_tree.py +7 -5
- _balder/executor/parametrized_testcase_executor.py +52 -0
- _balder/executor/scenario_executor.py +5 -2
- _balder/executor/setup_executor.py +5 -2
- _balder/executor/testcase_executor.py +41 -9
- _balder/executor/unresolved_parametrized_testcase_executor.py +209 -0
- _balder/executor/variation_executor.py +148 -123
- _balder/feature.py +1 -1
- _balder/fixture_definition_scope.py +19 -0
- _balder/fixture_execution_level.py +22 -0
- _balder/fixture_manager.py +170 -182
- _balder/fixture_metadata.py +26 -0
- _balder/objects/connections/osi_3_network.py +2 -2
- _balder/objects/connections/osi_4_transport.py +2 -2
- _balder/parametrization.py +75 -0
- _balder/routing_path.py +18 -25
- _balder/solver.py +52 -32
- _balder/testresult.py +1 -1
- _balder/utils.py +27 -1
- balder/__init__.py +6 -0
- balder/exceptions.py +4 -3
- balder/parametrization.py +8 -0
- {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b11.dist-info}/METADATA +2 -2
- baldertest-0.1.0b11.dist-info/RECORD +83 -0
- {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b11.dist-info}/WHEEL +1 -1
- baldertest-0.1.0b9.dist-info/RECORD +0 -68
- {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b11.dist-info}/LICENSE +0 -0
- {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b11.dist-info}/entry_points.txt +0 -0
- {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b11.dist-info}/top_level.txt +0 -0
|
@@ -1,20 +1,15 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import time
|
|
4
3
|
from typing import List, Union, Type, TYPE_CHECKING
|
|
5
4
|
|
|
6
|
-
import sys
|
|
7
5
|
import types
|
|
8
|
-
import traceback
|
|
9
6
|
from abc import ABC, abstractmethod
|
|
10
|
-
from _balder.testresult import FixturePartResult, ResultState, ResultSummary
|
|
11
7
|
from _balder.previous_executor_mark import PreviousExecutorMark
|
|
12
|
-
from _balder.testresult import TestcaseResult
|
|
8
|
+
from _balder.testresult import FixturePartResult, ResultState, ResultSummary, TestcaseResult
|
|
13
9
|
|
|
14
10
|
if TYPE_CHECKING:
|
|
15
11
|
from _balder.setup import Setup
|
|
16
12
|
from _balder.scenario import Scenario
|
|
17
|
-
from _balder.fixture_manager import FixtureManager
|
|
18
13
|
|
|
19
14
|
|
|
20
15
|
class BasicExecutor(ABC):
|
|
@@ -35,9 +30,6 @@ class BasicExecutor(ABC):
|
|
|
35
30
|
# contains the result object for the TEARDOWN FIXTURE part of this branch
|
|
36
31
|
self.teardown_result = FixturePartResult(self)
|
|
37
32
|
|
|
38
|
-
# holds the execution time of this branch (with branch fixtures)
|
|
39
|
-
self.execution_time_sec = None
|
|
40
|
-
|
|
41
33
|
# ---------------------------------- STATIC METHODS ----------------------------------------------------------------
|
|
42
34
|
|
|
43
35
|
# ---------------------------------- CLASS METHODS ----------------------------------------------------------------
|
|
@@ -62,11 +54,6 @@ class BasicExecutor(ABC):
|
|
|
62
54
|
"""returns the base class instance to which this executor instance belongs or None if this element is a
|
|
63
55
|
ExecutorTree"""
|
|
64
56
|
|
|
65
|
-
@property
|
|
66
|
-
@abstractmethod
|
|
67
|
-
def fixture_manager(self) -> FixtureManager:
|
|
68
|
-
"""returns the active fixture manager instance"""
|
|
69
|
-
|
|
70
57
|
@property
|
|
71
58
|
def executor_result(self) -> ResultState:
|
|
72
59
|
"""
|
|
@@ -86,26 +73,6 @@ class BasicExecutor(ABC):
|
|
|
86
73
|
|
|
87
74
|
# ---------------------------------- PROTECTED METHODS -------------------------------------------------------------
|
|
88
75
|
|
|
89
|
-
@abstractmethod
|
|
90
|
-
def _prepare_execution(self, show_discarded):
|
|
91
|
-
"""
|
|
92
|
-
This method runs before the branch will be executed and before the fixture construction code of this branch
|
|
93
|
-
runs.
|
|
94
|
-
"""
|
|
95
|
-
|
|
96
|
-
@abstractmethod
|
|
97
|
-
def _body_execution(self, show_discarded):
|
|
98
|
-
"""
|
|
99
|
-
This method runs between the fixture construction and teardown code. It should trigger the execution of the
|
|
100
|
-
child branches.
|
|
101
|
-
"""
|
|
102
|
-
|
|
103
|
-
@abstractmethod
|
|
104
|
-
def _cleanup_execution(self, show_discarded):
|
|
105
|
-
"""
|
|
106
|
-
This method runs after the branch was executed (also after the fixture teardown code ran)
|
|
107
|
-
"""
|
|
108
|
-
|
|
109
76
|
# ---------------------------------- METHODS -----------------------------------------------------------------------
|
|
110
77
|
|
|
111
78
|
def get_all_recognized_exception(self) -> List[Exception]:
|
|
@@ -125,18 +92,6 @@ class BasicExecutor(ABC):
|
|
|
125
92
|
all_own.remove(None)
|
|
126
93
|
return all_own
|
|
127
94
|
|
|
128
|
-
def set_result_for_whole_branch(self, value: ResultState):
|
|
129
|
-
"""
|
|
130
|
-
This method sets the executor result for all sub executors.
|
|
131
|
-
|
|
132
|
-
:param value: the new value that should be set for this branch
|
|
133
|
-
"""
|
|
134
|
-
if value not in (ResultState.SKIP, ResultState.COVERED_BY, ResultState.NOT_RUN):
|
|
135
|
-
raise ValueError("can not set a state that is not NOT_RUN, SKIP or COVERED_BY for a whole branch")
|
|
136
|
-
for cur_child_executor in self.all_child_executors:
|
|
137
|
-
if isinstance(cur_child_executor.body_result, TestcaseResult):
|
|
138
|
-
cur_child_executor.body_result.set_result(result=value, exception=None)
|
|
139
|
-
|
|
140
95
|
def has_runnable_tests(self, consider_discarded_too=False) -> bool:
|
|
141
96
|
"""
|
|
142
97
|
This method returns true if this executor element is runnable. The method returns true if this element has
|
|
@@ -221,35 +176,3 @@ class BasicExecutor(ABC):
|
|
|
221
176
|
for cur_child_exec in self.all_child_executors:
|
|
222
177
|
summary += cur_child_exec.testsummary()
|
|
223
178
|
return summary
|
|
224
|
-
|
|
225
|
-
def execute(self, show_discarded=False):
|
|
226
|
-
"""
|
|
227
|
-
Executes the whole branch
|
|
228
|
-
"""
|
|
229
|
-
start_time = time.perf_counter()
|
|
230
|
-
self._prepare_execution(show_discarded=show_discarded)
|
|
231
|
-
|
|
232
|
-
try:
|
|
233
|
-
try:
|
|
234
|
-
if self.has_runnable_tests():
|
|
235
|
-
self.fixture_manager.enter(self)
|
|
236
|
-
self.construct_result.set_result(ResultState.SUCCESS)
|
|
237
|
-
|
|
238
|
-
self._body_execution(show_discarded=show_discarded)
|
|
239
|
-
except Exception as exc:
|
|
240
|
-
# this has to be a construction fixture error
|
|
241
|
-
traceback.print_exception(*sys.exc_info())
|
|
242
|
-
self.construct_result.set_result(ResultState.ERROR, exc)
|
|
243
|
-
finally:
|
|
244
|
-
if self.has_runnable_tests():
|
|
245
|
-
if self.fixture_manager.is_allowed_to_leave(self):
|
|
246
|
-
self.fixture_manager.leave(self)
|
|
247
|
-
self.teardown_result.set_result(ResultState.SUCCESS)
|
|
248
|
-
except Exception as exc:
|
|
249
|
-
# this has to be a teardown fixture error
|
|
250
|
-
traceback.print_exception(*sys.exc_info())
|
|
251
|
-
self.teardown_result.set_result(ResultState.ERROR, exc)
|
|
252
|
-
|
|
253
|
-
self._cleanup_execution(show_discarded=show_discarded)
|
|
254
|
-
|
|
255
|
-
self.execution_time_sec = time.perf_counter() - start_time
|
|
@@ -3,22 +3,24 @@ from typing import Union, List, Type, TYPE_CHECKING
|
|
|
3
3
|
|
|
4
4
|
from dataclasses import fields
|
|
5
5
|
from _balder.executor.setup_executor import SetupExecutor
|
|
6
|
-
from _balder.executor.
|
|
7
|
-
from _balder.
|
|
6
|
+
from _balder.executor.basic_executable_executor import BasicExecutableExecutor
|
|
7
|
+
from _balder.fixture_execution_level import FixtureExecutionLevel
|
|
8
8
|
from _balder.testresult import ResultState, BranchBodyResult, ResultSummary
|
|
9
9
|
from _balder.previous_executor_mark import PreviousExecutorMark
|
|
10
10
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
12
|
from _balder.setup import Setup
|
|
13
|
+
from _balder.fixture_manager import FixtureManager
|
|
13
14
|
from _balder.executor.scenario_executor import ScenarioExecutor
|
|
14
15
|
from _balder.executor.variation_executor import VariationExecutor
|
|
15
16
|
from _balder.executor.testcase_executor import TestcaseExecutor
|
|
16
17
|
|
|
17
18
|
|
|
18
|
-
class ExecutorTree(
|
|
19
|
+
class ExecutorTree(BasicExecutableExecutor):
|
|
19
20
|
"""
|
|
20
21
|
This class is the root object of the executor tree structure
|
|
21
22
|
"""
|
|
23
|
+
fixture_execution_level = FixtureExecutionLevel.SESSION
|
|
22
24
|
LINE_LENGTH = 120
|
|
23
25
|
|
|
24
26
|
def __init__(self, fixture_manager: FixtureManager):
|
|
@@ -36,7 +38,7 @@ class ExecutorTree(BasicExecutor):
|
|
|
36
38
|
# ---------------------------------- PROPERTIES --------------------------------------------------------------------
|
|
37
39
|
|
|
38
40
|
@property
|
|
39
|
-
def all_child_executors(self) -> List[
|
|
41
|
+
def all_child_executors(self) -> List[BasicExecutableExecutor]:
|
|
40
42
|
return self._setup_executors
|
|
41
43
|
|
|
42
44
|
@property
|
|
@@ -180,7 +182,7 @@ class ExecutorTree(BasicExecutor):
|
|
|
180
182
|
is_first = False
|
|
181
183
|
else:
|
|
182
184
|
print(" | ", end="")
|
|
183
|
-
print(f"TOTAL {cur_field.name}: {getattr(summary, cur_field.name)}", end="")
|
|
185
|
+
print(f"TOTAL {cur_field.name.upper()}: {getattr(summary, cur_field.name)}", end="")
|
|
184
186
|
print("")
|
|
185
187
|
|
|
186
188
|
def print_tree(self, show_discarded=False) -> None:
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Any, TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
from collections import OrderedDict
|
|
5
|
+
from _balder.utils import inspect_method
|
|
6
|
+
from .testcase_executor import TestcaseExecutor
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from _balder.executor.unresolved_parametrized_testcase_executor import UnresolvedParametrizedTestcaseExecutor
|
|
10
|
+
from _balder.executor.variation_executor import VariationExecutor
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ParametrizedTestcaseExecutor(TestcaseExecutor):
|
|
14
|
+
"""
|
|
15
|
+
special testcase executor for parametrized testcases
|
|
16
|
+
"""
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
testcase: callable,
|
|
20
|
+
parent: VariationExecutor,
|
|
21
|
+
parametrization: OrderedDict[str, Any] = None,
|
|
22
|
+
unresolved_group_obj: UnresolvedParametrizedTestcaseExecutor = None
|
|
23
|
+
) -> None:
|
|
24
|
+
super().__init__(testcase=testcase, parent=parent)
|
|
25
|
+
self._parametrization = parametrization
|
|
26
|
+
# holds a reference to the parent unresolved object (if it has dynamic parametrized components
|
|
27
|
+
self._unresolved_group_obj: UnresolvedParametrizedTestcaseExecutor | None = unresolved_group_obj
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def full_test_name_str(self) -> str:
|
|
31
|
+
"""
|
|
32
|
+
returns the name of the test method, that should be used in output
|
|
33
|
+
"""
|
|
34
|
+
# try to get string name for parametrization
|
|
35
|
+
if self._parametrization:
|
|
36
|
+
# todo what if it is not convertable to string?
|
|
37
|
+
parametrization_name = ';'.join([str(e) for e in self._parametrization.values()])
|
|
38
|
+
return f"{super().full_test_name_str}[{parametrization_name}]"
|
|
39
|
+
return super().full_test_name_str
|
|
40
|
+
|
|
41
|
+
def get_all_test_method_args(self):
|
|
42
|
+
_, func_type = inspect_method(self.base_testcase_callable)
|
|
43
|
+
all_kwargs = self.fixture_manager.get_all_attribute_values(
|
|
44
|
+
self,
|
|
45
|
+
self.base_testcase_obj.__class__,
|
|
46
|
+
self.base_testcase_callable,
|
|
47
|
+
func_type,
|
|
48
|
+
ignore_attributes=self._parametrization.keys()
|
|
49
|
+
)
|
|
50
|
+
if self._parametrization:
|
|
51
|
+
all_kwargs.update(self._parametrization)
|
|
52
|
+
return all_kwargs
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
from typing import Type, Union, List, Dict, TYPE_CHECKING
|
|
3
3
|
|
|
4
|
+
from _balder.fixture_execution_level import FixtureExecutionLevel
|
|
4
5
|
from _balder.testresult import ResultState, BranchBodyResult
|
|
5
6
|
from _balder.utils import get_class_that_defines_method
|
|
6
|
-
from _balder.executor.
|
|
7
|
+
from _balder.executor.basic_executable_executor import BasicExecutableExecutor
|
|
7
8
|
from _balder.executor.variation_executor import VariationExecutor
|
|
8
9
|
from _balder.previous_executor_mark import PreviousExecutorMark
|
|
9
10
|
from _balder.controllers.scenario_controller import ScenarioController
|
|
@@ -14,10 +15,11 @@ if TYPE_CHECKING:
|
|
|
14
15
|
from _balder.fixture_manager import FixtureManager
|
|
15
16
|
|
|
16
17
|
|
|
17
|
-
class ScenarioExecutor(
|
|
18
|
+
class ScenarioExecutor(BasicExecutableExecutor):
|
|
18
19
|
"""
|
|
19
20
|
A ScenarioExecutor can contain :meth:`VariationExecutor` as children.
|
|
20
21
|
"""
|
|
22
|
+
fixture_execution_level = FixtureExecutionLevel.SCENARIO
|
|
21
23
|
|
|
22
24
|
def __init__(self, scenario: Type[Scenario], parent: SetupExecutor):
|
|
23
25
|
super().__init__()
|
|
@@ -67,6 +69,7 @@ class ScenarioExecutor(BasicExecutor):
|
|
|
67
69
|
|
|
68
70
|
@property
|
|
69
71
|
def base_scenario_controller(self) -> ScenarioController:
|
|
72
|
+
"""returns the :class:`ScenarioController` for the setup object of this executor"""
|
|
70
73
|
return ScenarioController.get_for(self.base_scenario_class.__class__)
|
|
71
74
|
|
|
72
75
|
@property
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
from typing import Type, Union, List, TYPE_CHECKING
|
|
3
3
|
|
|
4
|
+
from _balder.fixture_execution_level import FixtureExecutionLevel
|
|
4
5
|
from _balder.testresult import ResultState, BranchBodyResult
|
|
5
|
-
from _balder.executor.
|
|
6
|
+
from _balder.executor.basic_executable_executor import BasicExecutableExecutor
|
|
6
7
|
from _balder.executor.scenario_executor import ScenarioExecutor
|
|
7
8
|
from _balder.previous_executor_mark import PreviousExecutorMark
|
|
8
9
|
from _balder.controllers.setup_controller import SetupController
|
|
@@ -16,11 +17,12 @@ if TYPE_CHECKING:
|
|
|
16
17
|
from _balder.executor.executor_tree import ExecutorTree
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
class SetupExecutor(
|
|
20
|
+
class SetupExecutor(BasicExecutableExecutor):
|
|
20
21
|
"""
|
|
21
22
|
A SetupExecutor is the highest branch object of an :class:`ExecutorTree`. It contains all scenarios and the
|
|
22
23
|
underlying device mappings and their test cases that exist with this setup.
|
|
23
24
|
"""
|
|
25
|
+
fixture_execution_level = FixtureExecutionLevel.SETUP
|
|
24
26
|
|
|
25
27
|
def __init__(self, setup: Type[Setup], parent: ExecutorTree):
|
|
26
28
|
super().__init__()
|
|
@@ -63,6 +65,7 @@ class SetupExecutor(BasicExecutor):
|
|
|
63
65
|
|
|
64
66
|
@property
|
|
65
67
|
def base_setup_controller(self) -> SetupController:
|
|
68
|
+
"""returns the :class:`SetupController` for the setup object of this executor"""
|
|
66
69
|
return SetupController.get_for(self.base_setup_class.__class__)
|
|
67
70
|
|
|
68
71
|
@property
|
|
@@ -4,9 +4,12 @@ from typing import List, Union, TYPE_CHECKING
|
|
|
4
4
|
import sys
|
|
5
5
|
import time
|
|
6
6
|
import traceback
|
|
7
|
-
|
|
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
|
|
8
11
|
from _balder.testresult import ResultState, TestcaseResult
|
|
9
|
-
from _balder.
|
|
12
|
+
from _balder.utils import inspect_method
|
|
10
13
|
|
|
11
14
|
if TYPE_CHECKING:
|
|
12
15
|
from _balder.executor.variation_executor import VariationExecutor
|
|
@@ -14,13 +17,18 @@ if TYPE_CHECKING:
|
|
|
14
17
|
from _balder.scenario import Scenario
|
|
15
18
|
|
|
16
19
|
|
|
17
|
-
class TestcaseExecutor(
|
|
20
|
+
class TestcaseExecutor(BasicExecutableExecutor):
|
|
18
21
|
"""
|
|
19
22
|
A TestcaseExecutor class represents an actual single test that can be executed. It therefore references exactly to a
|
|
20
23
|
test method of a scenario that can be executed on the specific setup this executor belongs to.
|
|
21
24
|
"""
|
|
25
|
+
fixture_execution_level = FixtureExecutionLevel.TESTCASE
|
|
22
26
|
|
|
23
|
-
def __init__(
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
testcase: callable,
|
|
30
|
+
parent: VariationExecutor,
|
|
31
|
+
) -> None:
|
|
24
32
|
super().__init__()
|
|
25
33
|
|
|
26
34
|
self._base_testcase_callable = testcase
|
|
@@ -36,6 +44,13 @@ class TestcaseExecutor(BasicExecutor):
|
|
|
36
44
|
# holds the raw test execution time in seconds
|
|
37
45
|
self.test_execution_time_sec = 0
|
|
38
46
|
|
|
47
|
+
# determine prev_mark IGNORE/SKIP for the testcase
|
|
48
|
+
if self.should_be_skipped():
|
|
49
|
+
self.prev_mark = PreviousExecutorMark.SKIP
|
|
50
|
+
# always overwrite if it should be ignored
|
|
51
|
+
if self.should_be_ignored():
|
|
52
|
+
self.prev_mark = PreviousExecutorMark.IGNORE
|
|
53
|
+
|
|
39
54
|
# ---------------------------------- STATIC METHODS ----------------------------------------------------------------
|
|
40
55
|
|
|
41
56
|
# ---------------------------------- CLASS METHODS ----------------------------------------------------------------
|
|
@@ -73,22 +88,27 @@ class TestcaseExecutor(BasicExecutor):
|
|
|
73
88
|
"""returns the current active fixture manager"""
|
|
74
89
|
return self._fixture_manager
|
|
75
90
|
|
|
91
|
+
@property
|
|
92
|
+
def full_test_name_str(self) -> str:
|
|
93
|
+
"""
|
|
94
|
+
returns the name of the test method, that should be used in output
|
|
95
|
+
"""
|
|
96
|
+
return self._base_testcase_callable.__qualname__
|
|
97
|
+
|
|
76
98
|
# ---------------------------------- PROTECTED METHODS -------------------------------------------------------------
|
|
77
99
|
|
|
78
100
|
def _prepare_execution(self, show_discarded):
|
|
79
|
-
print(f" TEST {self.
|
|
101
|
+
print(f" TEST {self.full_test_name_str} ", end='')
|
|
80
102
|
if self.should_be_skipped():
|
|
81
103
|
self.body_result.set_result(ResultState.SKIP)
|
|
82
104
|
self.execution_time_sec = 0
|
|
83
105
|
print("[S]")
|
|
84
|
-
return
|
|
85
106
|
|
|
86
107
|
def _body_execution(self, show_discarded):
|
|
87
108
|
start_time = time.perf_counter()
|
|
88
109
|
try:
|
|
89
110
|
_, func_type = inspect_method(self.base_testcase_callable)
|
|
90
|
-
all_args = self.
|
|
91
|
-
self, self.base_testcase_obj.__class__, self.base_testcase_callable, func_type)
|
|
111
|
+
all_args = self.get_all_test_method_args()
|
|
92
112
|
if func_type == "staticmethod":
|
|
93
113
|
# testcase is a staticmethod - no special first attribute
|
|
94
114
|
self.base_testcase_callable(**all_args)
|
|
@@ -102,7 +122,7 @@ class TestcaseExecutor(BasicExecutor):
|
|
|
102
122
|
f"`{self.base_testcase_callable.__name__}`")
|
|
103
123
|
|
|
104
124
|
self.body_result.set_result(ResultState.SUCCESS)
|
|
105
|
-
except Exception as exc:
|
|
125
|
+
except Exception as exc: # pylint: disable=broad-exception-caught
|
|
106
126
|
# this has to be a test error
|
|
107
127
|
traceback.print_exception(*sys.exc_info())
|
|
108
128
|
self.body_result.set_result(ResultState.FAILURE, exc)
|
|
@@ -153,3 +173,15 @@ class TestcaseExecutor(BasicExecutor):
|
|
|
153
173
|
if scenario_class in covered_by_dict_resolved.keys():
|
|
154
174
|
all_covered_by_data += covered_by_dict_resolved[scenario_class]
|
|
155
175
|
return all_covered_by_data
|
|
176
|
+
|
|
177
|
+
def get_all_test_method_args(self):
|
|
178
|
+
"""
|
|
179
|
+
returns all kwargs values for the test method
|
|
180
|
+
"""
|
|
181
|
+
_, func_type = inspect_method(self.base_testcase_callable)
|
|
182
|
+
return self.fixture_manager.get_all_attribute_values(
|
|
183
|
+
self,
|
|
184
|
+
self.base_testcase_obj.__class__,
|
|
185
|
+
self.base_testcase_callable,
|
|
186
|
+
func_type
|
|
187
|
+
)
|
|
@@ -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
|