baldertest 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- _balder/__init__.py +12 -0
- _balder/_version.py +34 -0
- _balder/balder_plugin.py +73 -0
- _balder/balder_session.py +341 -0
- _balder/balder_settings.py +15 -0
- _balder/cnnrelations/__init__.py +7 -0
- _balder/cnnrelations/and_connection_relation.py +176 -0
- _balder/cnnrelations/base_connection_relation.py +270 -0
- _balder/cnnrelations/or_connection_relation.py +65 -0
- _balder/collector.py +874 -0
- _balder/connection.py +863 -0
- _balder/connection_metadata.py +255 -0
- _balder/console/__init__.py +0 -0
- _balder/console/balder.py +58 -0
- _balder/controllers/__init__.py +12 -0
- _balder/controllers/base_device_controller.py +72 -0
- _balder/controllers/controller.py +29 -0
- _balder/controllers/device_controller.py +446 -0
- _balder/controllers/feature_controller.py +715 -0
- _balder/controllers/normal_scenario_setup_controller.py +402 -0
- _balder/controllers/scenario_controller.py +524 -0
- _balder/controllers/setup_controller.py +134 -0
- _balder/controllers/vdevice_controller.py +95 -0
- _balder/decorator_connect.py +104 -0
- _balder/decorator_covered_by.py +74 -0
- _balder/decorator_fixture.py +29 -0
- _balder/decorator_for_vdevice.py +118 -0
- _balder/decorator_gateway.py +34 -0
- _balder/decorator_insert_into_tree.py +52 -0
- _balder/decorator_parametrize.py +31 -0
- _balder/decorator_parametrize_by_feature.py +36 -0
- _balder/device.py +18 -0
- _balder/exceptions.py +182 -0
- _balder/executor/__init__.py +0 -0
- _balder/executor/basic_executable_executor.py +133 -0
- _balder/executor/basic_executor.py +205 -0
- _balder/executor/executor_tree.py +217 -0
- _balder/executor/parametrized_testcase_executor.py +52 -0
- _balder/executor/scenario_executor.py +169 -0
- _balder/executor/setup_executor.py +163 -0
- _balder/executor/testcase_executor.py +203 -0
- _balder/executor/unresolved_parametrized_testcase_executor.py +184 -0
- _balder/executor/variation_executor.py +882 -0
- _balder/exit_code.py +19 -0
- _balder/feature.py +74 -0
- _balder/feature_replacement_mapping.py +107 -0
- _balder/feature_vdevice_mapping.py +88 -0
- _balder/fixture_definition_scope.py +19 -0
- _balder/fixture_execution_level.py +22 -0
- _balder/fixture_manager.py +483 -0
- _balder/fixture_metadata.py +26 -0
- _balder/node_gateway.py +103 -0
- _balder/objects/__init__.py +0 -0
- _balder/objects/connections/__init__.py +0 -0
- _balder/objects/connections/osi_1_physical.py +116 -0
- _balder/objects/connections/osi_2_datalink.py +35 -0
- _balder/objects/connections/osi_3_network.py +47 -0
- _balder/objects/connections/osi_4_transport.py +40 -0
- _balder/objects/connections/osi_5_session.py +13 -0
- _balder/objects/connections/osi_6_presentation.py +13 -0
- _balder/objects/connections/osi_7_application.py +83 -0
- _balder/objects/devices/__init__.py +0 -0
- _balder/objects/devices/this_device.py +12 -0
- _balder/parametrization.py +75 -0
- _balder/plugin_manager.py +138 -0
- _balder/previous_executor_mark.py +23 -0
- _balder/routing_path.py +335 -0
- _balder/scenario.py +20 -0
- _balder/setup.py +18 -0
- _balder/solver.py +246 -0
- _balder/testresult.py +163 -0
- _balder/unmapped_vdevice.py +13 -0
- _balder/utils/__init__.py +0 -0
- _balder/utils/functions.py +103 -0
- _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
- _balder/vdevice.py +9 -0
- balder/__init__.py +56 -0
- balder/connections.py +43 -0
- balder/devices.py +9 -0
- balder/exceptions.py +44 -0
- balder/parametrization.py +8 -0
- baldertest-0.1.0.dist-info/METADATA +356 -0
- baldertest-0.1.0.dist-info/RECORD +89 -0
- baldertest-0.1.0.dist-info/WHEEL +5 -0
- baldertest-0.1.0.dist-info/entry_points.txt +2 -0
- baldertest-0.1.0.dist-info/licenses/LICENSE +21 -0
- baldertest-0.1.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Type, Dict, Union
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
from _balder.vdevice import VDevice
|
|
6
|
+
from _balder.feature import Feature
|
|
7
|
+
from _balder.controllers.base_device_controller import BaseDeviceController
|
|
8
|
+
from _balder.exceptions import VDeviceResolvingError
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__file__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class VDeviceController(BaseDeviceController):
|
|
14
|
+
"""
|
|
15
|
+
This is the controller class for :class:`VDevice` items.
|
|
16
|
+
"""
|
|
17
|
+
# helper property to disable manual constructor creation
|
|
18
|
+
__priv_instantiate_key = object()
|
|
19
|
+
|
|
20
|
+
#: contains all existing VDevices and its corresponding controller object
|
|
21
|
+
_items: Dict[Type[VDevice], VDeviceController] = {}
|
|
22
|
+
|
|
23
|
+
def __init__(self, related_cls, _priv_instantiate_key):
|
|
24
|
+
super().__init__()
|
|
25
|
+
|
|
26
|
+
# this helps to make this constructor only possible inside the controller object
|
|
27
|
+
if _priv_instantiate_key != VDeviceController.__priv_instantiate_key:
|
|
28
|
+
raise RuntimeError('it is not allowed to instantiate a controller manually -> use the static method '
|
|
29
|
+
'`VDeviceController.get_for()` for it')
|
|
30
|
+
|
|
31
|
+
if not isinstance(related_cls, type):
|
|
32
|
+
raise TypeError('the attribute `related_cls` has to be a type (no object)')
|
|
33
|
+
if not issubclass(related_cls, VDevice):
|
|
34
|
+
raise TypeError(f'the attribute `related_cls` has to be a sub-type of `{VDevice.__name__}`')
|
|
35
|
+
if related_cls == VDevice:
|
|
36
|
+
raise TypeError(f'the attribute `related_cls` is `{VDevice.__name__}` - controllers for native type are '
|
|
37
|
+
f'forbidden')
|
|
38
|
+
# contains a reference to the related class this controller instance belongs to
|
|
39
|
+
self._related_cls = related_cls
|
|
40
|
+
|
|
41
|
+
# ---------------------------------- STATIC METHODS ----------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def get_for(related_cls: Type[VDevice]) -> VDeviceController:
|
|
45
|
+
"""
|
|
46
|
+
This class returns the current existing controller instance for the given item. If the instance does not exist
|
|
47
|
+
yet, it will automatically create it and saves the instance in an internal dictionary.
|
|
48
|
+
"""
|
|
49
|
+
if VDeviceController._items.get(related_cls) is None:
|
|
50
|
+
item = VDeviceController(related_cls, _priv_instantiate_key=VDeviceController.__priv_instantiate_key)
|
|
51
|
+
VDeviceController._items[related_cls] = item
|
|
52
|
+
|
|
53
|
+
return VDeviceController._items.get(related_cls)
|
|
54
|
+
|
|
55
|
+
# ---------------------------------- CLASS METHODS -----------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
# ---------------------------------- PROPERTIES --------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def related_cls(self) -> Type[VDevice]:
|
|
61
|
+
return self._related_cls
|
|
62
|
+
|
|
63
|
+
# ---------------------------------- PROTECTED METHODS -------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
# ---------------------------------- METHODS -----------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
def get_outer_class(self) -> Union[Type[Feature], None]:
|
|
68
|
+
"""
|
|
69
|
+
This method delivers the outer class of this device. In Balder, this has to be a :class:`Feature`.
|
|
70
|
+
"""
|
|
71
|
+
return getattr(self.related_cls, '_outer_balder_class', None)
|
|
72
|
+
|
|
73
|
+
def get_next_parent_vdevice(self) -> Union[Type[VDevice], None]:
|
|
74
|
+
"""
|
|
75
|
+
This method returns the next parent VDevice class, which is still a subclass of :class:`VDevice`. If the next
|
|
76
|
+
parent class is :class:`VDevice`, None will be returned.
|
|
77
|
+
|
|
78
|
+
:return: the parent VDevice class or None if no parent exists
|
|
79
|
+
"""
|
|
80
|
+
possible_vdevices_of_interest = []
|
|
81
|
+
for cur_vdevice_of_interest in self.related_cls.__bases__:
|
|
82
|
+
if issubclass(cur_vdevice_of_interest, VDevice) and cur_vdevice_of_interest != VDevice:
|
|
83
|
+
possible_vdevices_of_interest.append(cur_vdevice_of_interest)
|
|
84
|
+
|
|
85
|
+
if len(possible_vdevices_of_interest) > 1:
|
|
86
|
+
raise VDeviceResolvingError(
|
|
87
|
+
f"the vdevice `{self.related_cls.__name__}` has more than one parent classes from type "
|
|
88
|
+
f"`VDevice` - this is not allowed")
|
|
89
|
+
|
|
90
|
+
if len(possible_vdevices_of_interest) == 1:
|
|
91
|
+
# we have found one parent vDevice that has the same name as the cur_vdevice
|
|
92
|
+
return possible_vdevices_of_interest[0]
|
|
93
|
+
|
|
94
|
+
# we have no parent vDevice -> there are no parent ConnectionTree
|
|
95
|
+
return None
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Type, Union
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
from _balder.device import Device
|
|
6
|
+
from _balder.connection import Connection
|
|
7
|
+
from _balder.controllers import DeviceController
|
|
8
|
+
from _balder.cnnrelations import AndConnectionRelation, OrConnectionRelation
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def connect(
|
|
12
|
+
with_device: Union[Type[Device], str],
|
|
13
|
+
over_connection: Union[Connection, Type[Connection], AndConnectionRelation, OrConnectionRelation],
|
|
14
|
+
self_node_name: str = None,
|
|
15
|
+
dest_node_name: str = None
|
|
16
|
+
):
|
|
17
|
+
"""
|
|
18
|
+
This decorator connects two devices with each other. It can be used for scenarios as well as setup devices.
|
|
19
|
+
|
|
20
|
+
:param with_device: that's the :class:`Device` that should be connected to the decorated device
|
|
21
|
+
|
|
22
|
+
:param over_connection: that's the connection tree that should connect the devices with each other
|
|
23
|
+
|
|
24
|
+
:param self_node_name: the node name of this device (if this param is not given, balder automatically generates a
|
|
25
|
+
new node name in format `n{unique device counter}`)
|
|
26
|
+
|
|
27
|
+
:param dest_node_name: the node name of the destination device, given with `with_device` (if this param is not
|
|
28
|
+
given, balder automatically generates a new node name in format `n{unique device counter}`)
|
|
29
|
+
"""
|
|
30
|
+
allowed_regex_auto_node_names = r"n[0-9]+"
|
|
31
|
+
|
|
32
|
+
if not isinstance(with_device, str) and not issubclass(with_device, Device):
|
|
33
|
+
raise ValueError("the value of `with_device` must be a `Device` (or a subclass thereof) or the device name "
|
|
34
|
+
"as a string")
|
|
35
|
+
if isinstance(over_connection, type):
|
|
36
|
+
if not issubclass(over_connection, Connection):
|
|
37
|
+
raise TypeError("the type of `over_connection` must be a `Connection` (or a subclass of it)")
|
|
38
|
+
elif not isinstance(over_connection, Connection):
|
|
39
|
+
raise TypeError("the type of `over_connection` must be a `Connection` (or a subclass of it)")
|
|
40
|
+
|
|
41
|
+
if self_node_name is not None:
|
|
42
|
+
if not isinstance(self_node_name, str):
|
|
43
|
+
raise TypeError("the type of `node_name` must be a `str`")
|
|
44
|
+
if re.match(allowed_regex_auto_node_names, self_node_name):
|
|
45
|
+
raise ValueError(
|
|
46
|
+
f"the given `self_node_name` matches the regular expression `{allowed_regex_auto_node_names}` that is "
|
|
47
|
+
f"reserved for internal node naming and should not be used by you")
|
|
48
|
+
if dest_node_name is not None:
|
|
49
|
+
if not isinstance(dest_node_name, str):
|
|
50
|
+
raise TypeError("the type of `node_name` must be a `str`")
|
|
51
|
+
if re.match(allowed_regex_auto_node_names, dest_node_name):
|
|
52
|
+
raise ValueError(
|
|
53
|
+
f"the given `dest_node_name` matches the regular expression `{allowed_regex_auto_node_names}` that is "
|
|
54
|
+
f"reserved for internal node naming and should not be used by you")
|
|
55
|
+
|
|
56
|
+
class MyDecorator:
|
|
57
|
+
"""
|
|
58
|
+
This decorator will add the connection to the device that is decorated. If the `with_device` is no other device
|
|
59
|
+
class (given as string) this decorator can not handle the node allocation and the device resolving (because not
|
|
60
|
+
all devices are resolved yet). In this case the function adds the device-string in the :class:`Connection`
|
|
61
|
+
object and if the `dest_node_name` is None, it will also set this value to None. This secure that the
|
|
62
|
+
collector will create a unique auto node for it.
|
|
63
|
+
"""
|
|
64
|
+
def __new__(cls, *args, **kwargs): # pylint: disable=unused-argument
|
|
65
|
+
|
|
66
|
+
decorated_cls = args[0]
|
|
67
|
+
nonlocal with_device
|
|
68
|
+
nonlocal over_connection
|
|
69
|
+
nonlocal self_node_name
|
|
70
|
+
nonlocal dest_node_name
|
|
71
|
+
|
|
72
|
+
this_outer_ref = decorated_cls.__qualname__.split('.')[:-1]
|
|
73
|
+
if not isinstance(with_device, str):
|
|
74
|
+
other_outer_ref = with_device.__qualname__.split('.')[:-1]
|
|
75
|
+
if this_outer_ref != other_outer_ref:
|
|
76
|
+
raise ValueError(
|
|
77
|
+
f"the given device is not mentioned in this setup/scenario - please create a new "
|
|
78
|
+
f"direct inner device class, it can be inherited from `{with_device.__qualname__}`")
|
|
79
|
+
decorated_cls_device_controller = DeviceController.get_for(decorated_cls)
|
|
80
|
+
|
|
81
|
+
# if required give auto name to nodes
|
|
82
|
+
if self_node_name is None:
|
|
83
|
+
self_node_name = decorated_cls_device_controller.get_new_empty_auto_node()
|
|
84
|
+
if dest_node_name is None and not isinstance(with_device, str):
|
|
85
|
+
with_device_controller = DeviceController.get_for(with_device)
|
|
86
|
+
dest_node_name = with_device_controller.get_new_empty_auto_node()
|
|
87
|
+
|
|
88
|
+
cur_cnn_instance = None
|
|
89
|
+
if isinstance(over_connection, Connection):
|
|
90
|
+
# already instantiated because it comes back from a `based_on` - clone it to secure that it was not used
|
|
91
|
+
# in another `@connect()` decorator (and also remove possible metadata from the new clone)
|
|
92
|
+
cur_cnn_instance = over_connection.clone()
|
|
93
|
+
cur_cnn_instance.set_metadata_for_all_subitems(None)
|
|
94
|
+
elif isinstance(over_connection, type) and issubclass(over_connection, Connection):
|
|
95
|
+
# not instantiated -> instantiate it
|
|
96
|
+
cur_cnn_instance = over_connection()
|
|
97
|
+
elif isinstance(over_connection, (AndConnectionRelation, OrConnectionRelation)):
|
|
98
|
+
over_connection = Connection.based_on(over_connection)
|
|
99
|
+
cur_cnn_instance.metadata.set_from(from_device=decorated_cls, from_device_node_name=self_node_name)
|
|
100
|
+
cur_cnn_instance.metadata.set_to(to_device=with_device, to_device_node_name=dest_node_name)
|
|
101
|
+
|
|
102
|
+
decorated_cls_device_controller.add_new_raw_connection(cur_cnn_instance)
|
|
103
|
+
return decorated_cls
|
|
104
|
+
return MyDecorator
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Type, Union
|
|
3
|
+
|
|
4
|
+
import inspect
|
|
5
|
+
|
|
6
|
+
from _balder.controllers import ScenarioController
|
|
7
|
+
from _balder.scenario import Scenario
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def covered_by(item: Union[Type[Scenario], str, callable, None]):
|
|
11
|
+
"""
|
|
12
|
+
This decorator defines that there exists another Scenario class or test method item that has a similar
|
|
13
|
+
implementation like the decorated :class:`Scenario` class or the decorated test method.
|
|
14
|
+
|
|
15
|
+
:param item: the element which contains the whole implementation of this scenario or test method
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
if item is None:
|
|
19
|
+
pass
|
|
20
|
+
elif isinstance(item, str) and item.startswith("test_"):
|
|
21
|
+
pass
|
|
22
|
+
elif callable(item) and inspect.isfunction(item) and item.__name__.startswith("test_"):
|
|
23
|
+
pass
|
|
24
|
+
elif isinstance(item, type) and issubclass(item, Scenario):
|
|
25
|
+
raise NotImplementedError('The covered-by other scenario classes is not supported yet')
|
|
26
|
+
else:
|
|
27
|
+
raise TypeError("the given element for `item` must be a test method of a scenario class (has to start with "
|
|
28
|
+
"`test_`)")
|
|
29
|
+
|
|
30
|
+
class CoveredByDecorator:
|
|
31
|
+
"""decorator class for `@covered_by` decorator"""
|
|
32
|
+
def __init__(self, func):
|
|
33
|
+
self.func = func
|
|
34
|
+
|
|
35
|
+
if inspect.isclass(func):
|
|
36
|
+
# it must be a class decorator
|
|
37
|
+
if not issubclass(func, Scenario):
|
|
38
|
+
raise TypeError(f"The decorator `@covered_by` may only be used for `Scenario` objects or for test "
|
|
39
|
+
f"methods of one `Scenario` object. This is not possible for the applied class "
|
|
40
|
+
f"`{func.__name__}`.")
|
|
41
|
+
raise NotImplementedError('The covered-by decoration of other scenario classes is not supported yet')
|
|
42
|
+
# scenario_controller = ScenarioController.get_for(func)
|
|
43
|
+
# register for the whole class
|
|
44
|
+
# scenario_controller.register_covered_by_for(meth=None, covered_by=item)
|
|
45
|
+
if inspect.isfunction(func):
|
|
46
|
+
# work will done in `__set_name__`
|
|
47
|
+
pass
|
|
48
|
+
else:
|
|
49
|
+
raise TypeError(f"The use of the `@covered_by` decorator is not allowed for the `{str(func)}` element. "
|
|
50
|
+
f"You should only use this decorator for test method elements of a `Scenario` object")
|
|
51
|
+
|
|
52
|
+
def __set_name__(self, owner, name):
|
|
53
|
+
if issubclass(owner, Scenario):
|
|
54
|
+
if not inspect.isfunction(self.func):
|
|
55
|
+
raise TypeError("the use of the `@covered_by` decorator is only allowed for test methods of "
|
|
56
|
+
"`Scenario` objects")
|
|
57
|
+
if not name.startswith('test_'):
|
|
58
|
+
raise TypeError(f"the use of the `@covered_by` decorator is only allowed for test methods of "
|
|
59
|
+
f"`Scenario` objects - the method `{owner.__name__}.{name}` does not start with "
|
|
60
|
+
f"`test_` and is not a valid test method")
|
|
61
|
+
# if item is a string - resolve method
|
|
62
|
+
cleared_item = item
|
|
63
|
+
if isinstance(item, str):
|
|
64
|
+
cleared_item = getattr(owner, item)
|
|
65
|
+
scenario_controller = ScenarioController.get_for(owner)
|
|
66
|
+
scenario_controller.register_covered_by_for(meth=name, covered_by=cleared_item)
|
|
67
|
+
else:
|
|
68
|
+
raise TypeError(f"The use of the `@covered_by` decorator is not allowed for methods of a "
|
|
69
|
+
f"`{owner.__name__}`. You should only use this decorator for valid test methods of a "
|
|
70
|
+
f"`Scenario` object")
|
|
71
|
+
|
|
72
|
+
setattr(owner, name, self.func)
|
|
73
|
+
|
|
74
|
+
return CoveredByDecorator
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
import functools
|
|
5
|
+
from _balder.collector import Collector
|
|
6
|
+
from _balder.fixture_execution_level import FixtureExecutionLevel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def fixture(level: Literal['session', 'setup', 'scenario', 'variation', 'testcase']):
|
|
10
|
+
"""
|
|
11
|
+
This decorator declares the decorated function/method as a fixture function/method.
|
|
12
|
+
|
|
13
|
+
:param level: the execution level the fixture should have
|
|
14
|
+
"""
|
|
15
|
+
allowed_levels = [level.value for level in FixtureExecutionLevel]
|
|
16
|
+
|
|
17
|
+
if level not in allowed_levels:
|
|
18
|
+
raise ValueError(f"the value of `level` must be a `str` with one of the values `{'`, `'.join(allowed_levels)}`")
|
|
19
|
+
|
|
20
|
+
def decorator_fixture(func):
|
|
21
|
+
# always register the raw fixture in Collector - class determination will be done later by :meth:`Collector`
|
|
22
|
+
Collector.register_raw_fixture(func, level)
|
|
23
|
+
|
|
24
|
+
@functools.wraps(func)
|
|
25
|
+
def wrapper_fixture(*args, **kwargs):
|
|
26
|
+
func(*args, **kwargs)
|
|
27
|
+
|
|
28
|
+
return wrapper_fixture
|
|
29
|
+
return decorator_fixture
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Union, Type
|
|
3
|
+
|
|
4
|
+
import inspect
|
|
5
|
+
from _balder.cnnrelations import AndConnectionRelation, OrConnectionRelation
|
|
6
|
+
from _balder.collector import Collector
|
|
7
|
+
from _balder.feature import Feature
|
|
8
|
+
from _balder.vdevice import VDevice
|
|
9
|
+
from _balder.connection import Connection
|
|
10
|
+
from _balder.controllers import FeatureController
|
|
11
|
+
from _balder.exceptions import DuplicateForVDeviceError, UnknownVDeviceException
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def for_vdevice(
|
|
15
|
+
vdevice: Union[str, Type[VDevice]],
|
|
16
|
+
with_connections: Union[
|
|
17
|
+
Type[Connection], Connection, AndConnectionRelation, OrConnectionRelation
|
|
18
|
+
] = Connection(),
|
|
19
|
+
):
|
|
20
|
+
"""
|
|
21
|
+
With the `@for_vdevice` you can limit the decorated object for a special allowed connection tree for every existing
|
|
22
|
+
vDevice. This decorator can be used to decorate whole :class:`Feature` classes just like single methods of a
|
|
23
|
+
:class:`Feature` class.
|
|
24
|
+
|
|
25
|
+
Decorated Feature classes: This controls the allowed sub-connection tree between the mapped device of the given
|
|
26
|
+
vDevice and the device class that uses the decorated feature. If the defined sub-tree
|
|
27
|
+
does not match the sub-tree that connects the both devices with each other on the setup
|
|
28
|
+
level, the feature can not be applied.
|
|
29
|
+
|
|
30
|
+
Decorated Feature method: Similar to the class based decoration, you can specify if a method is executable with
|
|
31
|
+
the given sub-tree. Especially, at this point you are able to define your own method
|
|
32
|
+
variations. Balder will select the chosen one depending on the matching connection
|
|
33
|
+
sub-tree.
|
|
34
|
+
|
|
35
|
+
You can find more about this in the documentation chapter :ref:`VDevices and method-variations`.
|
|
36
|
+
|
|
37
|
+
:param vdevice: the vDevice this decorator should describe
|
|
38
|
+
|
|
39
|
+
:param with_connections: the assigned connection trees for this class/method (default: a universal connection)
|
|
40
|
+
"""
|
|
41
|
+
if isinstance(with_connections, Connection):
|
|
42
|
+
# do nothing
|
|
43
|
+
pass
|
|
44
|
+
elif isinstance(with_connections, (AndConnectionRelation, OrConnectionRelation)):
|
|
45
|
+
# use container connection
|
|
46
|
+
with_connections = Connection.based_on(with_connections)
|
|
47
|
+
elif isinstance(with_connections, type) and issubclass(with_connections, Connection):
|
|
48
|
+
# instantiate it
|
|
49
|
+
with_connections = with_connections()
|
|
50
|
+
else:
|
|
51
|
+
raise TypeError(f"the given element ``with_connection`` needs to be from type `AndConnectionRelation`, "
|
|
52
|
+
f"`OrConnectionRelation` or `Connection` - `{type(with_connections)}` is not allowed")
|
|
53
|
+
|
|
54
|
+
# note: if `args` is an empty list - no special sub-connection-tree bindings
|
|
55
|
+
|
|
56
|
+
if not (isinstance(vdevice, str) or (isinstance(vdevice, type) and issubclass(vdevice, VDevice))):
|
|
57
|
+
raise ValueError('the given element for `vdevice` has to be a `str` or has to be a subclass of'
|
|
58
|
+
'`VDevice`')
|
|
59
|
+
|
|
60
|
+
class ForVDeviceDecorator:
|
|
61
|
+
"""decorator class for `@for_vdevice` decorator"""
|
|
62
|
+
|
|
63
|
+
def __init__(self, func):
|
|
64
|
+
nonlocal vdevice
|
|
65
|
+
nonlocal with_connections
|
|
66
|
+
|
|
67
|
+
self.func = func
|
|
68
|
+
|
|
69
|
+
# we detect a decorated non-class object -> save it and check it later in collector
|
|
70
|
+
Collector.register_possible_method_variation(func, vdevice, with_connections)
|
|
71
|
+
|
|
72
|
+
def __new__(cls, *args, **kwargs): # pylint: disable=unused-argument
|
|
73
|
+
nonlocal vdevice
|
|
74
|
+
|
|
75
|
+
func = args[0]
|
|
76
|
+
|
|
77
|
+
if inspect.isclass(func):
|
|
78
|
+
# it must be a class decorator
|
|
79
|
+
if not issubclass(func, Feature):
|
|
80
|
+
raise TypeError(f"The decorator `@for_vdevice` may only be used for `Feature` objects. This is "
|
|
81
|
+
f"not possible for the applied class `{func.__name__}`.")
|
|
82
|
+
|
|
83
|
+
fn_feature_controller = FeatureController.get_for(func)
|
|
84
|
+
|
|
85
|
+
if isinstance(vdevice, str):
|
|
86
|
+
# vDevice is a string, so we have to convert it to the correct class
|
|
87
|
+
relevant_vdevices = [cur_vdevice for cur_vdevice
|
|
88
|
+
in fn_feature_controller.get_abs_inner_vdevice_classes()
|
|
89
|
+
if cur_vdevice.__name__ == vdevice]
|
|
90
|
+
|
|
91
|
+
if len(relevant_vdevices) == 0:
|
|
92
|
+
raise ValueError(
|
|
93
|
+
f"can not find a matching inner VDevice class for the given vDevice string `{vdevice}` in "
|
|
94
|
+
f"the feature class `{func.__name__}`")
|
|
95
|
+
|
|
96
|
+
if len(relevant_vdevices) > 1:
|
|
97
|
+
raise RuntimeError("found more than one possible vDevices - something unexpected happened")
|
|
98
|
+
|
|
99
|
+
vdevice = relevant_vdevices[0]
|
|
100
|
+
cls_for_vdevice = fn_feature_controller.get_class_based_for_vdevice()
|
|
101
|
+
if vdevice in cls_for_vdevice.keys():
|
|
102
|
+
raise DuplicateForVDeviceError(
|
|
103
|
+
f'there already exists a decorator for the vDevice `{vdevice}` in the Feature class '
|
|
104
|
+
f'`{func.__name__}`')
|
|
105
|
+
if vdevice not in fn_feature_controller.get_abs_inner_vdevice_classes():
|
|
106
|
+
raise UnknownVDeviceException(
|
|
107
|
+
f"the given vDevice `{vdevice}` is no usable vDevice in Feature class `{func.__name__}`")
|
|
108
|
+
|
|
109
|
+
cls_for_vdevice[vdevice] = with_connections
|
|
110
|
+
fn_feature_controller.set_class_based_for_vdevice(cls_for_vdevice)
|
|
111
|
+
# directly return the class -> we do not want to manipulate it
|
|
112
|
+
return func
|
|
113
|
+
|
|
114
|
+
# otherwise, work will be done in `__init__`
|
|
115
|
+
# return this decorator object to work with
|
|
116
|
+
return super().__new__(ForVDeviceDecorator)
|
|
117
|
+
|
|
118
|
+
return ForVDeviceDecorator
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from _balder.controllers.device_controller import DeviceController
|
|
4
|
+
from _balder.device import Device
|
|
5
|
+
from _balder.node_gateway import NodeGateway
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def gateway(from_node: str, to_node: str, bidirectional: bool = True):
|
|
9
|
+
"""
|
|
10
|
+
This decorator enables two nodes of a device to be connected to one another via a gateway. The gateway can
|
|
11
|
+
implement an unidirectional or bidirectional connection.
|
|
12
|
+
"""
|
|
13
|
+
if not isinstance(from_node, str) and not isinstance(to_node, str):
|
|
14
|
+
raise ValueError("the value of `from_node` and `to_node` must be the name of the nodes (type `str`)")
|
|
15
|
+
|
|
16
|
+
if isinstance(bidirectional, bool):
|
|
17
|
+
raise ValueError("the value of `bidirectional` must be a `bool`")
|
|
18
|
+
|
|
19
|
+
def decorator(cls):
|
|
20
|
+
nonlocal from_node
|
|
21
|
+
nonlocal to_node
|
|
22
|
+
nonlocal bidirectional
|
|
23
|
+
|
|
24
|
+
# it must be a class decorator
|
|
25
|
+
if not issubclass(cls, Device):
|
|
26
|
+
raise TypeError(
|
|
27
|
+
f"The decorator `gateway` may only be used for `Device` objects. This is not possible for the applied "
|
|
28
|
+
f"class `{cls.__name__}`.")
|
|
29
|
+
decorated_cls_device_controller = DeviceController.get_for(cls)
|
|
30
|
+
|
|
31
|
+
new_gateway = NodeGateway(cls, from_node, to_node, bidirectional)
|
|
32
|
+
decorated_cls_device_controller.add_new_raw_gateway(new_gateway)
|
|
33
|
+
return cls
|
|
34
|
+
return decorator
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Type, Union, List, Tuple
|
|
3
|
+
|
|
4
|
+
from _balder.connection import Connection
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def insert_into_tree(parents: List[Union[Type[Connection], Tuple[Type[Connection]]]] = None, tree_name: str = ""):
|
|
8
|
+
"""
|
|
9
|
+
This decorator inserts a :meth:`Connection` object into the global connection tree.
|
|
10
|
+
|
|
11
|
+
:param parents: all items that are parents of the decorated connection
|
|
12
|
+
|
|
13
|
+
:param tree_name: the tree name the connection should be inserted in
|
|
14
|
+
"""
|
|
15
|
+
if parents is None:
|
|
16
|
+
parents = []
|
|
17
|
+
if not isinstance(parents, list):
|
|
18
|
+
raise ValueError("the value of `parents` must be a `list`")
|
|
19
|
+
|
|
20
|
+
idx = 0
|
|
21
|
+
for cur_parent in parents:
|
|
22
|
+
if isinstance(cur_parent, tuple):
|
|
23
|
+
tuple_idx = 0
|
|
24
|
+
for cur_tuple_element in cur_parent:
|
|
25
|
+
if not isinstance(cur_tuple_element, type) or not issubclass(cur_tuple_element, Connection):
|
|
26
|
+
raise TypeError(
|
|
27
|
+
f"the tuple element on index {tuple_idx} (located on index {idx} of `parents`) must be a "
|
|
28
|
+
f"`Connection` type (given element is: {str(cur_parent)})")
|
|
29
|
+
tuple_idx += 1
|
|
30
|
+
else:
|
|
31
|
+
if not isinstance(cur_parent, type) or not issubclass(cur_parent, Connection):
|
|
32
|
+
raise TypeError(
|
|
33
|
+
f"the element on index {idx} in `parents` must be a `Connection` type (given: {str(cur_parent)})")
|
|
34
|
+
idx += 1
|
|
35
|
+
|
|
36
|
+
class MyDecorator:
|
|
37
|
+
"""decorator class for `@insert_into_tree` decorator"""
|
|
38
|
+
def __new__(cls, *args, **kwargs): # pylint: disable=unused-argument
|
|
39
|
+
|
|
40
|
+
nonlocal parents
|
|
41
|
+
|
|
42
|
+
decorated_cls = args[0]
|
|
43
|
+
|
|
44
|
+
conn_parents = decorated_cls.get_parents(tree_name=tree_name)
|
|
45
|
+
conn_parents = [] if conn_parents is None else conn_parents
|
|
46
|
+
|
|
47
|
+
conn_parents += [new_parent for new_parent in parents if new_parent not in conn_parents]
|
|
48
|
+
decorated_cls.set_parents(conn_parents, tree_name=tree_name)
|
|
49
|
+
|
|
50
|
+
return decorated_cls
|
|
51
|
+
|
|
52
|
+
return MyDecorator
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Iterable, Any
|
|
3
|
+
|
|
4
|
+
import inspect
|
|
5
|
+
from _balder.collector import Collector
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def parametrize(
|
|
9
|
+
field_name: str,
|
|
10
|
+
values: Iterable[Any],
|
|
11
|
+
):
|
|
12
|
+
"""
|
|
13
|
+
Allows to parametrize a test function. This decorator will be used to statically parametrize a test function.
|
|
14
|
+
|
|
15
|
+
:param field_name: the field name of the test function
|
|
16
|
+
|
|
17
|
+
:param values: an iterable of values, that should be used to parametrize the test function
|
|
18
|
+
"""
|
|
19
|
+
if not isinstance(field_name, str):
|
|
20
|
+
raise ValueError('the given field name must be a string')
|
|
21
|
+
|
|
22
|
+
def decorator(func):
|
|
23
|
+
nonlocal field_name
|
|
24
|
+
nonlocal values
|
|
25
|
+
|
|
26
|
+
if not inspect.isfunction(func):
|
|
27
|
+
raise TypeError('the decorated object needs to be a test method')
|
|
28
|
+
|
|
29
|
+
Collector.register_possible_parametrization(func, field_name, values)
|
|
30
|
+
return func
|
|
31
|
+
return decorator
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Dict, Tuple, Type
|
|
3
|
+
|
|
4
|
+
import inspect
|
|
5
|
+
from _balder.collector import Collector
|
|
6
|
+
from _balder.device import Device
|
|
7
|
+
from _balder.parametrization import FeatureAccessSelector, Parameter, Value
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def parametrize_by_feature(
|
|
11
|
+
field_name: str,
|
|
12
|
+
feature_accessor: Tuple[Type[Device], str, str],
|
|
13
|
+
parameter: Dict[str, FeatureAccessSelector | Parameter | Value] = None
|
|
14
|
+
):
|
|
15
|
+
"""
|
|
16
|
+
Allows to parametrize a test function. This decorator will be used to dynamically parametrize a test function, by
|
|
17
|
+
the value a setup feature returns before entering the test.
|
|
18
|
+
|
|
19
|
+
:param field_name: the field name of the test function
|
|
20
|
+
:param feature_accessor: a tuple that provides information for accessing the feature
|
|
21
|
+
:param parameter: the parameter to parametrize the feature method (if necessary)
|
|
22
|
+
"""
|
|
23
|
+
if not isinstance(field_name, str):
|
|
24
|
+
raise ValueError('the given field name must be a string')
|
|
25
|
+
if parameter is None:
|
|
26
|
+
parameter = {}
|
|
27
|
+
|
|
28
|
+
feature_accessor = FeatureAccessSelector(*feature_accessor, parameters=parameter)
|
|
29
|
+
|
|
30
|
+
def decorator(func):
|
|
31
|
+
if not inspect.isfunction(func):
|
|
32
|
+
raise TypeError('the decorated object needs to be a test method')
|
|
33
|
+
|
|
34
|
+
Collector.register_possible_parametrization(func, field_name, feature_accessor)
|
|
35
|
+
return func
|
|
36
|
+
return decorator
|
_balder/device.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Device:
|
|
5
|
+
"""
|
|
6
|
+
This is the basic device class. It represents an abstract class that should not be used directly. Every inner device
|
|
7
|
+
has to inherit from this class.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# ---------------------------------- STATIC METHODS ----------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
# ---------------------------------- CLASS METHODS ----------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
# ---------------------------------- PROPERTIES --------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
# ---------------------------------- PROTECTED METHODS -------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
# ---------------------------------- METHODS -----------------------------------------------------------------------
|