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,446 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
from typing import Dict, List, Type, Union, TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import inspect
|
|
8
|
+
from abc import ABC
|
|
9
|
+
from _balder.setup import Setup
|
|
10
|
+
from _balder.device import Device
|
|
11
|
+
from _balder.vdevice import VDevice
|
|
12
|
+
from _balder.scenario import Scenario
|
|
13
|
+
from _balder.controllers.base_device_controller import BaseDeviceController
|
|
14
|
+
from _balder.controllers.feature_controller import FeatureController
|
|
15
|
+
from _balder.exceptions import DeviceResolvingException, InnerFeatureResolvingError, \
|
|
16
|
+
FeatureOverwritingError, MultiInheritanceError
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from _balder.connection import Connection
|
|
19
|
+
from _balder.controllers import ScenarioController, SetupController
|
|
20
|
+
from _balder.node_gateway import NodeGateway
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__file__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class DeviceController(BaseDeviceController, ABC):
|
|
26
|
+
"""
|
|
27
|
+
This is the main device controller to manage :class:`Device` classes.
|
|
28
|
+
"""
|
|
29
|
+
# helper property to disable manual constructor creation
|
|
30
|
+
__priv_instantiate_key = object()
|
|
31
|
+
|
|
32
|
+
#: contains all existing setup devices and its corresponding controller object
|
|
33
|
+
_items: Dict[Type[Device], DeviceController] = {}
|
|
34
|
+
|
|
35
|
+
def __init__(self, related_cls, _priv_instantiate_key):
|
|
36
|
+
super().__init__()
|
|
37
|
+
|
|
38
|
+
# this helps to make this constructor only possible inside the controller object
|
|
39
|
+
if _priv_instantiate_key != DeviceController.__priv_instantiate_key:
|
|
40
|
+
raise RuntimeError('it is not allowed to instantiate a controller manually -> use the static method '
|
|
41
|
+
'`DeviceController.get_for()` for it')
|
|
42
|
+
|
|
43
|
+
if not isinstance(related_cls, type):
|
|
44
|
+
raise TypeError('the attribute `related_cls` has to be a type (no object)')
|
|
45
|
+
if not issubclass(related_cls, Device):
|
|
46
|
+
raise TypeError(f'the attribute `related_cls` has to be a sub-type of `{Device.__name__}` but not of '
|
|
47
|
+
f'`{VDevice.__name__}`')
|
|
48
|
+
if issubclass(related_cls, VDevice):
|
|
49
|
+
raise TypeError(f'the attribute `related_cls` has to be a sub-type of `{Device.__name__}` but not of '
|
|
50
|
+
f'`{VDevice.__name__}`')
|
|
51
|
+
if related_cls == Device:
|
|
52
|
+
raise TypeError(f'the attribute `related_cls` is `{Device.__name__}` - controllers for native type are '
|
|
53
|
+
f'forbidden')
|
|
54
|
+
# contains a reference to the related class this controller instance belongs to
|
|
55
|
+
self._related_cls = related_cls
|
|
56
|
+
|
|
57
|
+
# internal counter to auto provide unique node names
|
|
58
|
+
self._node_cnt = 0
|
|
59
|
+
|
|
60
|
+
#: contains all raw existing connection decorators for the related device
|
|
61
|
+
self._connections: Dict[str, List[Connection]] = {}
|
|
62
|
+
|
|
63
|
+
#: describes the absolute connections from the related device to another device
|
|
64
|
+
self._absolute_connections: Dict[Type[Device], List[Connection]] = {}
|
|
65
|
+
|
|
66
|
+
self._gateways: List[NodeGateway] = []
|
|
67
|
+
|
|
68
|
+
# ---------------------------------- STATIC METHODS ----------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
@staticmethod
|
|
71
|
+
def get_for(related_cls: Type[Device]) -> DeviceController:
|
|
72
|
+
"""
|
|
73
|
+
This class returns the current existing controller instance for the given item. If the instance does not exist
|
|
74
|
+
yet, it will automatically create it and saves the instance in an internal dictionary.
|
|
75
|
+
"""
|
|
76
|
+
if DeviceController._items.get(related_cls) is None:
|
|
77
|
+
item = DeviceController(
|
|
78
|
+
related_cls, _priv_instantiate_key=DeviceController.__priv_instantiate_key)
|
|
79
|
+
DeviceController._items[related_cls] = item
|
|
80
|
+
|
|
81
|
+
return DeviceController._items.get(related_cls)
|
|
82
|
+
|
|
83
|
+
# ---------------------------------- CLASS METHODS -----------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
# ---------------------------------- PROPERTIES --------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def related_cls(self) -> Type[Device]:
|
|
89
|
+
"""the related device type"""
|
|
90
|
+
return self._related_cls
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def connections(self) -> Dict[str, List[Connection]]:
|
|
94
|
+
"""
|
|
95
|
+
returns the defined connections for the related devices (sorted after node name) - NOT SYNCHRONIZED -
|
|
96
|
+
direct decorator values
|
|
97
|
+
"""
|
|
98
|
+
return self._connections
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def absolute_connections(self) -> Dict[Type[Device], List[Connection]]:
|
|
102
|
+
"""
|
|
103
|
+
returns the absolute and SYNCHRONIZED connections between the related device and all other devices
|
|
104
|
+
"""
|
|
105
|
+
return self._absolute_connections
|
|
106
|
+
|
|
107
|
+
# ---------------------------------- PROTECTED METHODS -------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
def __get_outer_class_controller(self) -> Union[ScenarioController, SetupController]:
|
|
110
|
+
# pylint: disable-next=import-outside-toplevel
|
|
111
|
+
from _balder.controllers.normal_scenario_setup_controller import NormalScenarioSetupController
|
|
112
|
+
|
|
113
|
+
outer_class = self.get_outer_class()
|
|
114
|
+
return NormalScenarioSetupController.get_for(outer_class)
|
|
115
|
+
|
|
116
|
+
# ---------------------------------- METHODS -----------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
def get_next_parent_class(self) -> Union[Type[Device], None]:
|
|
119
|
+
"""
|
|
120
|
+
This method returns the next parent class which is a subclass of the :class:`Device` itself.
|
|
121
|
+
|
|
122
|
+
:return: returns the next parent class or None if the next parent class is :class:`Device`
|
|
123
|
+
itself
|
|
124
|
+
"""
|
|
125
|
+
next_base_class = None
|
|
126
|
+
for cur_base in self.related_cls.__bases__:
|
|
127
|
+
if issubclass(cur_base, Device):
|
|
128
|
+
if next_base_class is not None:
|
|
129
|
+
raise MultiInheritanceError(
|
|
130
|
+
f"found more than one Device parent classes for `{self.related_cls.__name__}` "
|
|
131
|
+
f"- multi inheritance is not allowed for Device classes")
|
|
132
|
+
next_base_class = cur_base
|
|
133
|
+
if next_base_class == Device:
|
|
134
|
+
return None
|
|
135
|
+
return next_base_class
|
|
136
|
+
|
|
137
|
+
def add_new_raw_connection(self, connection: Connection):
|
|
138
|
+
"""
|
|
139
|
+
This method adds a new raw connection to the internal property `connections`.
|
|
140
|
+
|
|
141
|
+
:param connection: the connection object (the related device has to be part of it)
|
|
142
|
+
"""
|
|
143
|
+
if connection.from_device == self.related_cls:
|
|
144
|
+
own_node = connection.from_node_name
|
|
145
|
+
elif connection.to_device == self.related_cls:
|
|
146
|
+
own_node = connection.to_node_name
|
|
147
|
+
else:
|
|
148
|
+
raise ValueError("the given connection does not have the current device as component")
|
|
149
|
+
if own_node not in self._connections.keys():
|
|
150
|
+
self._connections[own_node] = []
|
|
151
|
+
|
|
152
|
+
self._connections[own_node].append(connection)
|
|
153
|
+
|
|
154
|
+
def add_new_raw_gateway(self, gateway: NodeGateway):
|
|
155
|
+
"""
|
|
156
|
+
This method adds a new raw gateway to the internal property `_gateways`.
|
|
157
|
+
|
|
158
|
+
:param gateway: the gateway object (the related device has to be part of it)
|
|
159
|
+
"""
|
|
160
|
+
if gateway.device != self.related_cls:
|
|
161
|
+
raise ValueError("the given gateway does not have the current device as component")
|
|
162
|
+
|
|
163
|
+
self._gateways.append(gateway)
|
|
164
|
+
|
|
165
|
+
def add_new_absolute_connection(self, connection: Connection):
|
|
166
|
+
"""
|
|
167
|
+
This method adds a new absolute connection to the internal property `absolute_connections`.
|
|
168
|
+
|
|
169
|
+
.. note::
|
|
170
|
+
This method doesn't secure any synchronizing, it only sets the connection internally.
|
|
171
|
+
|
|
172
|
+
.. note::
|
|
173
|
+
It only adds a connection, if it does not already exist in the internal list - duplicates will not be added.
|
|
174
|
+
|
|
175
|
+
:param connection: the connection object (the related device has to be part of it)
|
|
176
|
+
"""
|
|
177
|
+
if self.related_cls == connection.from_device:
|
|
178
|
+
other_device = connection.to_device
|
|
179
|
+
elif self.related_cls == connection.to_device:
|
|
180
|
+
other_device = connection.from_device
|
|
181
|
+
else:
|
|
182
|
+
raise ValueError("the given connection does not have the current device as component")
|
|
183
|
+
if other_device not in self._absolute_connections.keys():
|
|
184
|
+
self._absolute_connections[other_device] = []
|
|
185
|
+
if connection not in self._absolute_connections[other_device]:
|
|
186
|
+
self._absolute_connections[other_device].append(connection)
|
|
187
|
+
|
|
188
|
+
def cleanup_absolute_connections_with(self, other_device):
|
|
189
|
+
"""
|
|
190
|
+
This method removes all connections from the related device to the given ``other_device``.
|
|
191
|
+
"""
|
|
192
|
+
if other_device in self._absolute_connections.keys():
|
|
193
|
+
del self._absolute_connections[other_device]
|
|
194
|
+
|
|
195
|
+
def get_all_connections(self) -> Dict[str, List[Connection]]:
|
|
196
|
+
"""
|
|
197
|
+
This method returns all available connection objects for the related device, sorted accordingly to their node
|
|
198
|
+
name. The method gets all possible connection objects from the outer class (the setup or the scenario) and sorts
|
|
199
|
+
the relevant ones.
|
|
200
|
+
|
|
201
|
+
The method detects connections from other devices of the outer class that starts or ends at the related device,
|
|
202
|
+
too.
|
|
203
|
+
|
|
204
|
+
:returns: returns a mapping between the node name and a list of :class:`Connection` objects that belongs to the
|
|
205
|
+
node
|
|
206
|
+
"""
|
|
207
|
+
outer_class_controller = self.__get_outer_class_controller()
|
|
208
|
+
|
|
209
|
+
all_outer_class_conns = outer_class_controller.get_all_connections()
|
|
210
|
+
conns_as_from_device = [
|
|
211
|
+
cur_conn for cur_conn in all_outer_class_conns if cur_conn.from_device == self.related_cls]
|
|
212
|
+
conns_as_to_device = [
|
|
213
|
+
cur_conn for cur_conn in all_outer_class_conns if cur_conn.to_device == self.related_cls]
|
|
214
|
+
result_dict = {}
|
|
215
|
+
for cur_conn in conns_as_from_device:
|
|
216
|
+
if cur_conn.from_node_name not in result_dict.keys():
|
|
217
|
+
result_dict[cur_conn.from_node_name] = []
|
|
218
|
+
result_dict[cur_conn.from_node_name].append(cur_conn)
|
|
219
|
+
|
|
220
|
+
for cur_conn in conns_as_to_device:
|
|
221
|
+
if cur_conn.to_node_name not in result_dict.keys():
|
|
222
|
+
result_dict[cur_conn.to_node_name] = []
|
|
223
|
+
result_dict[cur_conn.to_node_name].append(cur_conn)
|
|
224
|
+
return result_dict
|
|
225
|
+
|
|
226
|
+
def get_all_absolute_connections(self) -> Dict[str, List[Connection]]:
|
|
227
|
+
"""
|
|
228
|
+
This method returns all available absolute connection objects for the related device, sorted accordingly to
|
|
229
|
+
their node name. Absolute connection are the cleaned connections, that are reduced to work with all used
|
|
230
|
+
:class:`Feature` and their :class:`VDevice` classes.
|
|
231
|
+
|
|
232
|
+
The method also gets all possible connection objects from the outer class and sorts the relevant ones. The
|
|
233
|
+
method detects connections from other devices of the outer class that starts or ends here, too.
|
|
234
|
+
|
|
235
|
+
:returns: returns a mapping between the node name and a list of :class:`.Connection` objects that belongs to
|
|
236
|
+
the node
|
|
237
|
+
"""
|
|
238
|
+
# we do not need to look in communication partner device here, because `_absolute_connections` is always be
|
|
239
|
+
# synchronized
|
|
240
|
+
outer_class_controller = self.__get_outer_class_controller()
|
|
241
|
+
all_outer_class_devices = outer_class_controller.get_all_abs_inner_device_classes()
|
|
242
|
+
|
|
243
|
+
result_dict = {}
|
|
244
|
+
for cur_outer_class_device in all_outer_class_devices:
|
|
245
|
+
cur_outer_class_device_controller = DeviceController.get_for(cur_outer_class_device)
|
|
246
|
+
|
|
247
|
+
for _, cur_cnn_list in cur_outer_class_device_controller.absolute_connections.items():
|
|
248
|
+
for cur_cnn in cur_cnn_list:
|
|
249
|
+
if cur_cnn.from_device == self.related_cls:
|
|
250
|
+
if cur_cnn.from_node_name not in result_dict.keys():
|
|
251
|
+
result_dict[cur_cnn.from_node_name] = []
|
|
252
|
+
if cur_cnn not in result_dict[cur_cnn.from_node_name]:
|
|
253
|
+
result_dict[cur_cnn.from_node_name].append(cur_cnn)
|
|
254
|
+
elif cur_cnn.to_device == self.related_cls:
|
|
255
|
+
if cur_cnn.to_node_name not in result_dict.keys():
|
|
256
|
+
result_dict[cur_cnn.to_node_name] = []
|
|
257
|
+
if cur_cnn not in result_dict[cur_cnn.to_node_name]:
|
|
258
|
+
result_dict[cur_cnn.to_node_name].append(cur_cnn)
|
|
259
|
+
return result_dict
|
|
260
|
+
|
|
261
|
+
# def _get_all_gateways(self) -> List[NodeGateway]:
|
|
262
|
+
# """provides a list with all gateway objects that are defined for this device"""
|
|
263
|
+
# self.__validate_gateway_node_names()
|
|
264
|
+
# return self._gateways.copy()
|
|
265
|
+
|
|
266
|
+
# def __validate_gateway_node_names(self):
|
|
267
|
+
# """
|
|
268
|
+
# this method checks whether all `node_name` keys for the defined gateways really exist
|
|
269
|
+
# """
|
|
270
|
+
# if len(self._gateways) > 0 and self not in self._connections.keys():
|
|
271
|
+
# raise NodeNotExistsError(
|
|
272
|
+
# f"gateways are defined for non-existent nodes for the device {self.related_cls.__name__}")
|
|
273
|
+
# for cur_gateway in self._gateways:
|
|
274
|
+
# cur_gateway.validate_given_node_names()
|
|
275
|
+
|
|
276
|
+
def get_node_types(self) -> Dict[str, List[Connection | None]]:
|
|
277
|
+
"""
|
|
278
|
+
This method returns a dictionary with the node name as key and a connection class as value. This class
|
|
279
|
+
describes the common connection sub-tree, that all incoming and outgoing connections of the related device have
|
|
280
|
+
in common.
|
|
281
|
+
|
|
282
|
+
:raises MultipleNodeBaseException: is thrown if the method finds several unrelated connections as a basis
|
|
283
|
+
"""
|
|
284
|
+
|
|
285
|
+
result = {}
|
|
286
|
+
all_connections = self.get_all_connections()
|
|
287
|
+
for cur_node_name, node_connections in all_connections.items():
|
|
288
|
+
cur_intersection = node_connections[0]
|
|
289
|
+
for cur_node_connection in node_connections[1:]:
|
|
290
|
+
cur_intersection = cur_node_connection.intersection_with(cur_intersection)
|
|
291
|
+
result[cur_node_name] = [cur_intersection]
|
|
292
|
+
|
|
293
|
+
return result
|
|
294
|
+
|
|
295
|
+
def get_new_empty_auto_node(self) -> str:
|
|
296
|
+
"""
|
|
297
|
+
This helper method returns a new empty node name. This method can be used if balder should manage node names
|
|
298
|
+
automatically.
|
|
299
|
+
"""
|
|
300
|
+
|
|
301
|
+
self_node_name = f"n{self._node_cnt}"
|
|
302
|
+
self._node_cnt += 1
|
|
303
|
+
return self_node_name
|
|
304
|
+
|
|
305
|
+
def get_outer_class(self) -> Union[Type[Scenario], Type[Setup], None]:
|
|
306
|
+
"""
|
|
307
|
+
This method delivers the outer class of the related device. This has to be a :class:`Setup` or a
|
|
308
|
+
:class:`Scenario`.
|
|
309
|
+
"""
|
|
310
|
+
return getattr(self.related_cls, '_outer_balder_class', None)
|
|
311
|
+
|
|
312
|
+
def resolve_connection_device_strings(self):
|
|
313
|
+
"""
|
|
314
|
+
This method ensures that device names, that are provided as strings within connections between the current
|
|
315
|
+
device and another device (which is given as string), are resolved. Since the `@connect` marker makes it
|
|
316
|
+
possible to specify the other device as a string, this method will exchange these strings with the related
|
|
317
|
+
device class.
|
|
318
|
+
|
|
319
|
+
.. note::
|
|
320
|
+
This is required, because in some cases you have to provide the devices for the decorator as a string,
|
|
321
|
+
because the outer class could be imported later than the execution of the decorator was done. After Balder
|
|
322
|
+
has read all files, all required information are available and this method should be able to resolve the
|
|
323
|
+
device-strings.
|
|
324
|
+
"""
|
|
325
|
+
for _, node_connections in self.connections.items():
|
|
326
|
+
for cur_conn in node_connections:
|
|
327
|
+
# for every connection applies that the `from_device` must already be a type; also the
|
|
328
|
+
# `to_device` has to be an inner class of this type
|
|
329
|
+
|
|
330
|
+
if isinstance(cur_conn.to_device, type) and issubclass(cur_conn.to_device, Device):
|
|
331
|
+
# Skip because resolving already done
|
|
332
|
+
return
|
|
333
|
+
|
|
334
|
+
# get outer class of `from_device`
|
|
335
|
+
from_device_controller = DeviceController.get_for(cur_conn.from_device)
|
|
336
|
+
parent_cls_from_device = from_device_controller.get_outer_class()
|
|
337
|
+
|
|
338
|
+
all_inner_classes_of_outer = dict(inspect.getmembers(parent_cls_from_device, inspect.isclass))
|
|
339
|
+
if cur_conn.to_device in all_inner_classes_of_outer.keys():
|
|
340
|
+
meta = cur_conn.metadata
|
|
341
|
+
|
|
342
|
+
to_device = all_inner_classes_of_outer[cur_conn.to_device]
|
|
343
|
+
# if there was given no unique node -> create one
|
|
344
|
+
to_device_node_name = DeviceController.get_for(to_device).get_new_empty_auto_node() \
|
|
345
|
+
if meta.to_node_name is None else meta.to_node_name
|
|
346
|
+
|
|
347
|
+
meta.set_to(
|
|
348
|
+
to_device=to_device,
|
|
349
|
+
to_device_node_name=to_device_node_name)
|
|
350
|
+
|
|
351
|
+
cur_conn.set_metadata_for_all_subitems(meta)
|
|
352
|
+
else:
|
|
353
|
+
raise DeviceResolvingException(
|
|
354
|
+
f"cannot resolve the str for the given device class `{cur_conn.to_device}` for "
|
|
355
|
+
f"`@connect` decorator at device `{cur_conn.from_device.__qualname__}`")
|
|
356
|
+
|
|
357
|
+
def validate_inner_referenced_features(self):
|
|
358
|
+
"""
|
|
359
|
+
This method validates that every :class:`Feature` that is referenced from another :class:`Feature` of this
|
|
360
|
+
device also exists in the definition list of this device.
|
|
361
|
+
"""
|
|
362
|
+
all_instantiated_feature_objs = self.get_all_instantiated_feature_objects()
|
|
363
|
+
for _, cur_feature in all_instantiated_feature_objs.items():
|
|
364
|
+
cur_feature_controller = FeatureController.get_for(cur_feature.__class__)
|
|
365
|
+
# now check the inner referenced features of this feature and check if that exists in the device
|
|
366
|
+
for cur_ref_feature_name, cur_ref_feature in \
|
|
367
|
+
cur_feature_controller.get_inner_referenced_features().items():
|
|
368
|
+
potential_candidates = []
|
|
369
|
+
for _, cur_potential_candidate_feature in all_instantiated_feature_objs.items():
|
|
370
|
+
if isinstance(cur_potential_candidate_feature, cur_ref_feature.__class__):
|
|
371
|
+
# the current match is the current feature itself -> not allowed to reference itself
|
|
372
|
+
if cur_potential_candidate_feature == cur_feature:
|
|
373
|
+
raise InnerFeatureResolvingError(
|
|
374
|
+
f"can not reference the same feature from itself (done in feature "
|
|
375
|
+
f"`{cur_feature.__class__.__name__}` with `{cur_ref_feature_name}`)")
|
|
376
|
+
potential_candidates.append(cur_potential_candidate_feature)
|
|
377
|
+
|
|
378
|
+
if len(potential_candidates) == 0:
|
|
379
|
+
raise InnerFeatureResolvingError(
|
|
380
|
+
f"can not find a matching feature in the device `{self.related_cls.__name__}` that could "
|
|
381
|
+
f"be assigned to the inner feature reference `{cur_ref_feature_name}` of the feature "
|
|
382
|
+
f"`{cur_feature.__class__.__name__}`")
|
|
383
|
+
|
|
384
|
+
if len(potential_candidates) > 1:
|
|
385
|
+
raise InnerFeatureResolvingError(
|
|
386
|
+
f"found more than one matching feature in the device `{self.related_cls.__name__}` that "
|
|
387
|
+
f"could be assigned to the inner feature reference `{cur_ref_feature_name}` of the "
|
|
388
|
+
f"feature `{cur_feature.__class__.__name__}`")
|
|
389
|
+
|
|
390
|
+
def validate_inheritance_of_instantiated_features(self):
|
|
391
|
+
"""
|
|
392
|
+
This method validates instantiated features and check that they are inherited correctly. It checks that the
|
|
393
|
+
feature of a child device is also a child class of the feature of the parent device (in case they use the same
|
|
394
|
+
property name).
|
|
395
|
+
"""
|
|
396
|
+
|
|
397
|
+
all_instantiated_feature_objs = self.get_all_instantiated_feature_objects()
|
|
398
|
+
# only one match possible, because we already have checked it before
|
|
399
|
+
next_base_device = self.get_next_parent_class()
|
|
400
|
+
if next_base_device is not None:
|
|
401
|
+
next_base_device_controller = DeviceController.get_for(next_base_device)
|
|
402
|
+
# also execute this method for the base device
|
|
403
|
+
next_base_device_controller.validate_inheritance_of_instantiated_features()
|
|
404
|
+
all_parent_instantiated_feature_objs = next_base_device_controller.get_all_instantiated_feature_objects()
|
|
405
|
+
else:
|
|
406
|
+
all_parent_instantiated_feature_objs = {}
|
|
407
|
+
|
|
408
|
+
for cur_attr_name, cur_feature in all_instantiated_feature_objs.items():
|
|
409
|
+
if cur_attr_name in all_parent_instantiated_feature_objs.keys():
|
|
410
|
+
# attribute name also exists before -> check if the feature is a parent of the current one
|
|
411
|
+
if not isinstance(cur_feature, all_parent_instantiated_feature_objs[cur_attr_name].__class__):
|
|
412
|
+
raise FeatureOverwritingError(
|
|
413
|
+
f"the feature `{cur_feature.__class__.__name__}` with the attribute name `{cur_attr_name}` "
|
|
414
|
+
f"of the device `{self.related_cls.__name__}` you are trying to overwrite is no child class of "
|
|
415
|
+
f"the feature `{all_parent_instantiated_feature_objs[cur_attr_name].__class__.__name__}` "
|
|
416
|
+
f"that was assigned to this property before")
|
|
417
|
+
|
|
418
|
+
def resolve_mapped_vdevice_strings(self):
|
|
419
|
+
"""
|
|
420
|
+
This method updates the inner VDevice-Device mappings for every instantiated :class:`.Feature`, if the
|
|
421
|
+
mapped device (value in constructor) was given as a string. It secures that this device has a real
|
|
422
|
+
:class:`VDevice` reference for its mapped VDevice.
|
|
423
|
+
"""
|
|
424
|
+
all_instanced_features = self.get_original_instanced_feature_objects()
|
|
425
|
+
scenario_or_setup_controller = self.__get_outer_class_controller()
|
|
426
|
+
if all_instanced_features is None:
|
|
427
|
+
# has no features -> skip
|
|
428
|
+
return
|
|
429
|
+
for cur_attr_name, cur_feature in all_instanced_features.items():
|
|
430
|
+
# clone feature and its active_device dict to make sure that shared instances in parent classes are handled
|
|
431
|
+
# correctly
|
|
432
|
+
new_feature = copy.copy(cur_feature)
|
|
433
|
+
new_feature.active_vdevices = {**cur_feature.active_vdevices}
|
|
434
|
+
setattr(self.related_cls, cur_attr_name, new_feature)
|
|
435
|
+
if new_feature.active_vdevices != {}:
|
|
436
|
+
# do something only if there exists an internal mapping
|
|
437
|
+
for cur_mapped_vdevice, cur_mapped_device in new_feature.active_vdevices.items():
|
|
438
|
+
if isinstance(cur_mapped_device, str):
|
|
439
|
+
resolved_device = \
|
|
440
|
+
scenario_or_setup_controller.get_inner_device_class_by_string(cur_mapped_device)
|
|
441
|
+
if resolved_device is None:
|
|
442
|
+
raise RuntimeError(
|
|
443
|
+
f"found no possible matching name while trying to resolve "
|
|
444
|
+
f"the given vDevice string `{cur_mapped_vdevice}` in feature "
|
|
445
|
+
f"`{new_feature.__class__.__name__}`")
|
|
446
|
+
new_feature.active_vdevices[cur_mapped_vdevice] = resolved_device
|