baldertest 0.1.0b11__py3-none-any.whl → 0.1.0b13__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 +8 -3
- _balder/cnnrelations/and_connection_relation.py +28 -1
- _balder/cnnrelations/base_connection_relation.py +1 -1
- _balder/collector.py +101 -113
- _balder/connection.py +76 -80
- _balder/controllers/device_controller.py +17 -29
- _balder/controllers/feature_controller.py +2 -2
- _balder/controllers/normal_scenario_setup_controller.py +1 -2
- _balder/controllers/scenario_controller.py +121 -0
- _balder/controllers/vdevice_controller.py +2 -23
- _balder/decorator_connect.py +3 -3
- _balder/decorator_covered_by.py +28 -36
- _balder/executor/basic_executable_executor.py +13 -6
- _balder/executor/basic_executor.py +31 -4
- _balder/executor/executor_tree.py +5 -4
- _balder/executor/parametrized_testcase_executor.py +2 -2
- _balder/executor/scenario_executor.py +12 -71
- _balder/executor/setup_executor.py +4 -3
- _balder/executor/testcase_executor.py +35 -19
- _balder/executor/unresolved_parametrized_testcase_executor.py +32 -57
- _balder/executor/variation_executor.py +127 -148
- _balder/feature.py +2 -1
- _balder/feature_replacement_mapping.py +107 -0
- _balder/feature_vdevice_mapping.py +88 -0
- _balder/fixture_manager.py +1 -1
- _balder/fixture_metadata.py +1 -1
- _balder/routing_path.py +27 -16
- _balder/scenario.py +2 -2
- _balder/setup.py +2 -1
- _balder/testresult.py +4 -3
- _balder/utils/__init__.py +0 -0
- _balder/{utils.py → utils/functions.py} +29 -31
- _balder/utils/inner_device_managing_metaclass.py +14 -0
- _balder/utils/mixin_can_be_covered_by_executor.py +24 -0
- _balder/utils/typings.py +4 -0
- {baldertest-0.1.0b11.dist-info → baldertest-0.1.0b13.dist-info}/METADATA +3 -2
- {baldertest-0.1.0b11.dist-info → baldertest-0.1.0b13.dist-info}/RECORD +41 -35
- {baldertest-0.1.0b11.dist-info → baldertest-0.1.0b13.dist-info}/WHEEL +1 -1
- {baldertest-0.1.0b11.dist-info → baldertest-0.1.0b13.dist-info}/entry_points.txt +0 -0
- {baldertest-0.1.0b11.dist-info → baldertest-0.1.0b13.dist-info/licenses}/LICENSE +0 -0
- {baldertest-0.1.0b11.dist-info → baldertest-0.1.0b13.dist-info}/top_level.txt +0 -0
_balder/_version.py
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
# file generated by
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
2
|
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
3
6
|
TYPE_CHECKING = False
|
|
4
7
|
if TYPE_CHECKING:
|
|
5
|
-
from typing import Tuple
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
6
11
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
12
|
else:
|
|
8
13
|
VERSION_TUPLE = object
|
|
@@ -12,5 +17,5 @@ __version__: str
|
|
|
12
17
|
__version_tuple__: VERSION_TUPLE
|
|
13
18
|
version_tuple: VERSION_TUPLE
|
|
14
19
|
|
|
15
|
-
__version__ = version = '0.1.
|
|
20
|
+
__version__ = version = '0.1.0b13'
|
|
16
21
|
__version_tuple__ = version_tuple = (0, 1, 0)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
from typing import TYPE_CHECKING
|
|
2
|
+
from typing import TYPE_CHECKING, Type
|
|
3
3
|
|
|
4
4
|
import itertools
|
|
5
5
|
|
|
@@ -19,6 +19,33 @@ class AndConnectionRelation(BaseConnectionRelation):
|
|
|
19
19
|
based_on_strings = [cur_elem.get_tree_str() for cur_elem in self._connections]
|
|
20
20
|
return f"({' | '.join(based_on_strings)})"
|
|
21
21
|
|
|
22
|
+
def get_possibilities_for_direct_parent_cnn(self, for_cnn_class: Type[Connection]) -> list[AndConnectionRelation]:
|
|
23
|
+
"""
|
|
24
|
+
Helper method that returns a list of possible :class:`AndConnectionRelation` elements that hold the next parent
|
|
25
|
+
in the connection-tree that can be there instead of the origin connection.
|
|
26
|
+
The method returns a list, because there can exist different possibilities for this AND connection.
|
|
27
|
+
"""
|
|
28
|
+
direct_ancestors_relations = ()
|
|
29
|
+
for cur_and_elem in self.connections:
|
|
30
|
+
# `cur_and_elem` needs to be a connection, because we are using simplified which has only
|
|
31
|
+
# `OR[AND[Cnn, ...], Cnn, ..]`
|
|
32
|
+
if cur_and_elem.__class__ in for_cnn_class.get_parents():
|
|
33
|
+
# element already is a direct ancestor
|
|
34
|
+
direct_ancestors_relations += (cur_and_elem,)
|
|
35
|
+
else:
|
|
36
|
+
all_pos_possibilities = []
|
|
37
|
+
# add all possible direct parents to the possibilities list
|
|
38
|
+
for cur_direct_parent in for_cnn_class.get_parents():
|
|
39
|
+
if cur_direct_parent.is_parent_of(cur_and_elem.__class__):
|
|
40
|
+
all_pos_possibilities.append(cur_direct_parent.based_on(cur_and_elem))
|
|
41
|
+
direct_ancestors_relations += (all_pos_possibilities,)
|
|
42
|
+
# resolve the opportunities and create multiple possible AND relations where all elements are
|
|
43
|
+
# direct parents
|
|
44
|
+
return [
|
|
45
|
+
AndConnectionRelation(*cur_possibility).get_resolved()
|
|
46
|
+
for cur_possibility in itertools.product(*direct_ancestors_relations)
|
|
47
|
+
]
|
|
48
|
+
|
|
22
49
|
def get_simplified_relation(self) -> OrConnectionRelation:
|
|
23
50
|
from ..connection import Connection # pylint: disable=import-outside-toplevel
|
|
24
51
|
from .or_connection_relation import OrConnectionRelation # pylint: disable=import-outside-toplevel
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
from typing import List, Union, Type, Dict, TypeVar, TYPE_CHECKING
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
|
-
from
|
|
4
|
+
from _balder.utils.functions import cnn_type_check_and_convert
|
|
5
5
|
|
|
6
6
|
if TYPE_CHECKING:
|
|
7
7
|
from ..connection import Connection
|
_balder/collector.py
CHANGED
|
@@ -10,7 +10,7 @@ import inspect
|
|
|
10
10
|
import pathlib
|
|
11
11
|
import functools
|
|
12
12
|
import importlib.util
|
|
13
|
-
from _balder.utils import get_class_that_defines_method,
|
|
13
|
+
from _balder.utils.functions import get_class_that_defines_method, get_method_type
|
|
14
14
|
from _balder.setup import Setup
|
|
15
15
|
from _balder.device import Device
|
|
16
16
|
from _balder.feature import Feature
|
|
@@ -23,7 +23,8 @@ from _balder.fixture_execution_level import FixtureExecutionLevel
|
|
|
23
23
|
from _balder.controllers import ScenarioController, SetupController, DeviceController, VDeviceController, \
|
|
24
24
|
FeatureController, NormalScenarioSetupController
|
|
25
25
|
from _balder.exceptions import DuplicateForVDeviceError, UnknownVDeviceException
|
|
26
|
-
from _balder.utils import get_scenario_inheritance_list_of
|
|
26
|
+
from _balder.utils.functions import get_scenario_inheritance_list_of
|
|
27
|
+
from _balder.utils.typings import MethodLiteralType
|
|
27
28
|
|
|
28
29
|
if TYPE_CHECKING:
|
|
29
30
|
from _balder.plugin_manager import PluginManager
|
|
@@ -62,8 +63,13 @@ class Collector:
|
|
|
62
63
|
sys.path.insert(0, str(self.working_dir.parent.absolute()))
|
|
63
64
|
|
|
64
65
|
self._all_py_files: Union[List[pathlib.Path], None] = None
|
|
66
|
+
|
|
67
|
+
self._all_collected_scenarios: Union[List[Type[Scenario]], None] = None
|
|
68
|
+
self._all_collected_setups: Union[List[Type[Setup]], None] = None
|
|
69
|
+
|
|
65
70
|
self._all_scenarios: Union[List[Type[Scenario]], None] = None
|
|
66
71
|
self._all_setups: Union[List[Type[Setup]], None] = None
|
|
72
|
+
|
|
67
73
|
self._all_connections: Union[List[Type[Connection]], None] = None
|
|
68
74
|
|
|
69
75
|
self.balderglob_was_loaded = False
|
|
@@ -123,6 +129,20 @@ class Collector:
|
|
|
123
129
|
raise AttributeError("please call the `collect()` method before omitting this value")
|
|
124
130
|
return self._all_py_files
|
|
125
131
|
|
|
132
|
+
@property
|
|
133
|
+
def all_collected_scenarios(self) -> List[Type[Scenario]]:
|
|
134
|
+
"""returns a list of all collected scenarios that were found by the collector"""
|
|
135
|
+
if self._all_collected_scenarios is None:
|
|
136
|
+
raise AttributeError("please call the `collect()` method before omitting this value")
|
|
137
|
+
return self._all_collected_scenarios
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def all_collected_setups(self) -> List[Type[Setup]]:
|
|
141
|
+
"""returns a list of all collected setups that were found by the collector"""
|
|
142
|
+
if self._all_collected_setups is None:
|
|
143
|
+
raise AttributeError("please call the `collect()` method before omitting this value")
|
|
144
|
+
return self._all_collected_setups
|
|
145
|
+
|
|
126
146
|
@property
|
|
127
147
|
def all_scenarios(self) -> List[Type[Scenario]]:
|
|
128
148
|
"""returns a list of all scenarios that were found by the collector"""
|
|
@@ -154,6 +174,29 @@ class Collector:
|
|
|
154
174
|
raise AttributeError("please call the `collect()` method before omitting this value")
|
|
155
175
|
return self._all_connections
|
|
156
176
|
|
|
177
|
+
def get_class_and_method_type_for(self, func) -> Tuple[Union[type, None], MethodLiteralType]:
|
|
178
|
+
"""
|
|
179
|
+
This helper function returns the related class and the type of the method (`staticmethod`, `classmethod`,
|
|
180
|
+
`instancemethod` or `function`) as tuple.
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
available_classes = self.all_collected_scenarios + self.all_collected_setups
|
|
184
|
+
available_classes_with_mro = []
|
|
185
|
+
for cur_class in available_classes:
|
|
186
|
+
available_classes_with_mro.extend([*inspect.getmro(cur_class)])
|
|
187
|
+
available_classes_with_mro = set(available_classes_with_mro)
|
|
188
|
+
|
|
189
|
+
qualname = func.__qualname__
|
|
190
|
+
|
|
191
|
+
if '.' not in qualname:
|
|
192
|
+
return None, 'function'
|
|
193
|
+
|
|
194
|
+
expected_class_name = qualname.rpartition('.')[0]
|
|
195
|
+
for cur_class in available_classes_with_mro:
|
|
196
|
+
if cur_class.__qualname__ == expected_class_name:
|
|
197
|
+
return cur_class, get_method_type(cur_class, func)
|
|
198
|
+
raise ValueError(f'function {func.__qualname__} is not part of any scenario or setup')
|
|
199
|
+
|
|
157
200
|
def get_fixture_manager(self) -> FixtureManager:
|
|
158
201
|
"""
|
|
159
202
|
Resolves all fixtures and returns the fixture manager for this session
|
|
@@ -164,7 +207,7 @@ class Collector:
|
|
|
164
207
|
cur_level = FixtureExecutionLevel(cur_level_as_str)
|
|
165
208
|
resolved_dict[cur_level] = {}
|
|
166
209
|
for cur_fn in cur_module_fixture_dict:
|
|
167
|
-
cls, func_type =
|
|
210
|
+
cls, func_type = self.get_class_and_method_type_for(cur_fn)
|
|
168
211
|
# mechanism also works for balderglob fixtures (`func_type` is 'function' and `cls` is None)
|
|
169
212
|
if cls not in resolved_dict[cur_level].keys():
|
|
170
213
|
resolved_dict[cur_level][cls] = []
|
|
@@ -352,120 +395,62 @@ class Collector:
|
|
|
352
395
|
result.append(cur_class)
|
|
353
396
|
return result
|
|
354
397
|
|
|
355
|
-
def
|
|
356
|
-
"""
|
|
357
|
-
This method secures that the scenario classes have a valid
|
|
358
|
-
"""
|
|
398
|
+
def _validate_skip_ignore(self):
|
|
399
|
+
"""
|
|
400
|
+
This method secures that the scenario classes have a valid SKIP and IGNORE attribute.
|
|
401
|
+
"""
|
|
402
|
+
ignore_str = "IGNORE"
|
|
403
|
+
skip_str = "SKIP"
|
|
404
|
+
|
|
405
|
+
def validate(scenario_class, list_attr_name: str):
|
|
406
|
+
# IGNORE/SKIP is mentioned in this specific class
|
|
407
|
+
if not isinstance(getattr(scenario_class, list_attr_name), list):
|
|
408
|
+
raise TypeError(f"the class attribute `{cur_class.__name__}.{list_attr_name}` has to be from type list")
|
|
409
|
+
# check that all elements that are mentioned here exists as a valid test method
|
|
410
|
+
for method in getattr(scenario_class, list_attr_name):
|
|
411
|
+
if isinstance(method, str):
|
|
412
|
+
if not hasattr(scenario_class, method):
|
|
413
|
+
raise ValueError(f'can not find the mentioned attribute `{method}` from '
|
|
414
|
+
f'{list_attr_name} list for scenario `{scenario_class}`')
|
|
415
|
+
if not method.startswith('test_'):
|
|
416
|
+
raise ValueError(f'the attribute `{method}` mentioned in {list_attr_name} list of scenario '
|
|
417
|
+
f'`{scenario_class}` is no valid test method and can not be used here')
|
|
418
|
+
elif not inspect.ismethod(method) or not method.__name__.startswith('test_'):
|
|
419
|
+
raise TypeError(f"the given element {method.__name__} for class attribute "
|
|
420
|
+
f"`{scenario_class.__name__}.{list_attr_name}` is no valid test method")
|
|
421
|
+
return True
|
|
422
|
+
|
|
359
423
|
for cur_scenario in self._all_scenarios:
|
|
360
424
|
# determines hierarchy of inherited classes
|
|
361
425
|
base_classes = get_scenario_inheritance_list_of(cur_scenario)
|
|
362
426
|
# removes the last scenario class
|
|
363
427
|
base_classes = base_classes[:-1]
|
|
364
|
-
# now determine all
|
|
428
|
+
# now determine all SKIP and IGNORE values if they aren't already mentioned - if there exists a value
|
|
365
429
|
# for them, check if the value is valid
|
|
366
430
|
base_classes.reverse()
|
|
431
|
+
# now go through all RUN/SKIP/IGNORE values and check if the values are correct here
|
|
367
432
|
for cur_idx, cur_class in enumerate(base_classes):
|
|
368
433
|
next_parent = None if cur_idx == 0 else base_classes[cur_idx - 1]
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
# SKIP is mentioned in this specific class
|
|
390
|
-
if not isinstance(cur_class.SKIP, list):
|
|
391
|
-
raise TypeError(f"the class attribute `{cur_class.__name__}.SKIP` has to be from type list")
|
|
392
|
-
# check that all elements that are mentioned here exist in the parent's RUN or SKIP class variable
|
|
393
|
-
# or are defined in this class
|
|
394
|
-
for cur_skip_method in cur_class.SKIP:
|
|
395
|
-
if not inspect.ismethod(cur_skip_method) or \
|
|
396
|
-
not cur_skip_method.__name__.startswith('test_'):
|
|
397
|
-
raise TypeError(f"the given element {cur_skip_method.__name__} for class attribute "
|
|
398
|
-
f"`{cur_class.__name__}.IGNORE` is no valid test method")
|
|
399
|
-
if cur_skip_method not in cur_class.__dict__.values():
|
|
400
|
-
if next_parent is None:
|
|
401
|
-
raise ValueError(f"the element `{cur_skip_method.__name__}` given at class attribute "
|
|
402
|
-
f"`{cur_class.__name__}.SKIP` is not a test method of this scenario")
|
|
403
|
-
|
|
404
|
-
if cur_skip_method in next_parent.IGNORE:
|
|
405
|
-
raise ValueError(f"the element `{cur_skip_method.__name__}` given at class "
|
|
406
|
-
f"attribute `{cur_class.__name__}.SKIP` was already added to "
|
|
407
|
-
f"IGNORE in a higher parent class - not possible to add it now to "
|
|
408
|
-
f"SKIP")
|
|
409
|
-
|
|
410
|
-
if cur_skip_method not in next_parent.SKIP and cur_skip_method not in next_parent.IGNORE:
|
|
411
|
-
raise ValueError(f"the element `{cur_skip_method.__name__}` given at class "
|
|
412
|
-
f"attribute `{cur_class.__name__}.SKIP` is not a test method of "
|
|
413
|
-
f"this scenario")
|
|
414
|
-
else:
|
|
415
|
-
# SKIP is not mentioned in this specific class -> so add an empty list for further access
|
|
416
|
-
cur_class.SKIP = []
|
|
417
|
-
if "RUN" in cur_class.__dict__.keys():
|
|
418
|
-
# RUN is mentioned in this specific class
|
|
419
|
-
if not isinstance(cur_class.RUN, list):
|
|
420
|
-
raise TypeError(f"the class attribute `{cur_class.__name__}.RUN` has to be from type list")
|
|
421
|
-
# check that all elements that are mentioned here exists in the parent's RUN class variable or are
|
|
422
|
-
# defined in this class
|
|
423
|
-
for cur_run_method in cur_class.RUN:
|
|
424
|
-
if not inspect.ismethod(cur_run_method) or \
|
|
425
|
-
not cur_run_method.__name__.startswith('test_'):
|
|
426
|
-
raise TypeError(f"the given element {cur_run_method.__name__} for class attribute "
|
|
427
|
-
f"`{cur_class.__name__}.RUN` is no valid test method")
|
|
428
|
-
if cur_run_method not in cur_class.__dict__.values():
|
|
429
|
-
if next_parent is None:
|
|
430
|
-
raise ValueError(
|
|
431
|
-
f"the element `{cur_run_method.__name__}` given at class attribute "
|
|
432
|
-
f"`{cur_class.__name__}.RUN` is not a test method of this scenario")
|
|
433
|
-
|
|
434
|
-
if cur_run_method in next_parent.IGNORE:
|
|
435
|
-
raise ValueError(
|
|
436
|
-
f"the element `{cur_run_method.__name__}` given at class attribute "
|
|
437
|
-
f"`{cur_class.__name__}.RUN` was already added to IGNORE in a higher parent "
|
|
438
|
-
f"class - not possible to add it now to RUN")
|
|
439
|
-
if cur_run_method in next_parent.SKIP:
|
|
440
|
-
raise ValueError(
|
|
441
|
-
f"the element `{cur_run_method.__name__}` given at class attribute "
|
|
442
|
-
f"`{cur_class.__name__}.RUN` was already added to SKIP in a higher parent "
|
|
443
|
-
f"class - not possible to add it now to RUN")
|
|
444
|
-
if cur_run_method not in next_parent.RUN:
|
|
445
|
-
raise ValueError(
|
|
446
|
-
f"the element `{cur_run_method.__name__}` given at class attribute "
|
|
447
|
-
f"`{cur_class.__name__}.RUN` is not a test method of this scenario")
|
|
448
|
-
else:
|
|
449
|
-
# RUN is not mentioned in this specific class -> so add an empty list for further access
|
|
450
|
-
cur_class.RUN = []
|
|
451
|
-
|
|
452
|
-
# also add all in this class defined test methods to this RUN list
|
|
453
|
-
for cur_item_name, cur_item in cur_class.__dict__.items():
|
|
454
|
-
if cur_item_name.startswith("test_") and inspect.ismethod(cur_item):
|
|
455
|
-
cur_class.RUN.append(cur_item)
|
|
456
|
-
# now add all items from parent classes to the lists that are not mentioned yet
|
|
457
|
-
if next_parent is not None:
|
|
458
|
-
for cur_parent_run_method in next_parent.RUN:
|
|
459
|
-
if cur_parent_run_method not in cur_class.RUN and cur_parent_run_method not in cur_class.SKIP \
|
|
460
|
-
and cur_parent_run_method not in cur_class.IGNORE:
|
|
461
|
-
cur_class.RUN.append(cur_parent_run_method)
|
|
462
|
-
for cur_parent_skip_method in next_parent.SKIP:
|
|
463
|
-
if cur_parent_skip_method not in cur_class.SKIP \
|
|
464
|
-
and cur_parent_skip_method not in cur_class.IGNORE:
|
|
465
|
-
cur_class.SKIP.append(cur_parent_skip_method)
|
|
466
|
-
for cur_parent_ignore_method in next_parent.IGNORE:
|
|
467
|
-
if cur_parent_ignore_method not in cur_class.IGNORE:
|
|
468
|
-
cur_class.IGNORE.append(cur_parent_ignore_method)
|
|
434
|
+
|
|
435
|
+
validate(cur_class, 'IGNORE')
|
|
436
|
+
validate(cur_class, 'SKIP')
|
|
437
|
+
|
|
438
|
+
# make sure that the method is not mentioned in IGNORE and in SKIP
|
|
439
|
+
ignore_list = cur_class.__dict__.get(ignore_str, [])
|
|
440
|
+
skip_list = cur_class.__dict__.get(skip_str, [])
|
|
441
|
+
for cur_ignore_method_as_str in ignore_list:
|
|
442
|
+
if cur_ignore_method_as_str in skip_list:
|
|
443
|
+
raise ValueError(f'mentioned test method `{cur_ignore_method_as_str}` is in '
|
|
444
|
+
f'`{cur_class.__name__}.IGNORE` and `{cur_class.__name__}.SKIP`')
|
|
445
|
+
# make sure that the skip method was not defined in IGNORE in parent classes
|
|
446
|
+
if next_parent:
|
|
447
|
+
for cur_skip_method_as_str in skip_list:
|
|
448
|
+
cur_skip_method = getattr(cur_class, cur_skip_method_as_str)
|
|
449
|
+
if cur_skip_method in ScenarioController.get_for(next_parent).get_ignore_test_methods():
|
|
450
|
+
raise ValueError(f"the element `{cur_skip_method.__name__}` given at class "
|
|
451
|
+
f"attribute `{cur_class.__name__}.SKIP` was already added to "
|
|
452
|
+
f"IGNORE in a higher parent class - not possible to add it now to "
|
|
453
|
+
f"SKIP")
|
|
469
454
|
|
|
470
455
|
@staticmethod
|
|
471
456
|
def rework_method_variation_decorators():
|
|
@@ -829,12 +814,16 @@ class Collector:
|
|
|
829
814
|
self._all_connections = self.get_all_connection_classes()
|
|
830
815
|
|
|
831
816
|
# collect all `Scenario` classes
|
|
832
|
-
self.
|
|
817
|
+
self._all_collected_scenarios = self.get_all_scenario_classes(
|
|
818
|
+
py_file_paths=all_scenario_filepaths, filter_abstracts=True
|
|
819
|
+
)
|
|
833
820
|
# collect all `Setup` classes
|
|
834
|
-
self.
|
|
821
|
+
self._all_collected_setups = self.get_all_setup_classes(
|
|
822
|
+
py_file_paths=all_setup_filepaths, filter_abstracts=True
|
|
823
|
+
)
|
|
835
824
|
|
|
836
825
|
self._all_scenarios, self._all_setups = plugin_manager.execute_collected_classes(
|
|
837
|
-
scenarios=self.
|
|
826
|
+
scenarios=self._all_collected_scenarios, setups=self._all_collected_setups)
|
|
838
827
|
|
|
839
828
|
self._all_scenarios = Collector.filter_parent_classes_of(items=self._all_scenarios)
|
|
840
829
|
self._all_setups = Collector.filter_parent_classes_of(items=self._all_setups)
|
|
@@ -847,8 +836,7 @@ class Collector:
|
|
|
847
836
|
self._set_original_device_features()
|
|
848
837
|
self._exchange_strings_with_objects()
|
|
849
838
|
|
|
850
|
-
|
|
851
|
-
self.set_run_skip_ignore_of_test_methods_in_scenarios()
|
|
839
|
+
self._validate_skip_ignore()
|
|
852
840
|
|
|
853
841
|
self._validate_scenario_and_setups()
|
|
854
842
|
|
_balder/connection.py
CHANGED
|
@@ -8,7 +8,7 @@ from _balder.connection_metadata import ConnectionMetadata
|
|
|
8
8
|
from _balder.device import Device
|
|
9
9
|
from _balder.exceptions import IllegalConnectionTypeError
|
|
10
10
|
from _balder.cnnrelations import AndConnectionRelation, OrConnectionRelation
|
|
11
|
-
from _balder.utils import cnn_type_check_and_convert
|
|
11
|
+
from _balder.utils.functions import cnn_type_check_and_convert
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class ConnectionType(type):
|
|
@@ -100,7 +100,7 @@ class Connection(metaclass=ConnectionType):
|
|
|
100
100
|
|
|
101
101
|
:param tree_name: the tree name the parents should be returned (default: use tree defined in `GlobalSetting`)
|
|
102
102
|
"""
|
|
103
|
-
from _balder.balder_session import BalderSession
|
|
103
|
+
from _balder.balder_session import BalderSession # pylint: disable=import-outside-toplevel
|
|
104
104
|
if tree_name is None:
|
|
105
105
|
tree_name = BalderSession.get_current_active_global_conntree_name()
|
|
106
106
|
conn_dict = Connection.__parents.get(cls, {})
|
|
@@ -241,6 +241,62 @@ class Connection(metaclass=ConnectionType):
|
|
|
241
241
|
|
|
242
242
|
# ---------------------------------- PROTECTED METHODS -------------------------------------------------------------
|
|
243
243
|
|
|
244
|
+
def _is_directly_contained_in(self, other_conn: Connection, ignore_metadata: bool) -> bool:
|
|
245
|
+
"""
|
|
246
|
+
Helper method that returns true if this connection is directly contained in the `other_conn`.
|
|
247
|
+
|
|
248
|
+
.. note::
|
|
249
|
+
'Directly contained-in' means that the resolved versions of both connections are of the same connection
|
|
250
|
+
type, and the elements of that connection are directly embedded within one another from that point onward.
|
|
251
|
+
It does not check deeper into the other_conn.
|
|
252
|
+
|
|
253
|
+
:param other_conn: the other connection
|
|
254
|
+
:param ignore_metadata: True, if the metadata should be ignored
|
|
255
|
+
"""
|
|
256
|
+
|
|
257
|
+
resolved_self = self.get_resolved()
|
|
258
|
+
resolved_other = other_conn.get_resolved()
|
|
259
|
+
|
|
260
|
+
if resolved_self.__class__ == Connection and len(resolved_self.based_on_elements) == 0 or \
|
|
261
|
+
resolved_other.__class__ == Connection and len(resolved_other.based_on_elements) == 0:
|
|
262
|
+
# one of the resolved object is a raw `Connection` object without based-on-elements -> always true
|
|
263
|
+
return True
|
|
264
|
+
|
|
265
|
+
if resolved_self.__class__ == Connection:
|
|
266
|
+
return resolved_self.based_on_elements.contained_in(resolved_other, ignore_metadata=ignore_metadata)
|
|
267
|
+
|
|
268
|
+
if resolved_self.__class__ == resolved_other.__class__:
|
|
269
|
+
# The element itself has already matched, now we still have to check whether at least one inner element
|
|
270
|
+
# of this type is contained in minimum one element of the other
|
|
271
|
+
singles_self = resolved_self.get_singles()
|
|
272
|
+
singles_other = other_conn.get_singles()
|
|
273
|
+
|
|
274
|
+
# check that for one single_self element all hierarchical based_on elements are in one of the single
|
|
275
|
+
# other element
|
|
276
|
+
for cur_single_self, cur_single_other in itertools.product(singles_self, singles_other):
|
|
277
|
+
# check if both consists of only one element
|
|
278
|
+
if len(cur_single_self.based_on_elements) == 0:
|
|
279
|
+
# the cur self single is only one element -> this is contained in the other
|
|
280
|
+
return True
|
|
281
|
+
|
|
282
|
+
if len(cur_single_other.based_on_elements) == 0:
|
|
283
|
+
# the other element is only one element, but the self element not -> contained_in
|
|
284
|
+
# for this single definitely false
|
|
285
|
+
continue
|
|
286
|
+
|
|
287
|
+
# note: for both only one `based_on_elements` is possible, because they are singles
|
|
288
|
+
self_first_basedon = cur_single_self.based_on_elements[0]
|
|
289
|
+
other_first_basedon = cur_single_other.based_on_elements[0]
|
|
290
|
+
|
|
291
|
+
if isinstance(self_first_basedon, Connection) and \
|
|
292
|
+
isinstance(other_first_basedon, AndConnectionRelation):
|
|
293
|
+
continue
|
|
294
|
+
|
|
295
|
+
# find a complete valid match
|
|
296
|
+
if self_first_basedon.contained_in(other_first_basedon, ignore_metadata=ignore_metadata):
|
|
297
|
+
return True
|
|
298
|
+
return False
|
|
299
|
+
|
|
244
300
|
# ---------------------------------- METHODS -----------------------------------------------------------------------
|
|
245
301
|
|
|
246
302
|
def get_intersection_with_other_single(
|
|
@@ -516,25 +572,8 @@ class Connection(metaclass=ConnectionType):
|
|
|
516
572
|
for next_higher_parent in simplified_based_on:
|
|
517
573
|
if isinstance(next_higher_parent, AndConnectionRelation):
|
|
518
574
|
# determine all possibilities
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
# `cur_and_elem` needs to be a connection, because we are using simplified which has only
|
|
522
|
-
# `OR[AND[Cnn, ...], Cnn, ..]`
|
|
523
|
-
if cur_and_elem.__class__ in self.__class__.get_parents():
|
|
524
|
-
# element already is a direct ancestor
|
|
525
|
-
direct_ancestors_relations += (cur_and_elem, )
|
|
526
|
-
else:
|
|
527
|
-
all_pos_possibilities = []
|
|
528
|
-
# add all possible direct parents to the possibilities list
|
|
529
|
-
for cur_direct_parent in self.__class__.get_parents():
|
|
530
|
-
if cur_direct_parent.is_parent_of(cur_and_elem.__class__):
|
|
531
|
-
all_pos_possibilities.append(cur_direct_parent.based_on(cur_and_elem))
|
|
532
|
-
direct_ancestors_relations += (all_pos_possibilities, )
|
|
533
|
-
# resolve the opportunities and create multiple possible AND relations where all elements are
|
|
534
|
-
# direct parents
|
|
535
|
-
for cur_possibility in itertools.product(*direct_ancestors_relations):
|
|
536
|
-
new_child_relation = AndConnectionRelation(*cur_possibility).get_resolved()
|
|
537
|
-
copied_base.append_to_based_on(new_child_relation)
|
|
575
|
+
for new_and_relation in next_higher_parent.get_possibilities_for_direct_parent_cnn(self.__class__):
|
|
576
|
+
copied_base.append_to_based_on(new_and_relation)
|
|
538
577
|
else:
|
|
539
578
|
# `next_higher_parent` needs to be a connection, because we are using simplified which has only
|
|
540
579
|
# `OR[AND[Cnn, ...], Cnn, ..]`
|
|
@@ -673,9 +712,10 @@ class Connection(metaclass=ConnectionType):
|
|
|
673
712
|
ignore_metadata=False
|
|
674
713
|
) -> bool:
|
|
675
714
|
"""
|
|
676
|
-
This method
|
|
677
|
-
object
|
|
678
|
-
whether
|
|
715
|
+
This method determines if one connection tree can be embedded within another connection tree. A connection
|
|
716
|
+
object represents a specific segment of the extensive connection tree managed by Balder. The method evaluates
|
|
717
|
+
whether at least one SINGLE connection object of this connection tree fits within a possible SINGLE connection
|
|
718
|
+
of the provided connection `other_conn`.
|
|
679
719
|
|
|
680
720
|
.. note::
|
|
681
721
|
The method returns true if one single connection of this object fits in another single connection that is
|
|
@@ -698,70 +738,26 @@ class Connection(metaclass=ConnectionType):
|
|
|
698
738
|
resolved_self = self.get_resolved()
|
|
699
739
|
resolved_other = other_conn.get_resolved()
|
|
700
740
|
|
|
701
|
-
if
|
|
702
|
-
resolved_other.__class__ == Connection and len(resolved_other.based_on_elements) == 0:
|
|
703
|
-
# one of the resolved object is a raw `Connection` object without based-on-elements -> always true
|
|
741
|
+
if self._is_directly_contained_in(resolved_other, ignore_metadata=ignore_metadata):
|
|
704
742
|
return True
|
|
705
743
|
|
|
706
|
-
|
|
707
|
-
return resolved_self.based_on_elements.contained_in(resolved_other, ignore_metadata=ignore_metadata)
|
|
744
|
+
# the elements itself do not match -> go deeper within the other connection
|
|
708
745
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
# of this type is contained in the minimum one element of the other
|
|
712
|
-
singles_self = resolved_self.get_singles()
|
|
713
|
-
singles_other = resolved_other.get_singles()
|
|
746
|
+
resolved_other_relation = resolved_other.based_on_elements \
|
|
747
|
+
if isinstance(resolved_other, Connection) else resolved_other
|
|
714
748
|
|
|
715
|
-
|
|
716
|
-
#
|
|
717
|
-
|
|
718
|
-
# check if both consists of only one element
|
|
719
|
-
if len(cur_single_self.based_on_elements) == 0:
|
|
720
|
-
# the cur self single is only one element -> this is contained in the other
|
|
721
|
-
return True
|
|
722
|
-
|
|
723
|
-
if len(cur_single_other.based_on_elements) == 0:
|
|
724
|
-
# the other element is only one element, but the self element not -> contained_in
|
|
725
|
-
# for this single definitely false
|
|
726
|
-
continue
|
|
727
|
-
|
|
728
|
-
# note: for both only one `based_on_elements` is possible, because they are singles
|
|
729
|
-
self_first_based_on = cur_single_self.based_on_elements[0]
|
|
730
|
-
other_first_based_on = cur_single_other.based_on_elements[0]
|
|
731
|
-
|
|
732
|
-
self_is_and = isinstance(self_first_based_on, AndConnectionRelation)
|
|
733
|
-
self_is_cnn = isinstance(self_first_based_on, Connection)
|
|
734
|
-
other_is_and = isinstance(other_first_based_on, AndConnectionRelation)
|
|
735
|
-
other_is_cnn = isinstance(other_first_based_on, Connection)
|
|
736
|
-
|
|
737
|
-
if self_is_and and (other_is_and or other_is_cnn) or self_is_cnn and other_is_cnn:
|
|
738
|
-
# find a complete valid match
|
|
739
|
-
if self_first_based_on.contained_in(other_first_based_on, ignore_metadata=ignore_metadata):
|
|
740
|
-
return True
|
|
741
|
-
# skip all others possibilities
|
|
742
|
-
else:
|
|
743
|
-
# the elements itself do not match -> go deeper within the other connection
|
|
744
|
-
if isinstance(resolved_other, AndConnectionRelation):
|
|
749
|
+
for cur_other_based_on in resolved_other_relation.connections:
|
|
750
|
+
# `cur_other_based_on` can only be a Connection or an AND (resolved can not ba a inner OR)
|
|
751
|
+
if isinstance(cur_other_based_on, AndConnectionRelation):
|
|
745
752
|
# check if the current connection fits in one of the AND relation items -> allowed too (f.e. a
|
|
746
753
|
# smaller AND contained in a bigger AND)
|
|
747
|
-
for cur_other_and_element in
|
|
754
|
+
for cur_other_and_element in cur_other_based_on.connections:
|
|
748
755
|
if resolved_self.contained_in(cur_other_and_element, ignore_metadata=ignore_metadata):
|
|
749
756
|
return True
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
for cur_other_based_on in resolved_other_relation.connections:
|
|
755
|
-
if isinstance(cur_other_based_on, AndConnectionRelation):
|
|
756
|
-
# check if the current connection fits in one of the AND relation items -> allowed too (f.e. a
|
|
757
|
-
# smaller AND contained in a bigger AND)
|
|
758
|
-
for cur_other_and_element in cur_other_based_on.connections:
|
|
759
|
-
if resolved_self.contained_in(cur_other_and_element, ignore_metadata=ignore_metadata):
|
|
760
|
-
return True
|
|
761
|
-
else:
|
|
762
|
-
if resolved_self.contained_in(cur_other_based_on, ignore_metadata=ignore_metadata):
|
|
763
|
-
# element was found in this branch
|
|
764
|
-
return True
|
|
757
|
+
else:
|
|
758
|
+
if resolved_self.contained_in(cur_other_based_on, ignore_metadata=ignore_metadata):
|
|
759
|
+
# element was found in this branch
|
|
760
|
+
return True
|
|
765
761
|
return False
|
|
766
762
|
|
|
767
763
|
def intersection_with(
|