baldertest 0.1.0b7__py3-none-any.whl → 0.1.0b9__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 +13 -1
- _balder/balder_session.py +6 -7
- _balder/collector.py +38 -5
- _balder/connection.py +137 -97
- _balder/controllers/feature_controller.py +38 -17
- _balder/controllers/normal_scenario_setup_controller.py +3 -10
- _balder/executor/basic_executor.py +10 -21
- _balder/executor/executor_tree.py +20 -7
- _balder/executor/scenario_executor.py +7 -2
- _balder/executor/setup_executor.py +38 -3
- _balder/executor/testcase_executor.py +1 -2
- _balder/executor/variation_executor.py +6 -44
- _balder/fixture_manager.py +38 -7
- _balder/routing_path.py +0 -2
- _balder/solver.py +6 -2
- _balder/testresult.py +38 -6
- {baldertest-0.1.0b7.dist-info → baldertest-0.1.0b9.dist-info}/METADATA +1 -1
- {baldertest-0.1.0b7.dist-info → baldertest-0.1.0b9.dist-info}/RECORD +22 -22
- {baldertest-0.1.0b7.dist-info → baldertest-0.1.0b9.dist-info}/WHEEL +1 -1
- {baldertest-0.1.0b7.dist-info → baldertest-0.1.0b9.dist-info}/LICENSE +0 -0
- {baldertest-0.1.0b7.dist-info → baldertest-0.1.0b9.dist-info}/entry_points.txt +0 -0
- {baldertest-0.1.0b7.dist-info → baldertest-0.1.0b9.dist-info}/top_level.txt +0 -0
_balder/_version.py
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
# file generated by setuptools_scm
|
|
2
2
|
# don't change, don't track in version control
|
|
3
|
-
|
|
3
|
+
TYPE_CHECKING = False
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from typing import Tuple, Union
|
|
6
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
|
+
else:
|
|
8
|
+
VERSION_TUPLE = object
|
|
9
|
+
|
|
10
|
+
version: str
|
|
11
|
+
__version__: str
|
|
12
|
+
__version_tuple__: VERSION_TUPLE
|
|
13
|
+
version_tuple: VERSION_TUPLE
|
|
14
|
+
|
|
15
|
+
__version__ = version = '0.1.0b9'
|
|
4
16
|
__version_tuple__ = version_tuple = (0, 1, 0)
|
_balder/balder_session.py
CHANGED
|
@@ -268,11 +268,6 @@ class BalderSession:
|
|
|
268
268
|
|
|
269
269
|
self.parsed_args = self.cmd_arg_parser.parse_args(self._alt_cmd_args)
|
|
270
270
|
|
|
271
|
-
if self.parsed_args.only_with_scenario is not None:
|
|
272
|
-
raise NotImplementedError("the parameter `--only-with-scenario` is not supported in this version of balder")
|
|
273
|
-
if self.parsed_args.only_with_setup is not None:
|
|
274
|
-
raise NotImplementedError("the parameter `--only-with-setup` is not supported in this version of balder")
|
|
275
|
-
|
|
276
271
|
self.working_dir = self.parsed_args.working_dir
|
|
277
272
|
self.collect_only = self.parsed_args.collect_only
|
|
278
273
|
self.resolve_only = self.parsed_args.resolve_only
|
|
@@ -285,7 +280,10 @@ class BalderSession:
|
|
|
285
280
|
"""
|
|
286
281
|
This method collects all data.
|
|
287
282
|
"""
|
|
288
|
-
self.collector.collect(
|
|
283
|
+
self.collector.collect(
|
|
284
|
+
plugin_manager=self.plugin_manager,
|
|
285
|
+
scenario_filter_patterns=self.only_with_scenario,
|
|
286
|
+
setup_filter_patterns=self.only_with_setup)
|
|
289
287
|
|
|
290
288
|
def solve(self):
|
|
291
289
|
"""
|
|
@@ -302,7 +300,8 @@ class BalderSession:
|
|
|
302
300
|
.. note::
|
|
303
301
|
Note that the method creates an :class:`ExecutorTree`, that hasn't to be completely resolved yet.
|
|
304
302
|
"""
|
|
305
|
-
self.executor_tree = self.solver.get_executor_tree(plugin_manager=self.plugin_manager
|
|
303
|
+
self.executor_tree = self.solver.get_executor_tree(plugin_manager=self.plugin_manager,
|
|
304
|
+
add_discarded=self.show_discarded)
|
|
306
305
|
self.plugin_manager.execute_filter_executor_tree(executor_tree=self.executor_tree)
|
|
307
306
|
|
|
308
307
|
def run(self):
|
_balder/collector.py
CHANGED
|
@@ -5,6 +5,7 @@ import os
|
|
|
5
5
|
import sys
|
|
6
6
|
import types
|
|
7
7
|
import logging
|
|
8
|
+
import fnmatch
|
|
8
9
|
import inspect
|
|
9
10
|
import pathlib
|
|
10
11
|
import functools
|
|
@@ -677,27 +678,59 @@ class Collector:
|
|
|
677
678
|
for cur_setup in self.all_setups:
|
|
678
679
|
SetupController.get_for(cur_setup).validate_feature_possibility()
|
|
679
680
|
|
|
680
|
-
def
|
|
681
|
+
def _filter_paths_after_allowed_paths(self, paths: List[pathlib.Path], filter_patterns: List[str]) \
|
|
682
|
+
-> List[pathlib.Path]:
|
|
683
|
+
"""
|
|
684
|
+
This method filters the given list of filepaths for the given filter_patterns. It returns a list with all
|
|
685
|
+
remaining paths that are mathing the filter statements in `filter_paths`.
|
|
686
|
+
|
|
687
|
+
Patterns are the same like for `fnmatch <https://docs.python.org/3/library/fnmatch.html#module-fnmatch>`_.
|
|
688
|
+
|
|
689
|
+
:param paths: a list of all filepaths that should be filtered
|
|
690
|
+
:param filter_patterns: a list of relative filter patterns that should be applied on the files of the classes
|
|
691
|
+
:return: returns all classes of items that match any of the given patterns.
|
|
692
|
+
"""
|
|
693
|
+
remaining = []
|
|
694
|
+
|
|
695
|
+
for cur_pattern in filter_patterns:
|
|
696
|
+
remaining += [cur_abs_path for cur_abs_path in paths
|
|
697
|
+
if fnmatch.fnmatch(str(cur_abs_path.relative_to(self.working_dir)), cur_pattern)]
|
|
698
|
+
return list(set(remaining))
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
def collect(self, plugin_manager: PluginManager, scenario_filter_patterns: Union[List[str], None],
|
|
702
|
+
setup_filter_patterns: Union[List[str], None]):
|
|
681
703
|
"""
|
|
682
704
|
This method manages the entire collection process.
|
|
683
705
|
|
|
684
706
|
:param plugin_manager: contains the reference to the used plugin manager
|
|
707
|
+
:param scenario_filter_patterns: a list with filter patterns for scenarios
|
|
708
|
+
:param setup_filter_patterns: a list with filter patterns for setups
|
|
685
709
|
"""
|
|
686
710
|
# load all py files
|
|
687
711
|
self.load_balderglob_py_file()
|
|
688
712
|
self._all_py_files = self.get_all_py_files()
|
|
689
713
|
self._all_py_files = plugin_manager.execute_modify_collected_pyfiles(self._all_py_files)
|
|
690
714
|
|
|
715
|
+
if scenario_filter_patterns:
|
|
716
|
+
all_scenario_filepaths = self._filter_paths_after_allowed_paths(
|
|
717
|
+
paths=self._all_py_files, filter_patterns=scenario_filter_patterns)
|
|
718
|
+
else:
|
|
719
|
+
all_scenario_filepaths = self._all_py_files
|
|
720
|
+
if setup_filter_patterns:
|
|
721
|
+
all_setup_filepaths = self._filter_paths_after_allowed_paths(
|
|
722
|
+
paths=self._all_py_files, filter_patterns=setup_filter_patterns)
|
|
723
|
+
else:
|
|
724
|
+
all_setup_filepaths = self._all_py_files
|
|
725
|
+
|
|
691
726
|
# collect all `Connection` classes (has to be first, because scenarios/setups can use them)
|
|
692
727
|
self.load_all_connection_classes(py_file_paths=self._all_py_files)
|
|
693
728
|
self._all_connections = self.get_all_connection_classes()
|
|
694
729
|
|
|
695
730
|
# collect all `Scenario` classes
|
|
696
|
-
self._all_scenarios = self.get_all_scenario_classes(
|
|
697
|
-
py_file_paths=self._all_py_files, filter_abstracts=True)
|
|
731
|
+
self._all_scenarios = self.get_all_scenario_classes(py_file_paths=all_scenario_filepaths, filter_abstracts=True)
|
|
698
732
|
# collect all `Setup` classes
|
|
699
|
-
self._all_setups = self.get_all_setup_classes(
|
|
700
|
-
py_file_paths=self._all_py_files, filter_abstracts=True)
|
|
733
|
+
self._all_setups = self.get_all_setup_classes(py_file_paths=all_setup_filepaths, filter_abstracts=True)
|
|
701
734
|
|
|
702
735
|
self._all_scenarios, self._all_setups = plugin_manager.execute_collected_classes(
|
|
703
736
|
scenarios=self._all_scenarios, setups=self._all_setups)
|
_balder/connection.py
CHANGED
|
@@ -88,6 +88,17 @@ class Connection:
|
|
|
88
88
|
all_hashes += hash(cur_child)
|
|
89
89
|
return hash(all_hashes)
|
|
90
90
|
|
|
91
|
+
def clone_without_based_on_elements(self) -> Connection:
|
|
92
|
+
"""
|
|
93
|
+
This method returns a copied version of this element, while all `_based_on_connections` are removed (the copied
|
|
94
|
+
element has an empty list here).
|
|
95
|
+
|
|
96
|
+
:return: a python copied object of this item
|
|
97
|
+
"""
|
|
98
|
+
self_copy = copy.copy(self)
|
|
99
|
+
self_copy._based_on_connections = [] # pylint: disable=protected-access
|
|
100
|
+
return self_copy
|
|
101
|
+
|
|
91
102
|
def clone(self) -> Connection:
|
|
92
103
|
"""
|
|
93
104
|
This method returns an exact clone of this connection. For this clone every inner connection object will be
|
|
@@ -95,18 +106,15 @@ class Connection:
|
|
|
95
106
|
same for this object and the clone). The method will make a normal copy for every connection object in the
|
|
96
107
|
`_based_on_elements` list.
|
|
97
108
|
"""
|
|
98
|
-
self_copy =
|
|
99
|
-
self_copy._based_on_connections = []
|
|
109
|
+
self_copy = self.clone_without_based_on_elements()
|
|
100
110
|
|
|
101
111
|
for cur_based_on in self._based_on_connections:
|
|
102
112
|
if isinstance(cur_based_on, tuple):
|
|
103
|
-
cloned_tuple = ()
|
|
104
|
-
|
|
105
|
-
cloned_tuple = cloned_tuple + (cur_tuple_element.clone(), )
|
|
106
|
-
self_copy._based_on_connections.append(cloned_tuple)
|
|
113
|
+
cloned_tuple = tuple([cur_tuple_element.clone() for cur_tuple_element in cur_based_on])
|
|
114
|
+
self_copy.append_to_based_on(cloned_tuple)
|
|
107
115
|
elif isinstance(cur_based_on, Connection):
|
|
108
116
|
cloned_cur_based_on = cur_based_on.clone()
|
|
109
|
-
self_copy.
|
|
117
|
+
self_copy.append_to_based_on(cloned_cur_based_on)
|
|
110
118
|
else:
|
|
111
119
|
raise TypeError('based on element is not from valid type')
|
|
112
120
|
return self_copy
|
|
@@ -132,22 +140,21 @@ class Connection:
|
|
|
132
140
|
for cur_tuple in Connection.__cut_tuple_from_only_parent_to_child(elem.based_on_elements[0]):
|
|
133
141
|
# for this we do not have to use `clone`, because we copy the base object with `copy.copy` and
|
|
134
142
|
# completely replace the `_based_on_connections` by our own
|
|
135
|
-
copied_conn =
|
|
136
|
-
copied_conn.
|
|
143
|
+
copied_conn = elem.clone_without_based_on_elements()
|
|
144
|
+
copied_conn.append_to_based_on(cur_tuple)
|
|
137
145
|
all_pieces.append(copied_conn)
|
|
138
146
|
return all_pieces
|
|
139
147
|
# if this element is the last element with a parent -> copy it and remove the parent, return it
|
|
140
148
|
if len(elem.based_on_elements[0].based_on_elements) == 0:
|
|
141
|
-
new_elem =
|
|
142
|
-
new_elem._based_on_connections = []
|
|
149
|
+
new_elem = elem.clone_without_based_on_elements()
|
|
143
150
|
all_pieces.append(new_elem)
|
|
144
151
|
return all_pieces
|
|
145
152
|
# otherwise, the current item has grandparents, so call the method recursively for parents and add a copy of
|
|
146
153
|
# this object as child
|
|
147
154
|
all_possible_parents = Connection.__cut_conn_from_only_parent_to_child(elem.based_on_elements[0])
|
|
148
155
|
for cur_parent in all_possible_parents:
|
|
149
|
-
copied_conn =
|
|
150
|
-
copied_conn.
|
|
156
|
+
copied_conn = elem.clone_without_based_on_elements()
|
|
157
|
+
copied_conn.append_to_based_on(cur_parent)
|
|
151
158
|
all_pieces.append(copied_conn)
|
|
152
159
|
return all_pieces
|
|
153
160
|
|
|
@@ -165,15 +172,12 @@ class Connection:
|
|
|
165
172
|
Note that this method also returns every possible ordering
|
|
166
173
|
"""
|
|
167
174
|
|
|
168
|
-
tuple_with_all_possibilities = (
|
|
169
|
-
|
|
170
|
-
tuple_with_all_possibilities += (Connection.__cut_conn_from_only_parent_to_child(cur_tuple_item), )
|
|
175
|
+
tuple_with_all_possibilities = (
|
|
176
|
+
tuple([Connection.__cut_conn_from_only_parent_to_child(cur_tuple_item) for cur_tuple_item in elem]))
|
|
171
177
|
|
|
172
178
|
cloned_tuple_list = []
|
|
173
179
|
for cur_tuple in list(itertools.product(*tuple_with_all_possibilities)):
|
|
174
|
-
cloned_tuple
|
|
175
|
-
for cur_tuple_item in cur_tuple:
|
|
176
|
-
cloned_tuple += (cur_tuple_item.clone(), )
|
|
180
|
+
cloned_tuple = tuple([cur_tuple_item.clone() for cur_tuple_item in cur_tuple])
|
|
177
181
|
cloned_tuple_list.append(cloned_tuple)
|
|
178
182
|
return cloned_tuple_list
|
|
179
183
|
|
|
@@ -215,11 +219,9 @@ class Connection:
|
|
|
215
219
|
while len(cur_sub_other_single) > 0:
|
|
216
220
|
if isinstance(cur_sub_other_single[0], tuple):
|
|
217
221
|
# found a tuple -> check if length does match
|
|
218
|
-
if len(cur_sub_other_single[0]) >= len(cur_tuple_single)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
cur_tuple_single, contained_in_tuple=cur_sub_other_single[0]):
|
|
222
|
-
return True
|
|
222
|
+
if len(cur_sub_other_single[0]) >= len(cur_tuple_single) and tuple_is_contained_in_other(
|
|
223
|
+
cur_tuple_single, contained_in_tuple=cur_sub_other_single[0]):
|
|
224
|
+
return True
|
|
223
225
|
# otherwise, this complete element is not possible - skip this single!
|
|
224
226
|
break
|
|
225
227
|
cur_sub_other_single = cur_sub_other_single[0].based_on_elements
|
|
@@ -371,6 +373,75 @@ class Connection:
|
|
|
371
373
|
this_instance.append_to_based_on(*new_items)
|
|
372
374
|
return this_instance
|
|
373
375
|
|
|
376
|
+
@classmethod
|
|
377
|
+
def check_equal_connections_are_in(
|
|
378
|
+
cls, cnns_from: List[Connection], are_in_cnns_from: List[Connection], ignore_metadata: bool=False) -> bool:
|
|
379
|
+
"""
|
|
380
|
+
This method validates that the elements from the first list are contained (are equal) with one of the elements
|
|
381
|
+
in the second list.
|
|
382
|
+
|
|
383
|
+
.. note::
|
|
384
|
+
This method only checks that every single connections from the first element is contained in the second too.
|
|
385
|
+
It does not check the other direction. If you want to validate this, you need to call this method with both
|
|
386
|
+
possibilities.
|
|
387
|
+
|
|
388
|
+
:param cnns_from: the first list of connections
|
|
389
|
+
:param are_in_cnns_from: the second list of connection
|
|
390
|
+
:param ignore_metadata: True, if the metadata of the single connections should be ignored
|
|
391
|
+
:return: True in case that every connection of the first list is equal with one in the second list, otherwise
|
|
392
|
+
False
|
|
393
|
+
"""
|
|
394
|
+
for cur_cnn in cnns_from:
|
|
395
|
+
found_equal = False
|
|
396
|
+
for cur_other_cnn in are_in_cnns_from:
|
|
397
|
+
if cur_cnn.equal_with(cur_other_cnn, ignore_metadata=ignore_metadata):
|
|
398
|
+
found_equal = True
|
|
399
|
+
break
|
|
400
|
+
if not found_equal:
|
|
401
|
+
return False
|
|
402
|
+
return True
|
|
403
|
+
|
|
404
|
+
@classmethod
|
|
405
|
+
def check_equal_connection_tuples_are_in(
|
|
406
|
+
cls, tuples_from: List[Tuple[Connection]], are_in_tuples_from: List[Tuple[Connection]],
|
|
407
|
+
ignore_metadata: bool=False) -> bool:
|
|
408
|
+
"""
|
|
409
|
+
This method validates that the connection tuples from the first list are contained (are equal) within the tuple
|
|
410
|
+
elements of the second list.
|
|
411
|
+
|
|
412
|
+
.. note::
|
|
413
|
+
This method only checks that every single tuple from the first element is contained in the second list.
|
|
414
|
+
It does not check the other direction. If you want to validate this, you need to call this method with both
|
|
415
|
+
possibilities.
|
|
416
|
+
|
|
417
|
+
:param tuples_from: the first list of connection-tuples
|
|
418
|
+
:param are_in_tuples_from: the second list of connection-tuples
|
|
419
|
+
:param ignore_metadata: True, if the metadata of the single connections should be ignored
|
|
420
|
+
:return: True in case that every tuple connections of the first list is equal with one tuple in the second,
|
|
421
|
+
otherwise False
|
|
422
|
+
"""
|
|
423
|
+
for cur_search_tuple in tuples_from:
|
|
424
|
+
found_match_for_cur_search_tuple = False
|
|
425
|
+
# go through each unmatched other tuple
|
|
426
|
+
for cur_other_tuple in are_in_tuples_from:
|
|
427
|
+
cur_search_tuple_is_completely_in_other_tuple = True
|
|
428
|
+
for cur_search_tuple_elem in cur_search_tuple:
|
|
429
|
+
fount_it = False
|
|
430
|
+
for cur_other_tuple_elem in cur_other_tuple:
|
|
431
|
+
if cur_search_tuple_elem.equal_with(cur_other_tuple_elem, ignore_metadata=ignore_metadata):
|
|
432
|
+
fount_it = True
|
|
433
|
+
break
|
|
434
|
+
if not fount_it:
|
|
435
|
+
cur_search_tuple_is_completely_in_other_tuple = False
|
|
436
|
+
break
|
|
437
|
+
if cur_search_tuple_is_completely_in_other_tuple:
|
|
438
|
+
# here we have no match
|
|
439
|
+
found_match_for_cur_search_tuple = True
|
|
440
|
+
break
|
|
441
|
+
if not found_match_for_cur_search_tuple:
|
|
442
|
+
return False
|
|
443
|
+
return True
|
|
444
|
+
|
|
374
445
|
# ---------------------------------- PROPERTIES --------------------------------------------------------------------
|
|
375
446
|
|
|
376
447
|
@property
|
|
@@ -484,7 +555,7 @@ class Connection:
|
|
|
484
555
|
|
|
485
556
|
return False
|
|
486
557
|
|
|
487
|
-
def
|
|
558
|
+
def get_intersection_with_other_single(self, other_conn: Union[Connection, Tuple[Connection]]) \
|
|
488
559
|
-> List[Connection, Tuple[Connection]]:
|
|
489
560
|
"""
|
|
490
561
|
A helper method that returns an intersection between the two connections (self and the given one).
|
|
@@ -519,27 +590,23 @@ class Connection:
|
|
|
519
590
|
|
|
520
591
|
intersection = []
|
|
521
592
|
|
|
593
|
+
def determine_for(pieces: List[Union[Connection, Tuple[Connection]]], in_other_cnn: Connection):
|
|
594
|
+
for cur_piece in pieces:
|
|
595
|
+
if isinstance(cur_piece, Connection):
|
|
596
|
+
if cur_piece.contained_in(in_other_cnn, ignore_metadata=True):
|
|
597
|
+
intersection.append(cur_piece)
|
|
598
|
+
else:
|
|
599
|
+
# isinstance of tuple
|
|
600
|
+
if Connection.check_if_tuple_contained_in_connection(cur_piece, in_other_cnn):
|
|
601
|
+
intersection.append(cur_piece)
|
|
602
|
+
|
|
522
603
|
#: check if some sub elements of self connection are contained in `other_conn`
|
|
523
604
|
self_pieces = self.cut_into_all_possible_subtrees()
|
|
524
|
-
|
|
525
|
-
if isinstance(cur_piece, Connection):
|
|
526
|
-
if cur_piece.contained_in(other_conn, ignore_metadata=True):
|
|
527
|
-
intersection.append(cur_piece)
|
|
528
|
-
else:
|
|
529
|
-
# isinstance of tuple
|
|
530
|
-
if Connection.check_if_tuple_contained_in_connection(cur_piece, other_conn):
|
|
531
|
-
intersection.append(cur_piece)
|
|
605
|
+
determine_for(pieces=self_pieces, in_other_cnn=other_conn)
|
|
532
606
|
|
|
533
607
|
#: check if some sub elements of `other_conn` are contained in self connection
|
|
534
608
|
other_pieces = other_conn.cut_into_all_possible_subtrees()
|
|
535
|
-
|
|
536
|
-
if isinstance(cur_piece, Connection):
|
|
537
|
-
if cur_piece.contained_in(self, ignore_metadata=True):
|
|
538
|
-
intersection.append(cur_piece)
|
|
539
|
-
else:
|
|
540
|
-
# isinstance of tuple
|
|
541
|
-
if Connection.check_if_tuple_contained_in_connection(cur_piece, self):
|
|
542
|
-
intersection.append(cur_piece)
|
|
609
|
+
determine_for(pieces=other_pieces, in_other_cnn=self)
|
|
543
610
|
|
|
544
611
|
#: filter all duplicated (and contained in each other) connections
|
|
545
612
|
intersection_without_duplicates = []
|
|
@@ -779,19 +846,19 @@ class Connection:
|
|
|
779
846
|
method returns this child connection directly without any :class:`Connection` container otherwise the
|
|
780
847
|
:class:`Connection` container class with all resolved child classes will be returned.
|
|
781
848
|
"""
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
849
|
+
if self.is_resolved():
|
|
850
|
+
copied_base = self.clone()
|
|
851
|
+
else:
|
|
852
|
+
copied_base = self.clone_without_based_on_elements()
|
|
853
|
+
|
|
785
854
|
if self.__class__ == Connection:
|
|
786
855
|
# the base object is a container Connection - iterate over the items and determine the values for them
|
|
787
856
|
for cur_item in self.based_on_elements.copy():
|
|
788
857
|
if isinstance(cur_item, tuple):
|
|
789
|
-
new_tuple = ()
|
|
790
|
-
|
|
791
|
-
new_tuple += (cur_tuple_item.get_resolved(), )
|
|
792
|
-
copied_base._based_on_connections.append(new_tuple)
|
|
858
|
+
new_tuple = tuple([cur_tuple_item.get_resolved() for cur_tuple_item in cur_item])
|
|
859
|
+
copied_base.append_to_based_on(new_tuple)
|
|
793
860
|
else:
|
|
794
|
-
copied_base.
|
|
861
|
+
copied_base.append_to_based_on(cur_item.get_resolved())
|
|
795
862
|
else:
|
|
796
863
|
for next_higher_parent in self.based_on_elements:
|
|
797
864
|
if isinstance(next_higher_parent, tuple):
|
|
@@ -811,21 +878,20 @@ class Connection:
|
|
|
811
878
|
# resolve the opportunities and create multiple possible tuples where all elements are direct
|
|
812
879
|
# parents
|
|
813
880
|
for cur_possibility in itertools.product(*direct_ancestors_tuple):
|
|
814
|
-
new_child_tuple = (
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
copied_base._based_on_connections.append(new_child_tuple)
|
|
881
|
+
new_child_tuple = (
|
|
882
|
+
tuple([cur_tuple_element.get_resolved() for cur_tuple_element in cur_possibility]))
|
|
883
|
+
copied_base.append_to_based_on(new_child_tuple)
|
|
818
884
|
else:
|
|
819
885
|
if next_higher_parent.__class__ in self.__class__.get_parents():
|
|
820
886
|
# is already a direct parent
|
|
821
|
-
copied_base.
|
|
887
|
+
copied_base.append_to_based_on(next_higher_parent.get_resolved())
|
|
822
888
|
else:
|
|
823
889
|
# only add the first level of direct parents - deeper will be added by recursively call of
|
|
824
890
|
# `get_resolved`
|
|
825
891
|
for cur_self_direct_parent in self.__class__.get_parents():
|
|
826
892
|
if next_higher_parent.__class__.is_parent_of(cur_self_direct_parent):
|
|
827
893
|
new_child = cur_self_direct_parent.based_on(next_higher_parent)
|
|
828
|
-
copied_base.
|
|
894
|
+
copied_base.append_to_based_on(new_child.get_resolved())
|
|
829
895
|
# if it is a connection container, where only one element exists that is no tuple -> return this directly
|
|
830
896
|
# instead of the container
|
|
831
897
|
if copied_base.__class__ == Connection and len(copied_base.based_on_elements) == 1 and not \
|
|
@@ -865,8 +931,8 @@ class Connection:
|
|
|
865
931
|
return [resolved_obj]
|
|
866
932
|
for cur_child in resolved_obj.based_on_elements:
|
|
867
933
|
for cur_single_child in cur_child.get_singles():
|
|
868
|
-
copied_base =
|
|
869
|
-
copied_base.
|
|
934
|
+
copied_base = resolved_obj.clone_without_based_on_elements()
|
|
935
|
+
copied_base.append_to_based_on(cur_single_child)
|
|
870
936
|
all_singles.append(copied_base)
|
|
871
937
|
# convert all tuple objects in a connection object and set metadata of self object
|
|
872
938
|
cleaned_singles = []
|
|
@@ -979,40 +1045,6 @@ class Connection:
|
|
|
979
1045
|
resolved_self = self.get_resolved()
|
|
980
1046
|
resolved_other = other_conn.get_resolved()
|
|
981
1047
|
|
|
982
|
-
def check_elems_if(elems_from, are_in_elems_from):
|
|
983
|
-
for cur_elem in elems_from:
|
|
984
|
-
found_equal = False
|
|
985
|
-
for cur_other_elem in are_in_elems_from:
|
|
986
|
-
if cur_elem.equal_with(cur_other_elem, ignore_metadata=ignore_metadata):
|
|
987
|
-
found_equal = True
|
|
988
|
-
break
|
|
989
|
-
if not found_equal:
|
|
990
|
-
return False
|
|
991
|
-
return True
|
|
992
|
-
|
|
993
|
-
def check_tuples_if(tuples_from, are_in_tuples_from):
|
|
994
|
-
for cur_search_tuple in tuples_from:
|
|
995
|
-
found_match_for_cur_search_tuple = False
|
|
996
|
-
# go through each unmatched other tuple
|
|
997
|
-
for cur_other_tuple in are_in_tuples_from:
|
|
998
|
-
cur_search_tuple_is_completely_in_other_tuple = True
|
|
999
|
-
for cur_search_tuple_elem in cur_search_tuple:
|
|
1000
|
-
fount_it = False
|
|
1001
|
-
for cur_other_tuple_elem in cur_other_tuple:
|
|
1002
|
-
if cur_search_tuple_elem.equal_with(cur_other_tuple_elem, ignore_metadata=ignore_metadata):
|
|
1003
|
-
fount_it = True
|
|
1004
|
-
break
|
|
1005
|
-
if not fount_it:
|
|
1006
|
-
cur_search_tuple_is_completely_in_other_tuple = False
|
|
1007
|
-
break
|
|
1008
|
-
if cur_search_tuple_is_completely_in_other_tuple:
|
|
1009
|
-
# here we have no match
|
|
1010
|
-
found_match_for_cur_search_tuple = True
|
|
1011
|
-
break
|
|
1012
|
-
if not found_match_for_cur_search_tuple:
|
|
1013
|
-
return False
|
|
1014
|
-
return True
|
|
1015
|
-
|
|
1016
1048
|
self_based_on_elems = [
|
|
1017
1049
|
cur_elem for cur_elem in resolved_self.based_on_elements if isinstance(cur_elem, Connection)]
|
|
1018
1050
|
self_based_on_tuples = [
|
|
@@ -1023,13 +1055,20 @@ class Connection:
|
|
|
1023
1055
|
cur_elem for cur_elem in resolved_other.based_on_elements if isinstance(cur_elem, tuple)]
|
|
1024
1056
|
|
|
1025
1057
|
# check single connection elements (if they match all in both directions)
|
|
1026
|
-
if not
|
|
1027
|
-
|
|
1058
|
+
if (not self.__class__.check_equal_connections_are_in(
|
|
1059
|
+
cnns_from=self_based_on_elems, are_in_cnns_from=other_based_on_elems, ignore_metadata=ignore_metadata)
|
|
1060
|
+
or not self.__class__.check_equal_connections_are_in(
|
|
1061
|
+
cnns_from=other_based_on_elems, are_in_cnns_from=self_based_on_elems,
|
|
1062
|
+
ignore_metadata=ignore_metadata)):
|
|
1028
1063
|
return False
|
|
1029
1064
|
|
|
1030
1065
|
# check tuple connection elements (if they match all in both directions)
|
|
1031
|
-
if not
|
|
1032
|
-
|
|
1066
|
+
if (not self.__class__.check_equal_connection_tuples_are_in(
|
|
1067
|
+
tuples_from=self_based_on_tuples, are_in_tuples_from=other_based_on_tuples,
|
|
1068
|
+
ignore_metadata=ignore_metadata)
|
|
1069
|
+
or not self.__class__.check_equal_connection_tuples_are_in(
|
|
1070
|
+
tuples_from=other_based_on_tuples, are_in_tuples_from=self_based_on_tuples,
|
|
1071
|
+
ignore_metadata=ignore_metadata)):
|
|
1033
1072
|
return False
|
|
1034
1073
|
|
|
1035
1074
|
return True
|
|
@@ -1208,7 +1247,7 @@ class Connection:
|
|
|
1208
1247
|
# determine intersections between all of these single components
|
|
1209
1248
|
for cur_self_conn in self_conn_singles:
|
|
1210
1249
|
for cur_other_conn in other_conn_singles:
|
|
1211
|
-
for cur_intersection in cur_self_conn.
|
|
1250
|
+
for cur_intersection in cur_self_conn.get_intersection_with_other_single(cur_other_conn):
|
|
1212
1251
|
if isinstance(cur_intersection, tuple):
|
|
1213
1252
|
cur_intersection = Connection.based_on(cur_intersection)
|
|
1214
1253
|
if cur_intersection not in intersections:
|
|
@@ -1236,7 +1275,8 @@ class Connection:
|
|
|
1236
1275
|
|
|
1237
1276
|
return intersection_filtered[0].clone()
|
|
1238
1277
|
|
|
1239
|
-
def append_to_based_on(
|
|
1278
|
+
def append_to_based_on(
|
|
1279
|
+
self, *args: Union[Connection, Type[Connection], Tuple[Union[Type[Connection], Connection]]]) -> None:
|
|
1240
1280
|
"""
|
|
1241
1281
|
with this method you can extend the internal based_on list with the transferred elements. Any number of
|
|
1242
1282
|
:meth:`Connection` objects or tuples with :meth:`Connection` objects can be given to this method
|
|
@@ -129,6 +129,39 @@ class FeatureController(Controller):
|
|
|
129
129
|
return [Connection()]
|
|
130
130
|
return intersection
|
|
131
131
|
|
|
132
|
+
def _determine_all_theoretically_unordered_method_variations(
|
|
133
|
+
self, of_method_name: str, for_vdevice: Type[VDevice],
|
|
134
|
+
with_connection: Union[Connection, Tuple[Connection]]) -> Dict[Callable, Connection]:
|
|
135
|
+
"""
|
|
136
|
+
This method returns all theoretically matching method variations. It returns more than one, if there are
|
|
137
|
+
multiple method variation for the given VDevice in this feature, where the given connection is part of the
|
|
138
|
+
connection described by the method variation.
|
|
139
|
+
|
|
140
|
+
:param of_method_name: the name of the method that should be returned
|
|
141
|
+
:param for_vdevice: the VDevice that is mapped
|
|
142
|
+
:param with_connection: the connection that is used between the device that uses the related feature and the
|
|
143
|
+
VDevice
|
|
144
|
+
:return: a dictionary that holds all available method variation that matches here
|
|
145
|
+
"""
|
|
146
|
+
all_possible_method_variations = {}
|
|
147
|
+
for cur_impl_method, cur_method_impl_dict in self.get_method_based_for_vdevice()[of_method_name].items():
|
|
148
|
+
if for_vdevice in cur_method_impl_dict.keys():
|
|
149
|
+
cur_impl_method_cnns = []
|
|
150
|
+
for cur_cnn in cur_method_impl_dict[for_vdevice]:
|
|
151
|
+
cur_impl_method_cnns += cur_cnn.get_singles()
|
|
152
|
+
for cur_single_impl_method_cnn in cur_impl_method_cnns:
|
|
153
|
+
if cur_single_impl_method_cnn.contained_in(with_connection, ignore_metadata=True):
|
|
154
|
+
# this variation is possible
|
|
155
|
+
# ADD IT if it is not available yet
|
|
156
|
+
if cur_impl_method not in all_possible_method_variations.keys():
|
|
157
|
+
all_possible_method_variations[cur_impl_method] = cur_single_impl_method_cnn
|
|
158
|
+
# COMBINE IT if it is already available
|
|
159
|
+
else:
|
|
160
|
+
all_possible_method_variations[cur_impl_method] = Connection.based_on(
|
|
161
|
+
all_possible_method_variations[cur_impl_method], cur_single_impl_method_cnn)
|
|
162
|
+
return all_possible_method_variations
|
|
163
|
+
|
|
164
|
+
|
|
132
165
|
# ---------------------------------- METHODS -----------------------------------------------------------------------
|
|
133
166
|
|
|
134
167
|
def get_class_based_for_vdevice(self) -> Union[Dict[Type[VDevice], List[Connection]], None]:
|
|
@@ -321,24 +354,10 @@ class FeatureController(Controller):
|
|
|
321
354
|
raise ValueError(f"can not find the method `{of_method_name}` in method variation data dictionary")
|
|
322
355
|
|
|
323
356
|
# first determine all possible method-variations
|
|
324
|
-
all_possible_method_variations =
|
|
325
|
-
|
|
326
|
-
if for_vdevice in cur_method_impl_dict.keys():
|
|
327
|
-
cur_impl_method_cnns = []
|
|
328
|
-
for cur_cnn in cur_method_impl_dict[for_vdevice]:
|
|
329
|
-
cur_impl_method_cnns += cur_cnn.get_singles()
|
|
330
|
-
for cur_single_impl_method_cnn in cur_impl_method_cnns:
|
|
331
|
-
if cur_single_impl_method_cnn.contained_in(with_connection, ignore_metadata=True):
|
|
332
|
-
# this variation is possible
|
|
333
|
-
# ADD IT if it is not available yet
|
|
334
|
-
if cur_impl_method not in all_possible_method_variations.keys():
|
|
335
|
-
all_possible_method_variations[cur_impl_method] = cur_single_impl_method_cnn
|
|
336
|
-
# COMBINE IT if it is already available
|
|
337
|
-
else:
|
|
338
|
-
all_possible_method_variations[cur_impl_method] = Connection.based_on(
|
|
339
|
-
all_possible_method_variations[cur_impl_method], cur_single_impl_method_cnn)
|
|
357
|
+
all_possible_method_variations = self._determine_all_theoretically_unordered_method_variations(
|
|
358
|
+
of_method_name=of_method_name, for_vdevice=for_vdevice, with_connection=with_connection)
|
|
340
359
|
|
|
341
|
-
#
|
|
360
|
+
# there are no method variations in this feature directly -> check parent classes
|
|
342
361
|
if len(all_possible_method_variations) == 0:
|
|
343
362
|
# try to execute this method in parent classes
|
|
344
363
|
for cur_base in self.related_cls.__bases__:
|
|
@@ -355,9 +374,11 @@ class FeatureController(Controller):
|
|
|
355
374
|
f"and usable connection `{with_connection.get_tree_str()}´")
|
|
356
375
|
return None
|
|
357
376
|
|
|
377
|
+
# we only have one -> selection is clear
|
|
358
378
|
if len(all_possible_method_variations) == 1:
|
|
359
379
|
return list(all_possible_method_variations.keys())[0]
|
|
360
380
|
|
|
381
|
+
# if there are more than one possible method variation, try to sort them hierarchical
|
|
361
382
|
# we have to determine the outer one
|
|
362
383
|
length_before = None
|
|
363
384
|
while length_before is None or length_before != len(all_possible_method_variations):
|
|
@@ -188,7 +188,7 @@ class NormalScenarioSetupController(Controller, ABC):
|
|
|
188
188
|
|
|
189
189
|
devices = self.get_all_inner_device_classes()
|
|
190
190
|
abs_parent_devices = parent_scenario_or_setup_controller.get_all_abs_inner_device_classes()
|
|
191
|
-
|
|
191
|
+
abs_parent_devices_by_name = {cur_parent.__name__: cur_parent for cur_parent in abs_parent_devices}
|
|
192
192
|
|
|
193
193
|
if len(devices) == 0:
|
|
194
194
|
# ignore it because cur item has no own device definitions
|
|
@@ -197,10 +197,7 @@ class NormalScenarioSetupController(Controller, ABC):
|
|
|
197
197
|
# check that a device is newly defined or has the same name as the parent device
|
|
198
198
|
for cur_item_device in devices:
|
|
199
199
|
# check if name exists in parent
|
|
200
|
-
relevant_parent_according_naming = None
|
|
201
|
-
if cur_item_device.__name__ in abs_parent_devices_as_names:
|
|
202
|
-
relevant_parent_according_naming = \
|
|
203
|
-
abs_parent_devices[abs_parent_devices_as_names.index(cur_item_device.__name__)]
|
|
200
|
+
relevant_parent_according_naming = abs_parent_devices_by_name.get(cur_item_device.__name__, None)
|
|
204
201
|
|
|
205
202
|
# check if device is inherited from a parent
|
|
206
203
|
relevant_parent_device_according_inheritance = None
|
|
@@ -236,11 +233,7 @@ class NormalScenarioSetupController(Controller, ABC):
|
|
|
236
233
|
|
|
237
234
|
# secure that all parent devices are implemented here too
|
|
238
235
|
for cur_parent in abs_parent_devices:
|
|
239
|
-
found_parent =
|
|
240
|
-
for cur_item_device in devices:
|
|
241
|
-
if issubclass(cur_item_device, cur_parent):
|
|
242
|
-
found_parent = True
|
|
243
|
-
break
|
|
236
|
+
found_parent = len([dev for dev in devices if issubclass(dev, cur_parent)]) > 0
|
|
244
237
|
if not found_parent:
|
|
245
238
|
raise DeviceOverwritingError(
|
|
246
239
|
f"found a device `{cur_parent.__qualname__}` which is part of a parent class, but it is "
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import time
|
|
4
|
-
from typing import List,
|
|
4
|
+
from typing import List, Union, Type, TYPE_CHECKING
|
|
5
5
|
|
|
6
6
|
import sys
|
|
7
7
|
import types
|
|
8
8
|
import traceback
|
|
9
9
|
from abc import ABC, abstractmethod
|
|
10
|
-
from _balder.testresult import FixturePartResult, ResultState
|
|
10
|
+
from _balder.testresult import FixturePartResult, ResultState, ResultSummary
|
|
11
11
|
from _balder.previous_executor_mark import PreviousExecutorMark
|
|
12
12
|
from _balder.testresult import TestcaseResult
|
|
13
13
|
|
|
@@ -158,8 +158,7 @@ class BasicExecutor(ABC):
|
|
|
158
158
|
if cur_child.has_runnable_tests(consider_discarded_too):
|
|
159
159
|
return True
|
|
160
160
|
return False
|
|
161
|
-
|
|
162
|
-
return True
|
|
161
|
+
return True
|
|
163
162
|
|
|
164
163
|
def get_all_base_instances_of_this_branch(
|
|
165
164
|
self, with_type: Union[Type[Setup], Type[Scenario], Type[types.FunctionType]],
|
|
@@ -193,10 +192,12 @@ class BasicExecutor(ABC):
|
|
|
193
192
|
return list(set(result))
|
|
194
193
|
|
|
195
194
|
@abstractmethod
|
|
196
|
-
def cleanup_empty_executor_branches(self):
|
|
195
|
+
def cleanup_empty_executor_branches(self, consider_discarded=False):
|
|
197
196
|
"""
|
|
198
197
|
This method searches the whole tree and removes branches where an executor item has no own children. It can
|
|
199
198
|
remove these branches, because they have no valid matchings.
|
|
199
|
+
|
|
200
|
+
:param consider_discarded: true if this method should consider discarded branches, otherwise False
|
|
200
201
|
"""
|
|
201
202
|
|
|
202
203
|
def filter_tree_for_user_filters(self):
|
|
@@ -206,31 +207,19 @@ class BasicExecutor(ABC):
|
|
|
206
207
|
for cur_child_executor in self.all_child_executors:
|
|
207
208
|
cur_child_executor.filter_tree_for_user_filters()
|
|
208
209
|
|
|
209
|
-
def testsummary(self) ->
|
|
210
|
+
def testsummary(self) -> ResultSummary:
|
|
210
211
|
"""
|
|
211
212
|
returns a dictionary with all possible :class:`ResultState` as keys and the number of times they have occurred
|
|
212
213
|
in this branch as values
|
|
213
214
|
"""
|
|
214
215
|
|
|
215
|
-
summary =
|
|
216
|
-
ResultState.NOT_RUN: 0,
|
|
217
|
-
ResultState.FAILURE: 0,
|
|
218
|
-
ResultState.ERROR: 0,
|
|
219
|
-
ResultState.SUCCESS: 0,
|
|
220
|
-
ResultState.SKIP: 0,
|
|
221
|
-
ResultState.COVERED_BY: 0
|
|
222
|
-
}
|
|
216
|
+
summary = ResultSummary()
|
|
223
217
|
|
|
224
218
|
if isinstance(self.body_result, TestcaseResult):
|
|
225
|
-
summary
|
|
219
|
+
setattr(summary, self.executor_result.value, 1)
|
|
226
220
|
else:
|
|
227
221
|
for cur_child_exec in self.all_child_executors:
|
|
228
|
-
|
|
229
|
-
for cur_key in cur_child_dict.keys():
|
|
230
|
-
if cur_key in summary:
|
|
231
|
-
summary[cur_key] += cur_child_dict[cur_key]
|
|
232
|
-
else:
|
|
233
|
-
summary[cur_key] = cur_child_dict[cur_key]
|
|
222
|
+
summary += cur_child_exec.testsummary()
|
|
234
223
|
return summary
|
|
235
224
|
|
|
236
225
|
def execute(self, show_discarded=False):
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
from typing import Union, List, Type, TYPE_CHECKING
|
|
3
3
|
|
|
4
|
+
from dataclasses import fields
|
|
4
5
|
from _balder.executor.setup_executor import SetupExecutor
|
|
5
6
|
from _balder.executor.basic_executor import BasicExecutor
|
|
6
7
|
from _balder.fixture_manager import FixtureManager
|
|
7
|
-
from _balder.testresult import ResultState, BranchBodyResult
|
|
8
|
+
from _balder.testresult import ResultState, BranchBodyResult, ResultSummary
|
|
8
9
|
from _balder.previous_executor_mark import PreviousExecutorMark
|
|
9
10
|
|
|
10
11
|
if TYPE_CHECKING:
|
|
@@ -55,7 +56,8 @@ class ExecutorTree(BasicExecutor):
|
|
|
55
56
|
# ---------------------------------- PROTECTED METHODS -------------------------------------------------------------
|
|
56
57
|
|
|
57
58
|
def _prepare_execution(self, show_discarded):
|
|
58
|
-
|
|
59
|
+
if not show_discarded:
|
|
60
|
+
self.update_inner_feature_reference_in_all_setups()
|
|
59
61
|
|
|
60
62
|
def _body_execution(self, show_discarded):
|
|
61
63
|
for cur_setup_executor in self.get_setup_executors():
|
|
@@ -129,16 +131,26 @@ class ExecutorTree(BasicExecutor):
|
|
|
129
131
|
# can not find some
|
|
130
132
|
return None
|
|
131
133
|
|
|
132
|
-
def cleanup_empty_executor_branches(self):
|
|
134
|
+
def cleanup_empty_executor_branches(self, consider_discarded=False):
|
|
133
135
|
to_remove_executor = []
|
|
134
136
|
for cur_setup_executor in self.get_setup_executors():
|
|
135
|
-
cur_setup_executor.cleanup_empty_executor_branches()
|
|
137
|
+
cur_setup_executor.cleanup_empty_executor_branches(consider_discarded=consider_discarded)
|
|
136
138
|
if len(cur_setup_executor.get_scenario_executors()) == 0:
|
|
137
139
|
# remove this whole executor because it has no children anymore
|
|
138
140
|
to_remove_executor.append(cur_setup_executor)
|
|
139
141
|
for cur_setup_executor in to_remove_executor:
|
|
140
142
|
self._setup_executors.remove(cur_setup_executor)
|
|
141
143
|
|
|
144
|
+
def update_inner_feature_reference_in_all_setups(self):
|
|
145
|
+
"""
|
|
146
|
+
This method iterates over all setups and triggers the method that updates the inner feature references.
|
|
147
|
+
|
|
148
|
+
This needs to be done on ExecutorTree level, because this reference should also be accessible within session
|
|
149
|
+
fixtures (in case they have setup scope).
|
|
150
|
+
"""
|
|
151
|
+
for cur_setup_executor in self.get_setup_executors():
|
|
152
|
+
cur_setup_executor.update_inner_referenced_feature_instances()
|
|
153
|
+
|
|
142
154
|
def execute(self, show_discarded=False) -> None:
|
|
143
155
|
"""
|
|
144
156
|
This method executes this branch of the tree
|
|
@@ -163,12 +175,12 @@ class ExecutorTree(BasicExecutor):
|
|
|
163
175
|
print_line(end_text)
|
|
164
176
|
summary = self.testsummary()
|
|
165
177
|
is_first = True
|
|
166
|
-
for
|
|
178
|
+
for cur_field in fields(ResultSummary):
|
|
167
179
|
if is_first:
|
|
168
180
|
is_first = False
|
|
169
181
|
else:
|
|
170
182
|
print(" | ", end="")
|
|
171
|
-
print(f"TOTAL {
|
|
183
|
+
print(f"TOTAL {cur_field.name}: {getattr(summary, cur_field.name)}", end="")
|
|
172
184
|
print("")
|
|
173
185
|
|
|
174
186
|
def print_tree(self, show_discarded=False) -> None:
|
|
@@ -192,7 +204,8 @@ class ExecutorTree(BasicExecutor):
|
|
|
192
204
|
for cur_key, cur_val in mapping_printings.items():
|
|
193
205
|
print(("{} {:<" + str(max_len) + "} = {}").format(start_char, cur_key, cur_val))
|
|
194
206
|
for cur_testcase_excutor in cur_variation_executor.get_testcase_executors():
|
|
195
|
-
print(f"{start_char} -> Testcase<
|
|
207
|
+
print(f"{start_char} -> Testcase<"
|
|
208
|
+
f"{cur_testcase_excutor.base_testcase_callable.__qualname__}>")
|
|
196
209
|
if cur_variation_executor.prev_mark == PreviousExecutorMark.DISCARDED:
|
|
197
210
|
print(f"{start_char}")
|
|
198
211
|
print(f"{start_char} DISCARDED BECAUSE "
|
|
@@ -6,6 +6,7 @@ from _balder.utils import get_class_that_defines_method
|
|
|
6
6
|
from _balder.executor.basic_executor import BasicExecutor
|
|
7
7
|
from _balder.executor.variation_executor import VariationExecutor
|
|
8
8
|
from _balder.previous_executor_mark import PreviousExecutorMark
|
|
9
|
+
from _balder.controllers.scenario_controller import ScenarioController
|
|
9
10
|
|
|
10
11
|
if TYPE_CHECKING:
|
|
11
12
|
from _balder.scenario import Scenario
|
|
@@ -64,6 +65,10 @@ class ScenarioExecutor(BasicExecutor):
|
|
|
64
65
|
"""returns the :class:`Scenario` class that belongs to this executor"""
|
|
65
66
|
return self._base_scenario_class
|
|
66
67
|
|
|
68
|
+
@property
|
|
69
|
+
def base_scenario_controller(self) -> ScenarioController:
|
|
70
|
+
return ScenarioController.get_for(self.base_scenario_class.__class__)
|
|
71
|
+
|
|
67
72
|
@property
|
|
68
73
|
def fixture_manager(self) -> FixtureManager:
|
|
69
74
|
"""returns the current active fixture manager that belongs to this scenario executor"""
|
|
@@ -116,12 +121,12 @@ class ScenarioExecutor(BasicExecutor):
|
|
|
116
121
|
if cur_executor.prev_mark != PreviousExecutorMark.DISCARDED]
|
|
117
122
|
return self._variation_executors
|
|
118
123
|
|
|
119
|
-
def cleanup_empty_executor_branches(self):
|
|
124
|
+
def cleanup_empty_executor_branches(self, consider_discarded=False):
|
|
120
125
|
"""
|
|
121
126
|
This method removes all sub executors that are empty and not relevant anymore.
|
|
122
127
|
"""
|
|
123
128
|
to_remove_executor = []
|
|
124
|
-
for cur_variation_executor in self.get_variation_executors():
|
|
129
|
+
for cur_variation_executor in self.get_variation_executors(return_discarded=consider_discarded):
|
|
125
130
|
if len(cur_variation_executor.get_testcase_executors()) == 0:
|
|
126
131
|
# remove this whole executor because it has no children anymore
|
|
127
132
|
to_remove_executor.append(cur_variation_executor)
|
|
@@ -5,6 +5,9 @@ from _balder.testresult import ResultState, BranchBodyResult
|
|
|
5
5
|
from _balder.executor.basic_executor import BasicExecutor
|
|
6
6
|
from _balder.executor.scenario_executor import ScenarioExecutor
|
|
7
7
|
from _balder.previous_executor_mark import PreviousExecutorMark
|
|
8
|
+
from _balder.controllers.setup_controller import SetupController
|
|
9
|
+
from _balder.controllers.device_controller import DeviceController
|
|
10
|
+
from _balder.controllers.feature_controller import FeatureController
|
|
8
11
|
|
|
9
12
|
if TYPE_CHECKING:
|
|
10
13
|
from _balder.setup import Setup
|
|
@@ -58,6 +61,10 @@ class SetupExecutor(BasicExecutor):
|
|
|
58
61
|
"""returns the base :class:`Setup` that belongs to this executor"""
|
|
59
62
|
return self._base_setup_class
|
|
60
63
|
|
|
64
|
+
@property
|
|
65
|
+
def base_setup_controller(self) -> SetupController:
|
|
66
|
+
return SetupController.get_for(self.base_setup_class.__class__)
|
|
67
|
+
|
|
61
68
|
@property
|
|
62
69
|
def fixture_manager(self) -> FixtureManager:
|
|
63
70
|
"""returns the current active fixture manager for this executor"""
|
|
@@ -84,15 +91,43 @@ class SetupExecutor(BasicExecutor):
|
|
|
84
91
|
|
|
85
92
|
# ---------------------------------- METHODS -----------------------------------------------------------------------
|
|
86
93
|
|
|
94
|
+
def update_inner_referenced_feature_instances(self):
|
|
95
|
+
"""
|
|
96
|
+
This method ensures that all inner referenced feature instances of all devices that are used in this setup are
|
|
97
|
+
set to the correct feature instance. For every existing device, this method goes trough all assigned features
|
|
98
|
+
and checks for a inner-referenced feature. It makes sure, that every inner-referenced feature variable has the
|
|
99
|
+
final assigned setup feature that belongs to it.
|
|
100
|
+
|
|
101
|
+
# TODO check where we validate that inner references feature exists in setup
|
|
102
|
+
"""
|
|
103
|
+
for cur_setup_device in self.base_setup_controller.get_all_abs_inner_device_classes():
|
|
104
|
+
# these features are subclasses of the real defined one (because they are already the replaced ones)
|
|
105
|
+
all_device_features = DeviceController.get_for(cur_setup_device).get_all_instantiated_feature_objects()
|
|
106
|
+
all_instantiated_feature_objs_of_this_dev = [cur_feature for _, cur_feature in all_device_features.items()]
|
|
107
|
+
for _, cur_feature in all_device_features.items():
|
|
108
|
+
cur_feature_controller = FeatureController.get_for(cur_feature.__class__)
|
|
109
|
+
# now check the inner referenced features of this feature and check if that exists in the device
|
|
110
|
+
for cur_ref_feature_name, cur_ref_feature in \
|
|
111
|
+
cur_feature_controller.get_inner_referenced_features().items():
|
|
112
|
+
potential_candidates = [
|
|
113
|
+
candidate_feature for candidate_feature in all_instantiated_feature_objs_of_this_dev
|
|
114
|
+
if isinstance(candidate_feature, cur_ref_feature.__class__)]
|
|
115
|
+
if len(potential_candidates) != 1:
|
|
116
|
+
raise RuntimeError("found none or more than one potential replacing candidates")
|
|
117
|
+
replacing_candidate = potential_candidates[0]
|
|
118
|
+
# because `cur_feature` is only the object instance, the value will be overwritten only for this
|
|
119
|
+
# object
|
|
120
|
+
setattr(cur_feature, cur_ref_feature_name, replacing_candidate)
|
|
121
|
+
|
|
87
122
|
def get_scenario_executors(self) -> List[ScenarioExecutor]:
|
|
88
123
|
"""returns a list with all scenario executors that belongs to this setup executor"""
|
|
89
124
|
return self._scenario_executors
|
|
90
125
|
|
|
91
|
-
def cleanup_empty_executor_branches(self):
|
|
126
|
+
def cleanup_empty_executor_branches(self, consider_discarded=False):
|
|
92
127
|
to_remove_executor = []
|
|
93
128
|
for cur_scenario_executor in self.get_scenario_executors():
|
|
94
|
-
cur_scenario_executor.cleanup_empty_executor_branches()
|
|
95
|
-
if len(cur_scenario_executor.get_variation_executors()) == 0:
|
|
129
|
+
cur_scenario_executor.cleanup_empty_executor_branches(consider_discarded=consider_discarded)
|
|
130
|
+
if len(cur_scenario_executor.get_variation_executors(return_discarded=consider_discarded)) == 0:
|
|
96
131
|
# remove this whole executor because it has no children anymore
|
|
97
132
|
to_remove_executor.append(cur_scenario_executor)
|
|
98
133
|
for cur_scenario_executor in to_remove_executor:
|
|
@@ -7,7 +7,6 @@ import traceback
|
|
|
7
7
|
from _balder.utils import inspect_method
|
|
8
8
|
from _balder.testresult import ResultState, TestcaseResult
|
|
9
9
|
from _balder.executor.basic_executor import BasicExecutor
|
|
10
|
-
from _balder.previous_executor_mark import PreviousExecutorMark
|
|
11
10
|
|
|
12
11
|
if TYPE_CHECKING:
|
|
13
12
|
from _balder.executor.variation_executor import VariationExecutor
|
|
@@ -132,7 +131,7 @@ class TestcaseExecutor(BasicExecutor):
|
|
|
132
131
|
return True
|
|
133
132
|
return False
|
|
134
133
|
|
|
135
|
-
def cleanup_empty_executor_branches(self):
|
|
134
|
+
def cleanup_empty_executor_branches(self, consider_discarded=False):
|
|
136
135
|
"""
|
|
137
136
|
This method searches the whole tree and removes branches where an executor item has no own children. It can
|
|
138
137
|
remove these branches, because they have no valid matchings.
|
|
@@ -5,7 +5,7 @@ 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
|
|
8
|
+
from _balder.testresult import ResultState, BranchBodyResult, ResultSummary
|
|
9
9
|
from _balder.executor.basic_executor import BasicExecutor
|
|
10
10
|
from _balder.executor.testcase_executor import TestcaseExecutor
|
|
11
11
|
from _balder.previous_executor_mark import PreviousExecutorMark
|
|
@@ -134,6 +134,7 @@ class VariationExecutor(BasicExecutor):
|
|
|
134
134
|
|
|
135
135
|
@property
|
|
136
136
|
def not_applicable_variation_exc(self) -> Union[NotApplicableVariationException, None]:
|
|
137
|
+
"""holds the :class:`NotApplicableVariationException` that describes why this variation in not applicable"""
|
|
137
138
|
return self._not_applicable_variation_exc
|
|
138
139
|
|
|
139
140
|
# ---------------------------------- PROTECTED METHODS -------------------------------------------------------------
|
|
@@ -149,7 +150,6 @@ class VariationExecutor(BasicExecutor):
|
|
|
149
150
|
self.determine_abs_variation_connections()
|
|
150
151
|
self.update_scenario_device_feature_instances()
|
|
151
152
|
self.update_active_vdevice_device_mappings_in_all_features()
|
|
152
|
-
self.update_inner_referenced_feature_instances()
|
|
153
153
|
self.exchange_unmapped_vdevice_references()
|
|
154
154
|
self.update_vdevice_referenced_feature_instances()
|
|
155
155
|
self.set_conn_dependent_methods()
|
|
@@ -282,17 +282,10 @@ class VariationExecutor(BasicExecutor):
|
|
|
282
282
|
|
|
283
283
|
# ---------------------------------- METHODS -----------------------------------------------------------------------
|
|
284
284
|
|
|
285
|
-
def testsummary(self):
|
|
285
|
+
def testsummary(self) -> ResultSummary:
|
|
286
286
|
if self.can_be_applied():
|
|
287
287
|
return super().testsummary()
|
|
288
|
-
return
|
|
289
|
-
ResultState.NOT_RUN: 0,
|
|
290
|
-
ResultState.FAILURE: 0,
|
|
291
|
-
ResultState.ERROR: 0,
|
|
292
|
-
ResultState.SUCCESS: 0,
|
|
293
|
-
ResultState.SKIP: 0,
|
|
294
|
-
ResultState.COVERED_BY: 0
|
|
295
|
-
}
|
|
288
|
+
return ResultSummary()
|
|
296
289
|
|
|
297
290
|
def get_testcase_executors(self) -> List[TestcaseExecutor]:
|
|
298
291
|
"""returns all sub testcase executors that belongs to this variation-executor"""
|
|
@@ -483,7 +476,7 @@ class VariationExecutor(BasicExecutor):
|
|
|
483
476
|
# can not find some
|
|
484
477
|
return None
|
|
485
478
|
|
|
486
|
-
def cleanup_empty_executor_branches(self):
|
|
479
|
+
def cleanup_empty_executor_branches(self, consider_discarded=False):
|
|
487
480
|
"""
|
|
488
481
|
This method searches the whole tree and removes branches where an executor item has no own children. It can
|
|
489
482
|
remove these branches, because they have no valid matchings.
|
|
@@ -569,34 +562,6 @@ class VariationExecutor(BasicExecutor):
|
|
|
569
562
|
for cur_feature, cur_original_mapping in cur_feature_mapping_dict.items():
|
|
570
563
|
cur_feature.active_vdevices = cur_original_mapping
|
|
571
564
|
|
|
572
|
-
def update_inner_referenced_feature_instances(self):
|
|
573
|
-
"""
|
|
574
|
-
This method ensures that all inner referenced feature instances of the used feature object, will be replaced
|
|
575
|
-
with the related feature instances of the device object itself.
|
|
576
|
-
|
|
577
|
-
.. note::
|
|
578
|
-
Note that this method expects that the true defined scenario features are already replaced with the real
|
|
579
|
-
setup features. So the method requires that the method `update_scenario_device_feature_instances()` was
|
|
580
|
-
called before.
|
|
581
|
-
"""
|
|
582
|
-
for scenario_device, _ in self._base_device_mapping.items():
|
|
583
|
-
# these features are subclasses of the real defined one (because they are already the replaced ones)
|
|
584
|
-
all_device_features = DeviceController.get_for(scenario_device).get_all_instantiated_feature_objects()
|
|
585
|
-
all_instantiated_feature_objs = [cur_feature for _, cur_feature in all_device_features.items()]
|
|
586
|
-
for _, cur_feature in all_device_features.items():
|
|
587
|
-
cur_feature_controller = FeatureController.get_for(cur_feature.__class__)
|
|
588
|
-
# now check the inner referenced features of this feature and check if that exists in the device
|
|
589
|
-
for cur_ref_feature_name, cur_ref_feature in \
|
|
590
|
-
cur_feature_controller.get_inner_referenced_features().items():
|
|
591
|
-
potential_candidates = [candidate_feature for candidate_feature in all_instantiated_feature_objs
|
|
592
|
-
if isinstance(candidate_feature, cur_ref_feature.__class__)]
|
|
593
|
-
if len(potential_candidates) != 1:
|
|
594
|
-
raise RuntimeError("found none or more than one potential replacing candidates")
|
|
595
|
-
replacing_candidate = potential_candidates[0]
|
|
596
|
-
# because `cur_feature` is only the object instance, the value will be overwritten only for this
|
|
597
|
-
# object
|
|
598
|
-
setattr(cur_feature, cur_ref_feature_name, replacing_candidate)
|
|
599
|
-
|
|
600
565
|
def exchange_unmapped_vdevice_references(self):
|
|
601
566
|
"""
|
|
602
567
|
This method exchanges all :class:`VDevice` references to an instance of :class:`UnmappedVDevice` if the
|
|
@@ -721,10 +686,7 @@ class VariationExecutor(BasicExecutor):
|
|
|
721
686
|
if cur_scenario_device not in abs_var_scenario_device_cnns.keys():
|
|
722
687
|
abs_var_scenario_device_cnns[cur_scenario_device] = {}
|
|
723
688
|
|
|
724
|
-
|
|
725
|
-
cur_to_device = cur_cnn.to_device
|
|
726
|
-
else:
|
|
727
|
-
cur_to_device = cur_cnn.from_device
|
|
689
|
+
cur_to_device, _ = cur_cnn.get_conn_partner_of(cur_scenario_device)
|
|
728
690
|
|
|
729
691
|
if cur_to_device not in abs_var_scenario_device_cnns[cur_scenario_device].keys():
|
|
730
692
|
abs_var_scenario_device_cnns[cur_scenario_device][cur_to_device] = []
|
_balder/fixture_manager.py
CHANGED
|
@@ -394,6 +394,41 @@ class FixtureManager:
|
|
|
394
394
|
if exception:
|
|
395
395
|
raise exception
|
|
396
396
|
|
|
397
|
+
def get_fixture_for_class(
|
|
398
|
+
self,
|
|
399
|
+
execution_level: Union[Type[ExecutorTree], Type[SetupExecutor], Type[ScenarioExecutor],
|
|
400
|
+
Type[VariationExecutor], Type[TestcaseExecutor]],
|
|
401
|
+
setup_or_scenario_class: Union[None, Type[Setup], Type[Scenario]],
|
|
402
|
+
parent_classes: bool = True
|
|
403
|
+
) -> List[Tuple[MethodLiteralType, Callable]]:
|
|
404
|
+
"""
|
|
405
|
+
This method returns all classes of a specific Setup/Scenario class for a specific execution-level.
|
|
406
|
+
|
|
407
|
+
:param execution_level: the execution level the fixture should be returned for
|
|
408
|
+
:param setup_or_scenario_class: the scenario or setup class, the fixtures should be returned for
|
|
409
|
+
:param parent_classes: true if the method should look for fixtures in parent classes too
|
|
410
|
+
:return: list with all fixtures that are matching search criteria
|
|
411
|
+
"""
|
|
412
|
+
# current relevant EXECUTION LEVEL - all other levels are not relevant for this call
|
|
413
|
+
cur_execution_level = self.resolve_type_level[execution_level]
|
|
414
|
+
# get all fixtures of the current relevant level
|
|
415
|
+
fixtures_of_exec_level = self.fixtures.get(cur_execution_level, {})
|
|
416
|
+
if setup_or_scenario_class is not None and parent_classes:
|
|
417
|
+
all_fixtures = []
|
|
418
|
+
for cur_parent_class in inspect.getmro(setup_or_scenario_class):
|
|
419
|
+
if issubclass(cur_parent_class, (Scenario, Setup)):
|
|
420
|
+
all_fixtures += self.get_fixture_for_class(execution_level, cur_parent_class, False)
|
|
421
|
+
# go through list and remove all overwritten fixtures
|
|
422
|
+
_added_fixtures = []
|
|
423
|
+
remaining_fixtures = []
|
|
424
|
+
for cur_fixture_tuple in all_fixtures:
|
|
425
|
+
if cur_fixture_tuple[1].__name__ not in _added_fixtures:
|
|
426
|
+
_added_fixtures.append(cur_fixture_tuple[1].__name__)
|
|
427
|
+
remaining_fixtures.append(cur_fixture_tuple)
|
|
428
|
+
return remaining_fixtures
|
|
429
|
+
else:
|
|
430
|
+
return fixtures_of_exec_level.get(setup_or_scenario_class, [])
|
|
431
|
+
|
|
397
432
|
def get_all_fixtures_for_current_level(
|
|
398
433
|
self, branch: Union[ExecutorTree, SetupExecutor, ScenarioExecutor, VariationExecutor, TestcaseExecutor]) \
|
|
399
434
|
-> Dict[Union[Type[ExecutorTree], Type[Scenario], Type[Setup]],
|
|
@@ -416,28 +451,24 @@ class FixtureManager:
|
|
|
416
451
|
argument (this list is ordered after the call hierarchy)
|
|
417
452
|
"""
|
|
418
453
|
from _balder.executor.executor_tree import ExecutorTree
|
|
419
|
-
# current relevant EXECUTION LEVEL - all other levels are not relevant for this call
|
|
420
|
-
cur_execution_level = self.resolve_type_level[branch.__class__]
|
|
421
|
-
# get all fixtures of the current relevant level
|
|
422
|
-
fixtures_of_exec_level = self.fixtures.get(cur_execution_level, {})
|
|
423
454
|
|
|
424
455
|
all_fixtures = {}
|
|
425
456
|
# get all relevant fixtures of `balderglob.py` (None is key for balderglob fixtures)
|
|
426
|
-
glob_fixtures =
|
|
457
|
+
glob_fixtures = self.get_fixture_for_class(branch.__class__, None)
|
|
427
458
|
all_fixtures[ExecutorTree] = {}
|
|
428
459
|
all_fixtures[ExecutorTree][ExecutorTree] = glob_fixtures
|
|
429
460
|
# get all relevant fixtures with definition scope "setup"
|
|
430
461
|
all_fixtures[Setup] = {}
|
|
431
462
|
for cur_setup in branch.get_all_base_instances_of_this_branch(Setup, only_runnable_elements=True):
|
|
432
463
|
# check if there exists fixtures for the current setup
|
|
433
|
-
cur_setup_fixtures =
|
|
464
|
+
cur_setup_fixtures = self.get_fixture_for_class(branch.__class__, cur_setup.__class__)
|
|
434
465
|
if cur_setup_fixtures:
|
|
435
466
|
all_fixtures[Setup][cur_setup.__class__] = cur_setup_fixtures
|
|
436
467
|
|
|
437
468
|
# get all relevant fixtures with definition scope "scenario"
|
|
438
469
|
all_fixtures[Scenario] = {}
|
|
439
470
|
for cur_scenario in branch.get_all_base_instances_of_this_branch(Scenario, only_runnable_elements=True):
|
|
440
|
-
cur_scenario_fixtures =
|
|
471
|
+
cur_scenario_fixtures = self.get_fixture_for_class(branch.__class__, cur_scenario.__class__)
|
|
441
472
|
if cur_scenario_fixtures:
|
|
442
473
|
all_fixtures[Scenario][cur_scenario.__class__] = cur_scenario_fixtures
|
|
443
474
|
|
_balder/routing_path.py
CHANGED
|
@@ -284,8 +284,6 @@ class RoutingPath:
|
|
|
284
284
|
|
|
285
285
|
if elem_before is not None:
|
|
286
286
|
# check if device and nodes of the two elements are the same -> have to be a chain
|
|
287
|
-
last_device, last_node_name = self._get_end_device_and_node()
|
|
288
|
-
|
|
289
287
|
if isinstance(elem, Connection):
|
|
290
288
|
if (self.end_device, self.end_node_name) not in \
|
|
291
289
|
[(elem.from_device, elem.from_node_name), (elem.to_device, elem.to_node_name)]:
|
_balder/solver.py
CHANGED
|
@@ -170,10 +170,14 @@ class Solver:
|
|
|
170
170
|
self._mapping = initial_mapping
|
|
171
171
|
self._resolving_was_executed = True
|
|
172
172
|
|
|
173
|
-
|
|
173
|
+
# pylint: disable-next=unused-argument
|
|
174
|
+
def get_executor_tree(self, plugin_manager: PluginManager, add_discarded=False) -> ExecutorTree:
|
|
174
175
|
"""
|
|
175
176
|
This method builds the ExecutorTree from the resolved data and returns it
|
|
176
177
|
|
|
178
|
+
:param plugin_manager: the related plugin manager object
|
|
179
|
+
:param add_discarded: True in case discarded elements should be added to the tree, otherwise False
|
|
180
|
+
|
|
177
181
|
:return: the executor tree is built on the basis of the mapping data
|
|
178
182
|
"""
|
|
179
183
|
|
|
@@ -215,7 +219,7 @@ class Solver:
|
|
|
215
219
|
# now filter all elements that have no child elements
|
|
216
220
|
# -> these are items that have no valid matching, because no variation can be applied for it (there are no
|
|
217
221
|
# required :class:`Feature` matching or there exists no possible routing for the variation)
|
|
218
|
-
executor_tree.cleanup_empty_executor_branches()
|
|
222
|
+
executor_tree.cleanup_empty_executor_branches(consider_discarded=add_discarded)
|
|
219
223
|
|
|
220
224
|
self._set_data_for_covered_by_in_tree(executor_tree=executor_tree)
|
|
221
225
|
|
_balder/testresult.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, fields
|
|
2
4
|
from typing import TYPE_CHECKING, List, Union
|
|
3
5
|
|
|
4
6
|
from enum import Enum
|
|
@@ -12,21 +14,21 @@ class ResultState(Enum):
|
|
|
12
14
|
enumeration that describes the possible results of a testcase-/fixture-executor
|
|
13
15
|
"""
|
|
14
16
|
# this state will be assigned if the executor doesn't run yet
|
|
15
|
-
NOT_RUN = '
|
|
17
|
+
NOT_RUN = 'not_run'
|
|
16
18
|
# this state will be assigned if the test fails (only assignable to :class:`TestcaseExecutor`)
|
|
17
|
-
FAILURE = '
|
|
19
|
+
FAILURE = 'failure'
|
|
18
20
|
# this state will be assigned if the executor can not be executed because the fixture fails (only possible in
|
|
19
21
|
# construction part of fixtures, if the error occurs in teardown the next higher executer get this state) - will be
|
|
20
22
|
# assigned to the executor which has the fixture that fails
|
|
21
|
-
ERROR = '
|
|
23
|
+
ERROR = 'error'
|
|
22
24
|
# this state will be assigned if the executor was executed successfully (session fixture and teardown fixture; for
|
|
23
25
|
# :class:`TestcaseExecutor` also the testcase itself)
|
|
24
|
-
SUCCESS = '
|
|
26
|
+
SUCCESS = 'success'
|
|
25
27
|
# this state will be assigned if the executor was skipped
|
|
26
|
-
SKIP = '
|
|
28
|
+
SKIP = 'skip'
|
|
27
29
|
# this state will be assigned if the executor is covered by another executor and wasn't executed (only assignable
|
|
28
30
|
# to :class:`ScenarioExecutor` and :class:`TestcaseExecutor`)
|
|
29
|
-
COVERED_BY = '
|
|
31
|
+
COVERED_BY = 'covered_by'
|
|
30
32
|
|
|
31
33
|
@staticmethod
|
|
32
34
|
def priority_order() -> List[ResultState]:
|
|
@@ -128,3 +130,33 @@ class TestcaseResult(_SettableResult):
|
|
|
128
130
|
#: contains the possible values that can be set for this Result type
|
|
129
131
|
ALLOWED_STATES = [ResultState.NOT_RUN, ResultState.FAILURE, ResultState.SUCCESS, ResultState.SKIP,
|
|
130
132
|
ResultState.COVERED_BY]
|
|
133
|
+
|
|
134
|
+
@dataclass
|
|
135
|
+
class ResultSummary:
|
|
136
|
+
"""
|
|
137
|
+
object that holds the test results for multiple tests like on branch or global level
|
|
138
|
+
"""
|
|
139
|
+
# this state will be assigned if the executor doesn't run yet
|
|
140
|
+
not_run: int = 0
|
|
141
|
+
# this state will be assigned if the test fails (only assignable to :class:`TestcaseExecutor`)
|
|
142
|
+
failure: int = 0
|
|
143
|
+
# this state will be assigned if the executor can not be executed because the fixture fails (only possible in
|
|
144
|
+
# construction part of fixtures, if the error occurs in teardown the next higher executer get this state) - will be
|
|
145
|
+
# assigned to the executor which has the fixture that fails
|
|
146
|
+
error: int = 0
|
|
147
|
+
# this state will be assigned if the executor was executed successfully (session fixture and teardown fixture; for
|
|
148
|
+
# :class:`TestcaseExecutor` also the testcase itself)
|
|
149
|
+
success: int = 0
|
|
150
|
+
# this state will be assigned if the executor was skipped
|
|
151
|
+
skip: int = 0
|
|
152
|
+
# this state will be assigned if the executor is covered by another executor and wasn't executed (only assignable
|
|
153
|
+
# to :class:`ScenarioExecutor` and :class:`TestcaseExecutor`)
|
|
154
|
+
covered_by: int = 0
|
|
155
|
+
|
|
156
|
+
def __add__(self, other) -> ResultSummary:
|
|
157
|
+
new_summary = ResultSummary()
|
|
158
|
+
for cur_field in fields(self.__class__):
|
|
159
|
+
self_val = getattr(self, cur_field.name)
|
|
160
|
+
other_val = getattr(other, cur_field.name)
|
|
161
|
+
setattr(new_summary, cur_field.name, self_val + other_val)
|
|
162
|
+
return new_summary
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
_balder/__init__.py,sha256=Qk4wkVInPlXLFV36Yco5K7PDawJoeeWQVakzj6g5pmA,195
|
|
2
|
-
_balder/_version.py,sha256=
|
|
2
|
+
_balder/_version.py,sha256=xdDxoDc8nbJHb9-aJOnXPcJO9u_wtajm1m932_7HjoI,413
|
|
3
3
|
_balder/balder_plugin.py,sha256=EQzJP1dwwVDydhMLJtAmTCXOczlDuXBJur05lalmK_k,3136
|
|
4
|
-
_balder/balder_session.py,sha256=
|
|
4
|
+
_balder/balder_session.py,sha256=WPOcarZQhTzMeAQt-hRqI8GtXN5TdMIqzyhgYD2h4rw,16079
|
|
5
5
|
_balder/balder_settings.py,sha256=U96PVep7dGSaTXrMfeZMYf6oCIcEDPEqrBlFcoX476s,582
|
|
6
|
-
_balder/collector.py,sha256=
|
|
7
|
-
_balder/connection.py,sha256=
|
|
6
|
+
_balder/collector.py,sha256=T_7LO8ZNty8EyOmM-U-uhU9-umdAv1fbuMVNjQWUwj0,42156
|
|
7
|
+
_balder/connection.py,sha256=MNazK97CIEJCA4o1krjKjtilVhOYMWD8TZT0YT5nCJs,71778
|
|
8
8
|
_balder/decorator_connect.py,sha256=TvyJNIslBAYVQWhsfSeFgXKp_DP7sZF1BmcP6RhIdKo,5988
|
|
9
9
|
_balder/decorator_covered_by.py,sha256=Y6WMUuyn_uvFkjGfbts8OE5Bsir_7LTRB-jxYGaYk4Y,4069
|
|
10
10
|
_balder/decorator_fixture.py,sha256=Nvr3H-r8LlPiGSGEst3D1M-KQaBqSA1u1u13qzZrSwk,1138
|
|
@@ -15,15 +15,15 @@ _balder/device.py,sha256=5O3tqj_iLKfHb5Zi_viJ76VH82cMOzX58OzRrMRRv0k,833
|
|
|
15
15
|
_balder/exceptions.py,sha256=d0YSw8zVax2bFakG_1hcLlAXlyQh5jXnOuIPL1HLfI4,4202
|
|
16
16
|
_balder/exit_code.py,sha256=P0oFWKfjMo36Frv13ADRcm8eSPN3kE-WmZBE9qZJHdA,513
|
|
17
17
|
_balder/feature.py,sha256=B3yPc-WZwLt1Q4dO9s2j9g1MBTcMBjn6oWoLnRgrwSs,3845
|
|
18
|
-
_balder/fixture_manager.py,sha256=
|
|
18
|
+
_balder/fixture_manager.py,sha256=_fJZAaBK8Pej96juvdkigi7rjKJ-RTbfxhkSs78OBvg,28656
|
|
19
19
|
_balder/node_gateway.py,sha256=64mv7Nx82JVknnQ09UXC-AcdDl6i_OB6NOsq_uBxeYo,4710
|
|
20
20
|
_balder/plugin_manager.py,sha256=Ev2jnx4NtFHDsZ3C6h0HrJtQisqLO-V34JRM3wzTnFM,6921
|
|
21
21
|
_balder/previous_executor_mark.py,sha256=gwpGu7d-kwPzQT8CmaPfuEG6fess2Upf5Q-zX6Oi6NY,835
|
|
22
|
-
_balder/routing_path.py,sha256=
|
|
22
|
+
_balder/routing_path.py,sha256=6MJkhzBTHow2ESXzKQ2otwRFbPcKhLTYVy-zh7c5HeE,17172
|
|
23
23
|
_balder/scenario.py,sha256=ATowBUl2HYQBmJHZ-eBpliqjPsWPnZAme9kwIeX3Tak,840
|
|
24
24
|
_balder/setup.py,sha256=zSgtzNIWTVBjiZ5mn-qfpqIAnP3Im73t3Lqoaw0gWEI,763
|
|
25
|
-
_balder/solver.py,sha256=
|
|
26
|
-
_balder/testresult.py,sha256=
|
|
25
|
+
_balder/solver.py,sha256=qgcCRyLg3nsO2d3H0pukxmm2oe_h6t0pBGyZ-FgQYKc,11998
|
|
26
|
+
_balder/testresult.py,sha256=byJD3F84TNH40k30pjnxeLHXAAE-lqbVlk1JOSBtdNo,6783
|
|
27
27
|
_balder/unmapped_vdevice.py,sha256=oKr01YVTLViWtZkYz8kx8ccTx-KmwgNrHuQqqD4eLQw,513
|
|
28
28
|
_balder/utils.py,sha256=2kcX6bZIJW9Z8g-Hv0ue2mdOLBQYq4T2XSZpH2in1MQ,3092
|
|
29
29
|
_balder/vdevice.py,sha256=fc2xuMnTuN1RyfWh9mqFgLdSO9yGA75eERobTXUQ9JA,215
|
|
@@ -33,18 +33,18 @@ _balder/controllers/__init__.py,sha256=UNb6QzMj4TqPI15OSvXyUJlA-NSai0CKkQhV5JIsb
|
|
|
33
33
|
_balder/controllers/base_device_controller.py,sha256=g-vY2SqKFUC9yGOvHLfbdmILT3sK2YyaWKSfvTRcC0o,3174
|
|
34
34
|
_balder/controllers/controller.py,sha256=XGRE5LKWxxftEf-bZODvKxXwULu09iG131wMwRoz4Fk,803
|
|
35
35
|
_balder/controllers/device_controller.py,sha256=bT8c0_9UmrAmzuZ-dsleSnw2pIppfITDG4ttEZjWlC8,23477
|
|
36
|
-
_balder/controllers/feature_controller.py,sha256=
|
|
37
|
-
_balder/controllers/normal_scenario_setup_controller.py,sha256=
|
|
36
|
+
_balder/controllers/feature_controller.py,sha256=jaIbKbiEOdYRpYWuGdaByQBtlThK-u7OAr_xYhAfnEg,43737
|
|
37
|
+
_balder/controllers/normal_scenario_setup_controller.py,sha256=cF2U-tC3LEyWdZDqZP_mN3Q_7uIzg9jiY_J--Pi5kk8,21877
|
|
38
38
|
_balder/controllers/scenario_controller.py,sha256=gpN_wxEYcHlh6DVk7pRbPj_x2cmM6iIAskcpLstQo2g,17973
|
|
39
39
|
_balder/controllers/setup_controller.py,sha256=iNIMFjawYJWaSToUUfpmRK6ssycPyZGNlcvms8c7GKM,7135
|
|
40
40
|
_balder/controllers/vdevice_controller.py,sha256=6-PidCKgvUteZVJsbGkKX69f3cYYYnolONl5Gja16W8,5777
|
|
41
41
|
_balder/executor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
42
|
-
_balder/executor/basic_executor.py,sha256=
|
|
43
|
-
_balder/executor/executor_tree.py,sha256=
|
|
44
|
-
_balder/executor/scenario_executor.py,sha256=
|
|
45
|
-
_balder/executor/setup_executor.py,sha256=
|
|
46
|
-
_balder/executor/testcase_executor.py,sha256=
|
|
47
|
-
_balder/executor/variation_executor.py,sha256=
|
|
42
|
+
_balder/executor/basic_executor.py,sha256=PYuQ1TKXKnbnQirCmayjDSyPyBmqJws1HWqkHmGKZtw,11137
|
|
43
|
+
_balder/executor/executor_tree.py,sha256=nEZ2bwWuayPZQrGSucFiDzuFSrYDnUVzqnVCtDLq2Ys,10439
|
|
44
|
+
_balder/executor/scenario_executor.py,sha256=3j5lAFCA4zCxhivjMPFGlWMi_ieZGkdwnZ_JQoAt4Rw,11094
|
|
45
|
+
_balder/executor/setup_executor.py,sha256=FsPTPsXZMoVB7xibEpCcwjC-BpkfGVZmx5lVbuebduw,8326
|
|
46
|
+
_balder/executor/testcase_executor.py,sha256=jNfACO-AuOktIgCVcsavh0rHbUr5MmI-jML-LPMvo0w,6639
|
|
47
|
+
_balder/executor/variation_executor.py,sha256=Ms8dfZPIL5-sYzMg2c1-WANDz8ZBHqNHXgegDobm7Gg,53106
|
|
48
48
|
_balder/objects/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
49
49
|
_balder/objects/connections/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
50
50
|
_balder/objects/connections/osi_1_physical.py,sha256=74lKWJd6ETEtvNXH0_dmTbkZlStJ_af218pQkUht0aA,2189
|
|
@@ -60,9 +60,9 @@ balder/__init__.py,sha256=BfbdkW_CZCXWGqtdg1gpAjO0AY8b4h-P-flWsIMG-X0,1003
|
|
|
60
60
|
balder/connections.py,sha256=H6rf7UsiVY_FeZLngZXCT9WDw9cQqpiDiPbz_0J4yjM,2331
|
|
61
61
|
balder/devices.py,sha256=zupHtz8yaiEjzR8CrvgZU-RzsDQcZFeN5mObfhtjwSw,173
|
|
62
62
|
balder/exceptions.py,sha256=z_vlipJIsFwwJy9Ae_oGDJGPTINiAInMNZuCvEy6SUE,1884
|
|
63
|
-
baldertest-0.1.
|
|
64
|
-
baldertest-0.1.
|
|
65
|
-
baldertest-0.1.
|
|
66
|
-
baldertest-0.1.
|
|
67
|
-
baldertest-0.1.
|
|
68
|
-
baldertest-0.1.
|
|
63
|
+
baldertest-0.1.0b9.dist-info/LICENSE,sha256=Daz9qTpqbiq-klWb2Q9lYOmn3rJ5oIQnbs62sGcqOZ4,1084
|
|
64
|
+
baldertest-0.1.0b9.dist-info/METADATA,sha256=HzdUqwAR2FIIYoAWg6RxXoFOJzUOqpkxcA7q-V3SHbM,15783
|
|
65
|
+
baldertest-0.1.0b9.dist-info/WHEEL,sha256=mguMlWGMX-VHnMpKOjjQidIo1ssRlCFu4a4mBpz1s2M,91
|
|
66
|
+
baldertest-0.1.0b9.dist-info/entry_points.txt,sha256=hzqu_nrMKTCi5IJqzS1fhIXWEiL7mTGZ-kgj2lUYlRU,65
|
|
67
|
+
baldertest-0.1.0b9.dist-info/top_level.txt,sha256=RUkIBkNLqHMemx2C9aEpoS65dpqb6_jU-oagIPxGQEA,15
|
|
68
|
+
baldertest-0.1.0b9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|