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,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