baldertest 0.1.0b6__py3-none-any.whl → 0.1.0b8__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 +20 -17
- _balder/collector.py +38 -6
- _balder/connection.py +137 -97
- _balder/console/balder.py +1 -0
- _balder/controllers/base_device_controller.py +2 -3
- _balder/controllers/feature_controller.py +11 -12
- _balder/exceptions.py +1 -1
- _balder/executor/basic_executor.py +35 -20
- _balder/executor/executor_tree.py +49 -36
- _balder/executor/scenario_executor.py +21 -15
- _balder/executor/setup_executor.py +15 -16
- _balder/executor/testcase_executor.py +4 -8
- _balder/executor/variation_executor.py +179 -132
- _balder/fixture_manager.py +38 -7
- _balder/previous_executor_mark.py +3 -0
- _balder/routing_path.py +1 -3
- _balder/solver.py +17 -15
- balder/exceptions.py +2 -2
- baldertest-0.1.0b8.dist-info/METADATA +388 -0
- {baldertest-0.1.0b6.dist-info → baldertest-0.1.0b8.dist-info}/RECORD +25 -25
- {baldertest-0.1.0b6.dist-info → baldertest-0.1.0b8.dist-info}/WHEEL +1 -1
- baldertest-0.1.0b6.dist-info/METADATA +0 -92
- {baldertest-0.1.0b6.dist-info → baldertest-0.1.0b8.dist-info}/LICENSE +0 -0
- {baldertest-0.1.0b6.dist-info → baldertest-0.1.0b8.dist-info}/entry_points.txt +0 -0
- {baldertest-0.1.0b6.dist-info → baldertest-0.1.0b8.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.0b8'
|
|
4
16
|
__version_tuple__ = version_tuple = (0, 1, 0)
|
_balder/balder_session.py
CHANGED
|
@@ -21,7 +21,7 @@ if TYPE_CHECKING:
|
|
|
21
21
|
from _balder.scenario import Scenario
|
|
22
22
|
from _balder.connection import Connection
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
# pylint: disable-next=too-many-instance-attributes
|
|
25
25
|
class BalderSession:
|
|
26
26
|
"""
|
|
27
27
|
This is the main balder executable object. It contains all information about the current session and executes the
|
|
@@ -60,6 +60,8 @@ class BalderSession:
|
|
|
60
60
|
self.collect_only: Union[bool, None] = None
|
|
61
61
|
#: specifies that the tests should only be collected and resolved but not executed
|
|
62
62
|
self.resolve_only: Union[bool, None] = None
|
|
63
|
+
#: specifies that all discarded variations should be printed (with information why they were discarded)
|
|
64
|
+
self.show_discarded: Union[bool, None] = None
|
|
63
65
|
#: contains a number of :class:`Setup` class strings that should only be considered for the execution
|
|
64
66
|
self.only_with_setup: Union[List[str], None] = None
|
|
65
67
|
#: contains a number of :class:`Scenario` class strings that should only be considered for the execution
|
|
@@ -246,6 +248,10 @@ class BalderSession:
|
|
|
246
248
|
'--resolve-only', action='store_true',
|
|
247
249
|
help="specifies that the tests are only collected and resolved but not executed")
|
|
248
250
|
|
|
251
|
+
self.cmd_arg_parser.add_argument(
|
|
252
|
+
'--show-discarded', action='store_true',
|
|
253
|
+
help="specifies that all discarded variations should be printed (with information why they were discarded)")
|
|
254
|
+
|
|
249
255
|
self.cmd_arg_parser.add_argument(
|
|
250
256
|
'--only-with-setup', nargs="*",
|
|
251
257
|
help="defines a number of Setup classes which should only be considered for the execution")
|
|
@@ -262,14 +268,10 @@ class BalderSession:
|
|
|
262
268
|
|
|
263
269
|
self.parsed_args = self.cmd_arg_parser.parse_args(self._alt_cmd_args)
|
|
264
270
|
|
|
265
|
-
if self.parsed_args.only_with_scenario is not None:
|
|
266
|
-
raise NotImplementedError("the parameter `--only-with-scenario` is not supported in this version of balder")
|
|
267
|
-
if self.parsed_args.only_with_setup is not None:
|
|
268
|
-
raise NotImplementedError("the parameter `--only-with-setup` is not supported in this version of balder")
|
|
269
|
-
|
|
270
271
|
self.working_dir = self.parsed_args.working_dir
|
|
271
272
|
self.collect_only = self.parsed_args.collect_only
|
|
272
273
|
self.resolve_only = self.parsed_args.resolve_only
|
|
274
|
+
self.show_discarded = self.parsed_args.show_discarded
|
|
273
275
|
self.only_with_setup = self.parsed_args.only_with_setup
|
|
274
276
|
self.only_with_scenario = self.parsed_args.only_with_scenario
|
|
275
277
|
self.force_covered_by_duplicates = self.parsed_args.force_covered_by_duplicates
|
|
@@ -278,7 +280,10 @@ class BalderSession:
|
|
|
278
280
|
"""
|
|
279
281
|
This method collects all data.
|
|
280
282
|
"""
|
|
281
|
-
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)
|
|
282
287
|
|
|
283
288
|
def solve(self):
|
|
284
289
|
"""
|
|
@@ -295,15 +300,10 @@ class BalderSession:
|
|
|
295
300
|
.. note::
|
|
296
301
|
Note that the method creates an :class:`ExecutorTree`, that hasn't to be completely resolved yet.
|
|
297
302
|
"""
|
|
298
|
-
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)
|
|
299
305
|
self.plugin_manager.execute_filter_executor_tree(executor_tree=self.executor_tree)
|
|
300
306
|
|
|
301
|
-
def execute_executor_tree(self):
|
|
302
|
-
"""
|
|
303
|
-
This method executes the :class:`ExecutorTree`.
|
|
304
|
-
"""
|
|
305
|
-
self.executor_tree.execute()
|
|
306
|
-
|
|
307
307
|
def run(self):
|
|
308
308
|
"""
|
|
309
309
|
This method executes the whole session
|
|
@@ -326,11 +326,14 @@ class BalderSession:
|
|
|
326
326
|
if not self.collect_only:
|
|
327
327
|
self.solve()
|
|
328
328
|
self.create_executor_tree()
|
|
329
|
-
|
|
329
|
+
count_valid = len(self.executor_tree.get_all_variation_executors())
|
|
330
|
+
count_discarded = len(self.executor_tree.get_all_variation_executors(return_discarded=True)) - count_valid
|
|
331
|
+
addon_text = f" ({count_discarded} discarded)" if self.show_discarded else ""
|
|
332
|
+
print(f" resolve them to {count_valid} valid variations{addon_text}")
|
|
330
333
|
print("")
|
|
331
334
|
if not self.resolve_only:
|
|
332
|
-
self.
|
|
335
|
+
self.executor_tree.execute(show_discarded=self.show_discarded)
|
|
333
336
|
else:
|
|
334
|
-
self.executor_tree.print_tree()
|
|
337
|
+
self.executor_tree.print_tree(show_discarded=self.show_discarded)
|
|
335
338
|
|
|
336
339
|
self.plugin_manager.execute_session_finished(self.executor_tree)
|
_balder/collector.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
from typing import List, Type, Union, Dict, Callable, Tuple, TYPE_CHECKING
|
|
2
|
+
from typing import List, Type, Union, Dict, Callable, Tuple, TYPE_CHECKING, Any
|
|
3
3
|
|
|
4
4
|
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,58 @@ 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]) -> List[pathlib.Path]:
|
|
682
|
+
"""
|
|
683
|
+
This method filters the given list of filepaths for the given filter_patterns. It returns a list with all
|
|
684
|
+
remaining paths that are mathing the filter statements in `filter_paths`.
|
|
685
|
+
|
|
686
|
+
Patterns are the same like for `fnmatch <https://docs.python.org/3/library/fnmatch.html#module-fnmatch>`_.
|
|
687
|
+
|
|
688
|
+
:param paths: a list of all filepaths that should be filtered
|
|
689
|
+
:param filter_patterns: a list of relative filter patterns that should be applied on the files of the classes
|
|
690
|
+
:return: returns all classes of items that match any of the given patterns.
|
|
691
|
+
"""
|
|
692
|
+
remaining = []
|
|
693
|
+
|
|
694
|
+
for cur_pattern in filter_patterns:
|
|
695
|
+
remaining += [cur_abs_path for cur_abs_path in paths
|
|
696
|
+
if fnmatch.fnmatch(str(cur_abs_path.relative_to(self.working_dir)), cur_pattern)]
|
|
697
|
+
return list(set(remaining))
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
def collect(self, plugin_manager: PluginManager, scenario_filter_patterns: Union[List[str], None],
|
|
701
|
+
setup_filter_patterns: Union[List[str], None]):
|
|
681
702
|
"""
|
|
682
703
|
This method manages the entire collection process.
|
|
683
704
|
|
|
684
705
|
:param plugin_manager: contains the reference to the used plugin manager
|
|
706
|
+
:param scenario_filter_patterns: a list with filter patterns for scenarios
|
|
707
|
+
:param setup_filter_patterns: a list with filter patterns for setups
|
|
685
708
|
"""
|
|
686
709
|
# load all py files
|
|
687
710
|
self.load_balderglob_py_file()
|
|
688
711
|
self._all_py_files = self.get_all_py_files()
|
|
689
712
|
self._all_py_files = plugin_manager.execute_modify_collected_pyfiles(self._all_py_files)
|
|
690
713
|
|
|
714
|
+
if scenario_filter_patterns:
|
|
715
|
+
all_scenario_filepaths = self._filter_paths_after_allowed_paths(
|
|
716
|
+
paths=self._all_py_files, filter_patterns=scenario_filter_patterns)
|
|
717
|
+
else:
|
|
718
|
+
all_scenario_filepaths = self._all_py_files
|
|
719
|
+
if setup_filter_patterns:
|
|
720
|
+
all_setup_filepaths = self._filter_paths_after_allowed_paths(
|
|
721
|
+
paths=self._all_py_files, filter_patterns=setup_filter_patterns)
|
|
722
|
+
else:
|
|
723
|
+
all_setup_filepaths = self._all_py_files
|
|
724
|
+
|
|
691
725
|
# collect all `Connection` classes (has to be first, because scenarios/setups can use them)
|
|
692
726
|
self.load_all_connection_classes(py_file_paths=self._all_py_files)
|
|
693
727
|
self._all_connections = self.get_all_connection_classes()
|
|
694
728
|
|
|
695
729
|
# collect all `Scenario` classes
|
|
696
|
-
self._all_scenarios = self.get_all_scenario_classes(
|
|
697
|
-
py_file_paths=self._all_py_files, filter_abstracts=True)
|
|
730
|
+
self._all_scenarios = self.get_all_scenario_classes(py_file_paths=all_scenario_filepaths, filter_abstracts=True)
|
|
698
731
|
# collect all `Setup` classes
|
|
699
|
-
self._all_setups = self.get_all_setup_classes(
|
|
700
|
-
py_file_paths=self._all_py_files, filter_abstracts=True)
|
|
732
|
+
self._all_setups = self.get_all_setup_classes(py_file_paths=all_setup_filepaths, filter_abstracts=True)
|
|
701
733
|
|
|
702
734
|
self._all_scenarios, self._all_setups = plugin_manager.execute_collected_classes(
|
|
703
735
|
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
|
_balder/console/balder.py
CHANGED
|
@@ -15,6 +15,7 @@ def console_balder(cmd_args: Optional[List[str]] = None, working_dir: Union[str,
|
|
|
15
15
|
_console_balder_debug(cmd_args=cmd_args, working_dir=working_dir)
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
# pylint: disable-next=too-many-arguments
|
|
18
19
|
def _console_balder_debug(cmd_args: Optional[List[str]] = None, working_dir: Union[str, pathlib.Path, None] = None,
|
|
19
20
|
cb_session_created: Optional[Callable] = None, cb_run_finished: Optional[Callable] = None,
|
|
20
21
|
cb_balder_exc: Optional[Callable] = None, cb_unexpected_exc: Optional[Callable] = None):
|
|
@@ -58,9 +58,8 @@ class BaseDeviceController(Controller, ABC):
|
|
|
58
58
|
This method returns the original instanced feature objects of the related device
|
|
59
59
|
"""
|
|
60
60
|
if self._original_instanced_features is None:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
'`save_all_original_instanced_features`')
|
|
61
|
+
raise RuntimeError('can not access the original instantiated features before they were set with '
|
|
62
|
+
'`save_all_original_instanced_features`')
|
|
64
63
|
return self._original_instanced_features
|
|
65
64
|
|
|
66
65
|
def save_all_original_instanced_features(self):
|
|
@@ -117,13 +117,14 @@ class FeatureController(Controller):
|
|
|
117
117
|
if self.get_method_based_for_vdevice() is not None:
|
|
118
118
|
for _, method_dict in self.get_method_based_for_vdevice().items():
|
|
119
119
|
for _, vdevice_dict in method_dict.items():
|
|
120
|
-
if for_vdevice in vdevice_dict.keys():
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
120
|
+
if for_vdevice not in vdevice_dict.keys():
|
|
121
|
+
continue
|
|
122
|
+
for cur_cnn in vdevice_dict[for_vdevice]:
|
|
123
|
+
if isinstance(cur_cnn, type):
|
|
124
|
+
cur_cnn = cur_cnn()
|
|
125
|
+
# clean metadata here because this is no connection between real devices
|
|
126
|
+
cur_cnn.set_metadata_for_all_subitems(None)
|
|
127
|
+
intersection.append(cur_cnn)
|
|
127
128
|
if len(intersection) == 0:
|
|
128
129
|
return [Connection()]
|
|
129
130
|
return intersection
|
|
@@ -151,8 +152,7 @@ class FeatureController(Controller):
|
|
|
151
152
|
This method returns the absolute calculated class-based-for-vdevice data for this feature.
|
|
152
153
|
"""
|
|
153
154
|
if self._abs_cls_for_vdevice is None:
|
|
154
|
-
|
|
155
|
-
raise EnvironmentError('can not access the absolute class based for-vdevices because they are not set')
|
|
155
|
+
raise RuntimeError('can not access the absolute class based for-vdevices because they are not set yet')
|
|
156
156
|
return self._abs_cls_for_vdevice
|
|
157
157
|
|
|
158
158
|
def set_class_based_for_vdevice(
|
|
@@ -718,9 +718,8 @@ class FeatureController(Controller):
|
|
|
718
718
|
This method returns the :class:`VDevice` definitions that are the original definitions for this feature.
|
|
719
719
|
"""
|
|
720
720
|
if self._original_vdevice_definitions is None:
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
'`save_all_current_vdevice_references_as_originals`')
|
|
721
|
+
raise RuntimeError('can not access the original VDevice definitions before they were set with '
|
|
722
|
+
'`save_all_current_vdevice_references_as_originals`')
|
|
724
723
|
return self._original_vdevice_definitions
|
|
725
724
|
|
|
726
725
|
def save_all_current_vdevice_references_as_originals(self):
|
_balder/exceptions.py
CHANGED
|
@@ -158,7 +158,7 @@ class IllegalVDeviceMappingError(BalderException):
|
|
|
158
158
|
"""
|
|
159
159
|
|
|
160
160
|
|
|
161
|
-
class
|
|
161
|
+
class NotApplicableVariationException(BalderException):
|
|
162
162
|
"""
|
|
163
163
|
is thrown internally after the current variation is not applicable
|
|
164
164
|
"""
|