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.
Files changed (89) hide show
  1. _balder/__init__.py +12 -0
  2. _balder/_version.py +34 -0
  3. _balder/balder_plugin.py +73 -0
  4. _balder/balder_session.py +341 -0
  5. _balder/balder_settings.py +15 -0
  6. _balder/cnnrelations/__init__.py +7 -0
  7. _balder/cnnrelations/and_connection_relation.py +176 -0
  8. _balder/cnnrelations/base_connection_relation.py +270 -0
  9. _balder/cnnrelations/or_connection_relation.py +65 -0
  10. _balder/collector.py +874 -0
  11. _balder/connection.py +863 -0
  12. _balder/connection_metadata.py +255 -0
  13. _balder/console/__init__.py +0 -0
  14. _balder/console/balder.py +58 -0
  15. _balder/controllers/__init__.py +12 -0
  16. _balder/controllers/base_device_controller.py +72 -0
  17. _balder/controllers/controller.py +29 -0
  18. _balder/controllers/device_controller.py +446 -0
  19. _balder/controllers/feature_controller.py +715 -0
  20. _balder/controllers/normal_scenario_setup_controller.py +402 -0
  21. _balder/controllers/scenario_controller.py +524 -0
  22. _balder/controllers/setup_controller.py +134 -0
  23. _balder/controllers/vdevice_controller.py +95 -0
  24. _balder/decorator_connect.py +104 -0
  25. _balder/decorator_covered_by.py +74 -0
  26. _balder/decorator_fixture.py +29 -0
  27. _balder/decorator_for_vdevice.py +118 -0
  28. _balder/decorator_gateway.py +34 -0
  29. _balder/decorator_insert_into_tree.py +52 -0
  30. _balder/decorator_parametrize.py +31 -0
  31. _balder/decorator_parametrize_by_feature.py +36 -0
  32. _balder/device.py +18 -0
  33. _balder/exceptions.py +182 -0
  34. _balder/executor/__init__.py +0 -0
  35. _balder/executor/basic_executable_executor.py +133 -0
  36. _balder/executor/basic_executor.py +205 -0
  37. _balder/executor/executor_tree.py +217 -0
  38. _balder/executor/parametrized_testcase_executor.py +52 -0
  39. _balder/executor/scenario_executor.py +169 -0
  40. _balder/executor/setup_executor.py +163 -0
  41. _balder/executor/testcase_executor.py +203 -0
  42. _balder/executor/unresolved_parametrized_testcase_executor.py +184 -0
  43. _balder/executor/variation_executor.py +882 -0
  44. _balder/exit_code.py +19 -0
  45. _balder/feature.py +74 -0
  46. _balder/feature_replacement_mapping.py +107 -0
  47. _balder/feature_vdevice_mapping.py +88 -0
  48. _balder/fixture_definition_scope.py +19 -0
  49. _balder/fixture_execution_level.py +22 -0
  50. _balder/fixture_manager.py +483 -0
  51. _balder/fixture_metadata.py +26 -0
  52. _balder/node_gateway.py +103 -0
  53. _balder/objects/__init__.py +0 -0
  54. _balder/objects/connections/__init__.py +0 -0
  55. _balder/objects/connections/osi_1_physical.py +116 -0
  56. _balder/objects/connections/osi_2_datalink.py +35 -0
  57. _balder/objects/connections/osi_3_network.py +47 -0
  58. _balder/objects/connections/osi_4_transport.py +40 -0
  59. _balder/objects/connections/osi_5_session.py +13 -0
  60. _balder/objects/connections/osi_6_presentation.py +13 -0
  61. _balder/objects/connections/osi_7_application.py +83 -0
  62. _balder/objects/devices/__init__.py +0 -0
  63. _balder/objects/devices/this_device.py +12 -0
  64. _balder/parametrization.py +75 -0
  65. _balder/plugin_manager.py +138 -0
  66. _balder/previous_executor_mark.py +23 -0
  67. _balder/routing_path.py +335 -0
  68. _balder/scenario.py +20 -0
  69. _balder/setup.py +18 -0
  70. _balder/solver.py +246 -0
  71. _balder/testresult.py +163 -0
  72. _balder/unmapped_vdevice.py +13 -0
  73. _balder/utils/__init__.py +0 -0
  74. _balder/utils/functions.py +103 -0
  75. _balder/utils/inner_device_managing_metaclass.py +14 -0
  76. _balder/utils/mixin_can_be_covered_by_executor.py +24 -0
  77. _balder/utils/typings.py +4 -0
  78. _balder/vdevice.py +9 -0
  79. balder/__init__.py +56 -0
  80. balder/connections.py +43 -0
  81. balder/devices.py +9 -0
  82. balder/exceptions.py +44 -0
  83. balder/parametrization.py +8 -0
  84. baldertest-0.1.0.dist-info/METADATA +356 -0
  85. baldertest-0.1.0.dist-info/RECORD +89 -0
  86. baldertest-0.1.0.dist-info/WHEEL +5 -0
  87. baldertest-0.1.0.dist-info/entry_points.txt +2 -0
  88. baldertest-0.1.0.dist-info/licenses/LICENSE +21 -0
  89. 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 -----------------------------------------------------------------------