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,715 @@
1
+ from __future__ import annotations
2
+
3
+ import copy
4
+ from typing import Type, Dict, Union, List, Callable, Tuple
5
+
6
+ import logging
7
+ import inspect
8
+ from _balder.cnnrelations import OrConnectionRelation
9
+ from _balder.device import Device
10
+ from _balder.vdevice import VDevice
11
+ from _balder.feature import Feature
12
+ from _balder.controllers import Controller
13
+ from _balder.controllers.vdevice_controller import VDeviceController
14
+ from _balder.connection import Connection
15
+ from _balder.exceptions import UnclearMethodVariationError, MultiInheritanceError, VDeviceOverwritingError, \
16
+ VDeviceResolvingError, FeatureOverwritingError, IllegalVDeviceMappingError
17
+
18
+ logger = logging.getLogger(__file__)
19
+
20
+
21
+ class FeatureController(Controller):
22
+ """
23
+ This is the controller class for :class:`Feature` items.
24
+ """
25
+ # helper property to disable manual constructor creation
26
+ __priv_instantiate_key = object()
27
+
28
+ #: contains all existing feature and its corresponding controller object
29
+ _items: Dict[Type[Feature], FeatureController] = {}
30
+
31
+ def __init__(self, related_cls, _priv_instantiate_key):
32
+
33
+ # this helps to make this constructor only possible inside the controller object
34
+ if _priv_instantiate_key != FeatureController.__priv_instantiate_key:
35
+ raise RuntimeError('it is not allowed to instantiate a controller manually -> use the static method '
36
+ '`FeatureController.get_for()` for it')
37
+
38
+ if not isinstance(related_cls, type):
39
+ raise TypeError('the attribute `related_cls` has to be a type (no object)')
40
+ if not issubclass(related_cls, Feature):
41
+ raise TypeError(f'the attribute `related_cls` has to be a sub-type of `{Feature.__name__}`')
42
+ if related_cls == Feature:
43
+ raise TypeError(f'the attribute `related_cls` is `{Feature.__name__}` - controllers for native type are '
44
+ f'forbidden')
45
+ # contains a reference to the related class this controller instance belongs to
46
+ self._related_cls = related_cls
47
+
48
+ #: holds the defined **Class-Based-Binding** for the related feature class sorted by VDevice types
49
+ self._cls_for_vdevice: Dict[Type[VDevice], Connection] = {}
50
+
51
+ #: holds the absolute calculated **Class-Based-Binding** for the related feature class sorted by VDevice types
52
+ #: (will be calculated by :meth:`FeatureController.determine_absolute_class_based_for_vdevice`, which will be
53
+ #: called during collecting)
54
+ self._abs_cls_for_vdevice: Union[Dict[Type[VDevice], Connection], None] = None
55
+
56
+ #: contains the **Method-Based-Binding** information for the current feature type (will be automatically set by
57
+ #: executor)
58
+ self._for_vdevice: Union[Dict[str, Dict[Callable, Dict[Type[VDevice], Connection]]], None] = None
59
+
60
+ #: contains the original defined :class:`VDevice` objects for this feature (will be automatically set by
61
+ #: :class:`Collector`)
62
+ self._original_vdevice_definitions: Union[Dict[str, Type[VDevice]], None] = None
63
+
64
+ #: contains the current active method variations for the related feature class - for every key (method name str)
65
+ #: a tuple with the VDevice type, the valid connection and the callable itself will be returned
66
+ self._current_active_method_variation: Dict[str, Tuple[Type[VDevice], Connection, Callable]] = {}
67
+
68
+ # ---------------------------------- STATIC METHODS ----------------------------------------------------------------
69
+
70
+ @staticmethod
71
+ def get_for(related_cls: Type[Feature]) -> FeatureController:
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 FeatureController._items.get(related_cls) is None:
77
+ item = FeatureController(related_cls, _priv_instantiate_key=FeatureController.__priv_instantiate_key)
78
+ FeatureController._items[related_cls] = item
79
+
80
+ return FeatureController._items.get(related_cls)
81
+
82
+ # ---------------------------------- CLASS METHODS -----------------------------------------------------------------
83
+
84
+ # ---------------------------------- PROPERTIES --------------------------------------------------------------------
85
+
86
+ @property
87
+ def related_cls(self) -> Type[Feature]:
88
+ return self._related_cls
89
+
90
+ # ---------------------------------- PROTECTED METHODS -------------------------------------------------------------
91
+
92
+ def _validate_vdevice_reference_used_in_for_vdevice_decorators(self):
93
+ # now check if a definition for this class exists
94
+ all_vdevices = self.get_abs_inner_vdevice_classes()
95
+ # check the class based @for_vdevice and check the used vDevice classes here
96
+ for cur_decorated_vdevice in self.get_class_based_for_vdevice().keys():
97
+ if cur_decorated_vdevice not in all_vdevices:
98
+ raise VDeviceResolvingError(
99
+ f"you assign a vDevice to the class based decorator `@for_vdevice()` of the feature class "
100
+ f"`{self.related_cls.__name__}` which is no direct member of this feature - note that you have "
101
+ f"to define the vDevice in your feature before using it in the decorator - if necessary "
102
+ f"overwrite it")
103
+ # check the method based @for_vdevice and check the used vDevice classes here
104
+ if self.get_method_based_for_vdevice() is not None:
105
+ for cur_method_name, cur_method_data in self.get_method_based_for_vdevice().items():
106
+ for _, vdevice_dict in cur_method_data.items():
107
+ for cur_vdevice in vdevice_dict.keys():
108
+ if cur_vdevice not in all_vdevices:
109
+ raise VDeviceResolvingError(
110
+ f"you assign a vDevice to the method variation `{cur_method_name}` of the feature "
111
+ f"class `{self.related_cls.__name__}` which is no direct member of this feature - note "
112
+ f"that you have to use the overwritten version if you overwrite a vDevice")
113
+
114
+ def _get_method_based_for_vdevice_intersection(self, for_vdevice) -> List[Connection]:
115
+ """helper method that determines the intersection connection list of all method based `@for_vdevice`
116
+ connections of the given `for_vdevice`"""
117
+ intersection = []
118
+
119
+ if self.get_method_based_for_vdevice() is not None:
120
+ for _, method_dict in self.get_method_based_for_vdevice().items():
121
+ for _, vdevice_dict in method_dict.items():
122
+ if for_vdevice not in vdevice_dict.keys():
123
+ continue
124
+ for cur_cnn in vdevice_dict[for_vdevice]:
125
+ if isinstance(cur_cnn, type):
126
+ cur_cnn = cur_cnn()
127
+ # clean metadata here because this is no connection between real devices
128
+ cur_cnn.set_metadata_for_all_subitems(None)
129
+ intersection.append(cur_cnn)
130
+ if len(intersection) == 0:
131
+ return [Connection()]
132
+ return intersection
133
+
134
+ def _determine_all_theoretically_unordered_method_variations(
135
+ self, of_method_name: str, for_vdevice: Type[VDevice],
136
+ with_connection: Union[Connection, Tuple[Connection]]) -> Dict[Callable, Connection]:
137
+ """
138
+ This method returns all theoretically matching method variations. It returns more than one, if there are
139
+ multiple method variation for the given VDevice in this feature, where the given connection is part of the
140
+ connection described by the method variation.
141
+
142
+ :param of_method_name: the name of the method that should be returned
143
+ :param for_vdevice: the VDevice that is mapped
144
+ :param with_connection: the connection that is used between the device that uses the related feature and the
145
+ VDevice
146
+ :return: a dictionary that holds all available method variation that matches here
147
+ """
148
+ all_vdevice_method_variations = self.get_method_based_for_vdevice()
149
+
150
+ if all_vdevice_method_variations is None:
151
+ raise ValueError("the current feature has no method variations")
152
+
153
+ if of_method_name not in all_vdevice_method_variations.keys():
154
+ raise ValueError(f"can not find the method `{of_method_name}` in method variation data dictionary")
155
+
156
+ all_possible_method_variations = {}
157
+ for cur_impl_method, cur_method_impl_dict in all_vdevice_method_variations[of_method_name].items():
158
+ if for_vdevice in cur_method_impl_dict.keys():
159
+ cur_impl_method_cnns = cur_method_impl_dict[for_vdevice].get_singles()
160
+ for cur_single_impl_method_cnn in cur_impl_method_cnns:
161
+ if cur_single_impl_method_cnn.contained_in(with_connection, ignore_metadata=True):
162
+ # this variation is possible
163
+ # ADD IT if it is not available yet
164
+ if cur_impl_method not in all_possible_method_variations.keys():
165
+ all_possible_method_variations[cur_impl_method] = cur_single_impl_method_cnn
166
+ # COMBINE IT if it is already available
167
+ else:
168
+ all_possible_method_variations[cur_impl_method] = Connection.based_on(
169
+ OrConnectionRelation(all_possible_method_variations[cur_impl_method],
170
+ cur_single_impl_method_cnn))
171
+ return all_possible_method_variations
172
+
173
+ # ---------------------------------- METHODS -----------------------------------------------------------------------
174
+
175
+ def get_class_based_for_vdevice(self) -> Dict[Type[VDevice], Connection]:
176
+ """
177
+ This method returns the class based data for the `@for_vdevice` decorator.
178
+ """
179
+ return copy.copy(self._cls_for_vdevice)
180
+
181
+ def get_abs_class_based_for_vdevice(self) -> Dict[Type[VDevice], Connection]:
182
+ """
183
+ This method returns the absolute calculated class-based-for-vdevice data for this feature.
184
+ """
185
+ if self._abs_cls_for_vdevice is None:
186
+ raise RuntimeError('can not access the absolute class based for-vdevices because they are not set yet')
187
+ return self._abs_cls_for_vdevice
188
+
189
+ def set_class_based_for_vdevice(self, data: Dict[Type[VDevice], Connection]):
190
+ """
191
+ This method allows to set the data of the class based `@for_vdevice` decorator.
192
+ """
193
+ self._cls_for_vdevice = data
194
+
195
+ def determine_absolute_class_based_for_vdevice(self, print_warning):
196
+ """
197
+
198
+ This method determines the absolute class based `@for_vdevice` value for the related feature.
199
+
200
+ First it checks if there is a direct class based `@for_vdevice` decorator for this feature. It will not change
201
+ anything, if the value was already set by an explicit class based `@for_vdevice` decorator. In this case the
202
+ method only checks that every given vDevice class is a real part of the current :class:`Feature` class (will be
203
+ returned by direct call of method `Feature.get_inner_vdevice_classes()`). Otherwise, it determines the class
204
+ based `@for_vdevice` value through analysing of the method based decorators and sets this determined value. If
205
+ the method has to determine the value, it throws a warning with a suggestion for a nice class based decorator.
206
+ Also, here the method will analyse the given vDevice classes and secures that they are defined in the current
207
+ :class:`Feature` class.
208
+
209
+ .. note::
210
+ This method automatically updates the values for the parent classes, too. Every time it searches for the
211
+ values it considers the parent values for the vDevice or the parent class of the vDevice, too.
212
+
213
+ .. note::
214
+ This method can throw a user warning (`throw_warning` has to be True for that), but only on the given list
215
+ of :class:`Feature` classes. All parent :class:`Feature` classes will be determined correctly, but will not
216
+ throw a waring.
217
+ """
218
+ # first determine this for all parent classes
219
+ next_parent_feat = self.get_next_parent_feature()
220
+ # with the following recursive call we guarantee that the next parent class has the correct resolved class
221
+ # based @for_vdevice information
222
+ if next_parent_feat:
223
+ FeatureController.get_for(next_parent_feat).determine_absolute_class_based_for_vdevice(print_warning=False)
224
+
225
+ # validate if all used vDevice references in method and class based `@for_vdevice` decorators can be used,
226
+ # because they are members of this feature
227
+ self._validate_vdevice_reference_used_in_for_vdevice_decorators()
228
+
229
+ # now check if a definition for this class exists
230
+ all_vdevices = self.get_abs_inner_vdevice_classes()
231
+
232
+ cls_based_for_vdevice = self.get_class_based_for_vdevice()
233
+ for cur_vdevice in all_vdevices:
234
+ # determine the class based for_vdevice value only if there is no one defined for this vDevice
235
+ if cur_vdevice in cls_based_for_vdevice.keys():
236
+ # there already exists a definition for this vDevice -> IGNORE
237
+ continue
238
+
239
+ # first determine the valid parent intersection (can also be extended itself)
240
+
241
+ # get the correct vDevice that is known in the parent feature class
242
+ vdevice_controller = VDeviceController.get_for(cur_vdevice)
243
+ if vdevice_controller.get_outer_class() == self.related_cls:
244
+ # this cur_vdevice is defined in this Feature class -> check if a parent class of it exists in
245
+ # parent classes of this feature
246
+ # -> search parent vDevice existence and check if this vDevice is also used in a parent class of
247
+ # this feature
248
+ # -> if it does not exist -> there is no parent class based definition
249
+ vdevice_of_interest = vdevice_controller.get_next_parent_vdevice()
250
+
251
+ else:
252
+ # the definition scope of the VDevice is in a higher Feature parent -> has to be in the
253
+ # `__cls_for_vdevice` in one of the next base classes
254
+ vdevice_of_interest = cur_vdevice
255
+
256
+ parent_values = []
257
+ # read vDevice connection tree of parent feature class (only if the vDevice already exists in the
258
+ # parent class)
259
+ if vdevice_of_interest is not None and next_parent_feat is not None:
260
+ next_parent_feat_cls_based_for_vdevice = \
261
+ FeatureController.get_for(next_parent_feat).get_abs_class_based_for_vdevice()
262
+ if vdevice_of_interest in next_parent_feat_cls_based_for_vdevice.keys():
263
+ cnn = next_parent_feat_cls_based_for_vdevice[vdevice_of_interest]
264
+ # clean metadata here because this is no connection between real devices
265
+ cnn.set_metadata_for_all_subitems(None)
266
+ parent_values.append(cnn)
267
+
268
+ this_vdevice_intersection = parent_values
269
+
270
+ # determine the class value automatically by discovering all method variations for this vDevice only
271
+ this_vdevice_intersection += self._get_method_based_for_vdevice_intersection(for_vdevice=cur_vdevice)
272
+
273
+ # print warning only if the printing is enabled and there is a sub connection tree (otherwise, the
274
+ # decorator is not necessary)
275
+ if print_warning and len(this_vdevice_intersection) > 0:
276
+ # only print the warning if there exists method variations for the feature
277
+ if self.get_method_based_for_vdevice() is not None:
278
+ logger.warning(
279
+ f"your used feature class `{self.related_cls.__name__}` doesn't provide a class based "
280
+ f"@for_vdevice decorator for the vDevice `{cur_vdevice.__name__}`\n"
281
+ f"Balder has determined a possible marker:\n\n"
282
+ f'@balder.for_vdevice("{cur_vdevice.__name__}", '
283
+ f'{", ".join([cur_cnn.get_tree_str() for cur_cnn in this_vdevice_intersection])})\n\n')
284
+
285
+ # set the determined data into the class based `@for_vdevice` class property
286
+ cls_based_for_vdevice[cur_vdevice] = Connection.based_on(OrConnectionRelation(*this_vdevice_intersection))
287
+
288
+ self._abs_cls_for_vdevice = cls_based_for_vdevice
289
+
290
+ def get_method_based_for_vdevice(self) -> \
291
+ Union[Dict[str, Dict[Callable, Dict[Type[VDevice], Connection]]], None]:
292
+ """
293
+ This method returns the method based data for the `@for_vdevice` decorator or None, if there is no decorator
294
+ given
295
+ """
296
+ return self._for_vdevice
297
+
298
+ def set_method_based_for_vdevice(
299
+ self, data: Union[Dict[str, Dict[Callable, Dict[Type[VDevice], Connection]]], None]):
300
+ """
301
+ This method allows to set the data for the method based `@for_vdevice` decorator.
302
+ """
303
+ self._for_vdevice = data
304
+
305
+ def get_method_variation(
306
+ self,
307
+ of_method_name: str,
308
+ for_vdevice: Type[VDevice],
309
+ with_connection: Connection,
310
+ ignore_no_findings: bool = False
311
+ ) -> Union[Callable, None]:
312
+ """
313
+ This method searches for the unique possible method variation and returns it. In its search, the method also
314
+ includes the parent classes of the related feature element of this controller.
315
+
316
+ .. note::
317
+ The method throws an exception if it can not find a valid unique method variation for the given data.
318
+
319
+ .. note::
320
+ Note, that the method does not check if the method name, the VDevice nor the given `connection` is really a
321
+ part of this object. Please secure that the data is validated before.
322
+
323
+ .. note::
324
+ The method determines all possible method-variations. If it finds more than one clear method variation it
325
+ tries to sort them hierarchical. This is done by checking if one possible method variation is contained in
326
+ the other. If this can be clearly done, the method returns the furthest out one. Otherwise, it throws an
327
+ `UnclearMethodVariationError`
328
+
329
+ :param of_method_name: the name of the method that should be returned
330
+
331
+ :param for_vdevice: the VDevice that is mapped
332
+
333
+ :param with_connection: the connection that is used between the device that uses the related feature and the
334
+ VDevice
335
+
336
+ :param ignore_no_findings: if this attribute is true, the method will not throw an exception if it can not find
337
+ something, it only returns None
338
+
339
+ :return: the method variation callable for the given data (or none, if the method does not exist in this object
340
+ or in a parent class of it)
341
+ """
342
+ # first determine all possible method-variations
343
+ all_possible_method_variations = self._determine_all_theoretically_unordered_method_variations(
344
+ of_method_name=of_method_name, for_vdevice=for_vdevice, with_connection=with_connection)
345
+
346
+ # there are no method variations in this feature directly -> check parent classes
347
+ if len(all_possible_method_variations) == 0:
348
+ # try to execute this method in parent classes
349
+ for cur_base in self.related_cls.__bases__:
350
+ if issubclass(cur_base, Feature) and cur_base != Feature:
351
+ parent_meth_result = FeatureController.get_for(cur_base).get_method_variation(
352
+ of_method_name=of_method_name, for_vdevice=for_vdevice, with_connection=with_connection,
353
+ ignore_no_findings=True)
354
+ if parent_meth_result:
355
+ return parent_meth_result
356
+ if not ignore_no_findings:
357
+ raise UnclearMethodVariationError(
358
+ f"found no possible method variation for method "
359
+ f"`{self.related_cls.__name__}.{of_method_name}` with vDevice `{for_vdevice.__name__}` "
360
+ f"and usable connection `{with_connection.get_tree_str()}´")
361
+ return None
362
+
363
+ # we only have one -> selection is clear
364
+ if len(all_possible_method_variations) == 1:
365
+ return list(all_possible_method_variations.keys())[0]
366
+
367
+ # if there are more than one possible method variation, try to determine the outer one
368
+ remaining_possible_method_variations = list(
369
+ filter(
370
+ lambda meth: not max(all_possible_method_variations[meth].contained_in(cur_other_cnn)
371
+ for cur_other_cnn in all_possible_method_variations.values()
372
+ if cur_other_cnn != all_possible_method_variations[meth]),
373
+ all_possible_method_variations.keys())
374
+ )
375
+
376
+ if len(remaining_possible_method_variations) > 1:
377
+ raise UnclearMethodVariationError(
378
+ f"found more than one possible method variation for method "
379
+ f"`{self.related_cls.__name__}.{of_method_name}` with vDevice `{for_vdevice.__name__}` "
380
+ f"and usable connection `{with_connection.get_tree_str()}´")
381
+ return remaining_possible_method_variations[0]
382
+
383
+ def get_inner_vdevice_classes(self) -> List[Type[VDevice]]:
384
+ """
385
+ This is a method that determines the inner VDevice classes for the related feature class. If the method can not
386
+ find some VDevices in the current class it returns an empty list. This method will never search in parent
387
+ classes.
388
+
389
+ If you want to get the absolute VDevices use :meth:`Feature.get_inner_vdevice_classes`.
390
+ """
391
+
392
+ all_classes = inspect.getmembers(self.related_cls, inspect.isclass)
393
+ filtered_classes = []
394
+ for _, cur_class in all_classes:
395
+ if not issubclass(cur_class, VDevice):
396
+ # filter all classes and make sure that only the child classes of :class:`VDevice` remain
397
+ continue
398
+
399
+ if VDeviceController.get_for(cur_class).get_outer_class() != self.related_cls:
400
+ # filter all classes that do not match the setup name in __qualname__
401
+ continue
402
+ # otherwise, add this candidate
403
+ filtered_classes.append(cur_class)
404
+
405
+ if len(filtered_classes) == 0:
406
+ # do not found some VDevice classes -> search in parent
407
+ for cur_base in self.related_cls.__bases__:
408
+ if cur_base == Feature:
409
+ return []
410
+ if issubclass(cur_base, Feature):
411
+ return FeatureController.get_for(cur_base).get_abs_inner_vdevice_classes()
412
+
413
+ return filtered_classes
414
+
415
+ def get_inner_vdevice_class_by_string(self, device_str: str) -> Union[Type[VDevice], None]:
416
+ """
417
+ This method returns the inner VDevice class for the given string.
418
+
419
+ :param device_str: the name string of the VDevice that should be returned
420
+
421
+ :return: the VDevice class or None, if the method has not found any class with this name
422
+ """
423
+ possible_vdevs = [cur_vdevice for cur_vdevice in self.get_inner_vdevice_classes()
424
+ if cur_vdevice.__name__ == device_str]
425
+ if len(possible_vdevs) == 0:
426
+ return None
427
+ if len(possible_vdevs) > 1:
428
+ raise RuntimeError("found more than one possible vDevices - something unexpected happened")
429
+
430
+ return possible_vdevs[0]
431
+
432
+ def get_abs_inner_vdevice_classes(self) -> List[Type[VDevice]]:
433
+ """
434
+ This is a method that determines the inner VDevice classes for the feature class. If the method can not find
435
+ some VDevices in the related feature class it also starts searching in the base classes. It always returns the
436
+ first existing definition in the relevant parent classes.
437
+ """
438
+
439
+ filtered_classes = self.get_inner_vdevice_classes()
440
+
441
+ if len(filtered_classes) == 0:
442
+ # do not found some VDevice classes -> search in parent
443
+ for cur_base in self.related_cls.__bases__:
444
+ if cur_base == Feature:
445
+ return []
446
+ if issubclass(cur_base, Feature):
447
+ return FeatureController.get_for(cur_base).get_abs_inner_vdevice_classes()
448
+
449
+ return filtered_classes
450
+
451
+ def get_inner_referenced_features(self) -> Dict[str, Feature]:
452
+ """
453
+ This method returns a dictionary with all referenced :class:`Feature` objects, where the variable name is the
454
+ key and the instantiated object the value.
455
+ """
456
+
457
+ result = {}
458
+ for cur_name in dir(self.related_cls):
459
+ cur_val = getattr(self.related_cls, cur_name)
460
+ if isinstance(cur_val, Feature):
461
+ result[cur_name] = cur_val
462
+ return result
463
+
464
+ def validate_inner_vdevice_inheritance(self):
465
+ """
466
+ This method validates the inheritance of all inner :class:`VDevice` classes of the feature that belongs to this
467
+ controller.
468
+
469
+ It secures that new :class:`VDevice` classes are added or existing :class:`VDevice` classes are completely being
470
+ overwritten for every feature level. The method only allows the overwriting of :class:`VDevices`, which are
471
+ subclasses of another :class:`VDevice` that is defined in a parent :class:`Feature` class. In addition, the
472
+ class has to have the same name as its parent class.
473
+
474
+ The method also secures that the user overwrites instantiated :class:`Feature` classes in the VDevice (class
475
+ property name is the same) only with subclasses of the element that is being overwritten. New Features can be
476
+ added without consequences.
477
+ """
478
+
479
+ all_direct_vdevices_of_this_feature_lvl = self.get_inner_vdevice_classes()
480
+ if len(all_direct_vdevices_of_this_feature_lvl) != 0:
481
+ # check that all absolute items of higher class are implemented
482
+ next_feature_parent = None
483
+ for cur_parent in self._related_cls.__bases__:
484
+ if issubclass(cur_parent, Feature) and cur_parent != Feature:
485
+ if next_feature_parent is not None:
486
+ raise MultiInheritanceError(
487
+ "can not select the next parent class, found more than one parent classes for feature "
488
+ f"`{self._related_cls.__name__}` that is a subclass of `{Feature.__name__}`")
489
+ next_feature_parent = cur_parent
490
+ # only continue if the current feature has a parent class
491
+ if next_feature_parent:
492
+ # first check the parent feature (secure that the inheritance chain is valid first)
493
+ parent_collector = FeatureController.get_for(next_feature_parent)
494
+ parent_collector.validate_inner_vdevice_inheritance()
495
+
496
+ # now continue with checking the inheritance between this feature and its direct parent
497
+ parent_vdevices = parent_collector.get_abs_inner_vdevice_classes()
498
+ # now check that every parent vDevice also exists in the current selection
499
+ for cur_parent_vdevice in parent_vdevices:
500
+ direct_namings = [cur_item.__name__ for cur_item in all_direct_vdevices_of_this_feature_lvl]
501
+ # check that the parent vDevice exists in the direct namings
502
+ if cur_parent_vdevice.__name__ not in direct_namings:
503
+ raise VDeviceOverwritingError(
504
+ f"missing overwriting of parent VDevice class `{cur_parent_vdevice.__qualname__}` in "
505
+ f"feature class `{self._related_cls.__name__}` - if you overwrite one or more VDevice(s) "
506
+ f"you have to overwrite all!")
507
+
508
+ # otherwise check if inheritance AND feature overwriting is correct
509
+ cur_child_idx = direct_namings.index(cur_parent_vdevice.__name__)
510
+ related_child_vdevice = all_direct_vdevices_of_this_feature_lvl[cur_child_idx]
511
+ if not issubclass(related_child_vdevice, cur_parent_vdevice):
512
+ # inherit from a parent device, but it has not the same naming -> NOT ALLOWED
513
+ raise VDeviceOverwritingError(
514
+ f"the inner vDevice class `{related_child_vdevice.__qualname__}` has the same "
515
+ f"name than the vDevice `{cur_parent_vdevice.__qualname__}` - it should also "
516
+ f"inherit from it")
517
+ # todo check that feature overwriting inside the VDevice is correct
518
+ # now check that the vDevice overwrites the existing properties only in a proper manner (to
519
+ # overwrite it, it has to have the same property name as the property in the next parent
520
+ # class)
521
+ cur_vdevice_features = \
522
+ VDeviceController.get_for(related_child_vdevice).get_all_instantiated_feature_objects()
523
+ cur_vdevice_base_features = \
524
+ VDeviceController.get_for(cur_parent_vdevice).get_all_instantiated_feature_objects()
525
+ for cur_base_property_name, cur_base_feature_instance in cur_vdevice_base_features.items():
526
+ # now check that every base property is available in the current vDevice too - check
527
+ # that the instantiated feature is the same or the feature of the child vDevice is a
528
+ # child of it -> ignore it, if the child vDevice has more features than the base -
529
+ # that doesn't matter
530
+ if cur_base_property_name not in cur_vdevice_features.keys():
531
+ raise VDeviceResolvingError(
532
+ f"can not find the property `{cur_base_property_name}` of "
533
+ f"parent vDevice `{cur_parent_vdevice.__qualname__}` in the "
534
+ f"current vDevice class `{related_child_vdevice.__qualname__}`")
535
+ cur_feature_instance = cur_vdevice_features[cur_base_property_name]
536
+ if not isinstance(cur_feature_instance, cur_base_feature_instance.__class__):
537
+ raise FeatureOverwritingError(
538
+ f"you are trying to overwrite an existing vDevice Feature property "
539
+ f"`{cur_base_property_name}` in vDevice `{related_child_vdevice.__qualname__}` "
540
+ f"from the parent vDevice class `{cur_parent_vdevice.__qualname__}` - this is "
541
+ f"only possible with a child (or with the same) feature class the parent "
542
+ f"uses (in this case the `{cur_base_feature_instance.__class__.__name__}`)")
543
+
544
+ def validate_inherited_class_based_vdevice_cnn_subset(self):
545
+ """
546
+ This method checks that the class based for_vdevice values of a child :class:`Feature` class are contained_in
547
+ the related VDevice defined in a parent :class:`Feature` class.
548
+ """
549
+
550
+ to_checking_parent_features = []
551
+
552
+ feature_vdevices = self.get_abs_inner_vdevice_classes()
553
+ for cur_vdevice in feature_vdevices:
554
+ cur_vdevice_cls_cnn = self.get_abs_class_based_for_vdevice().get(cur_vdevice)
555
+ # get parent class of vdevice
556
+ relevant_parent_class = VDeviceController.get_for(cur_vdevice).get_next_parent_vdevice()
557
+
558
+ # continue with next vdevice if no relevant parent class was found
559
+ if relevant_parent_class is None:
560
+ continue
561
+
562
+ relevant_parent_class_controller = VDeviceController.get_for(relevant_parent_class)
563
+ # only if there is a higher class which has to be considered
564
+ parent_vdevice_feature = relevant_parent_class_controller.get_outer_class()
565
+ if parent_vdevice_feature not in to_checking_parent_features:
566
+ to_checking_parent_features.append(parent_vdevice_feature)
567
+ parent_vdevice_cnn = \
568
+ FeatureController.get_for(
569
+ parent_vdevice_feature).get_abs_class_based_for_vdevice()[relevant_parent_class]
570
+ # check if VDevice connection elements are all contained in the parent connection
571
+ if not cur_vdevice_cls_cnn.contained_in(parent_vdevice_cnn, ignore_metadata=True):
572
+ raise VDeviceResolvingError(
573
+ f"the VDevice `{cur_vdevice.__name__}` is a child of the VDevice "
574
+ f"`{relevant_parent_class.__name__}`, which doesn't implements the connection of "
575
+ f"the child - the connection element `{cur_vdevice_cls_cnn.get_tree_str()})´ is not "
576
+ f"contained in the connection-tree of the parent VDevice")
577
+
578
+ # validate inheritance levels for all features with parent VDevices as inner-classes
579
+ for cur_feature in to_checking_parent_features:
580
+ FeatureController.get_for(cur_feature).validate_inherited_class_based_vdevice_cnn_subset()
581
+
582
+ def get_next_parent_feature(self) -> Union[Type[Feature], None]:
583
+ """
584
+ This method returns the next parent class of this feature, which is still a subclass of :class:`Feature`. If
585
+ the next parent class is :class:`Feature`, None will be returned.
586
+
587
+ :return: the parent Feature class or None if no parent exists
588
+ """
589
+ possible_parent_classes = []
590
+ for cur_parent in self.related_cls.__bases__:
591
+ if issubclass(cur_parent, Feature) and cur_parent != Feature:
592
+ possible_parent_classes.append(cur_parent)
593
+
594
+ if len(possible_parent_classes) > 1:
595
+ raise MultiInheritanceError(
596
+ f"the feature `{self.related_cls.__name__}` has more than one parent classes from type "
597
+ f"`Feature` - this is not allowed")
598
+
599
+ if len(possible_parent_classes) == 1:
600
+ # we have found one parent feature class
601
+ return possible_parent_classes[0]
602
+
603
+ # we have no parent class
604
+ return None
605
+
606
+ def set_active_method_variation(self, method_selection: Dict[str, Tuple[Type[VDevice], Connection, Callable]]):
607
+ """
608
+ This method sets the active method variation selection for the related feature class.
609
+ :param method_selection: the method selection that should be set
610
+ """
611
+ self._current_active_method_variation = method_selection
612
+
613
+ def get_active_method_variation(self, method_name: str) \
614
+ -> Union[Tuple[Type[VDevice], Connection, Callable], Tuple[None, None, None]]:
615
+ """
616
+ This method returns the current active method variation for the given `method_name` for the related fixture.
617
+
618
+ .. note::
619
+ Please note, this method only returns the set active method variation for this related feature only. It does
620
+ not check parent classes of this feature.
621
+
622
+ :param method_name: the name of the method the current active method variation should be returned
623
+
624
+ :return: a tuple with the current active method selection or a tuple with `None` if no active method variation
625
+ exists on this feature class level
626
+ """
627
+ return self._current_active_method_variation.get(method_name, tuple([None, None, None]))
628
+
629
+ def get_inherited_method_variation(self, parent_class: Type[Feature], method_var_name: str):
630
+ """
631
+ This method will determine the correct inherited method-variation for the current object. For this, it searches
632
+ in the base classes of the given `parent_class` (which has to be a parent class of `self`) for the
633
+ method-variation that should be called.
634
+ It automatically detects if the parent class has a method-variation or is a single normal method. In case that
635
+ the method is a single normal method, it will directly return it, otherwise it searches the correct
636
+ method-variation according to the vDevice mapping of the current object and return the current active
637
+ method-variation.
638
+
639
+ :param parent_class: the parent class of this object, the method should start searching for the
640
+ `method_var_name` method (it searches in this class and all parents)
641
+
642
+ :param method_var_name: the name of the method or of the method variation that should be returned
643
+ """
644
+
645
+ parent_class_controller = FeatureController.get_for(parent_class)
646
+ if parent_class_controller.get_method_based_for_vdevice() is not None and \
647
+ method_var_name in parent_class_controller.get_method_based_for_vdevice().keys():
648
+ # the parent class has a method-variation -> get the current active version of it
649
+
650
+ # first get the active data for the instantiated feature object
651
+ active_vdevice, active_cnn_intersection, _ = self.get_active_method_variation(method_var_name)
652
+ # get the vDevice object that is used in the given parent class
653
+ if hasattr(parent_class, active_vdevice.__name__):
654
+ parent_vdevice = getattr(parent_class, active_vdevice.__name__)
655
+ else:
656
+ return None
657
+
658
+ # then determine the correct method variation according to the data of the instantiated object
659
+ cur_method_variation = parent_class_controller.get_method_variation(
660
+ of_method_name=method_var_name, for_vdevice=parent_vdevice,
661
+ with_connection=active_cnn_intersection, ignore_no_findings=True)
662
+ return cur_method_variation
663
+
664
+ if hasattr(parent_class, method_var_name):
665
+ # we found one normal method in this object
666
+ return getattr(parent_class, method_var_name)
667
+
668
+ # execute this method for all based and check if there is exactly one
669
+ next_base_feature_class = parent_class_controller.get_next_parent_feature()
670
+ if next_base_feature_class is None:
671
+ return None
672
+
673
+ return self.get_inherited_method_variation(next_base_feature_class, method_var_name)
674
+
675
+ def validate_inner_classes(self):
676
+ """
677
+ This method validates all inner classes of the related feature and secures that none of these subclasses are
678
+ subclass of :class:`Device` but not subclasses from :class:`VDevice`. Of course other inner-classes that are not
679
+ required for balder are allowed too.
680
+ """
681
+ all_inner_classes = inspect.getmembers(self.related_cls, inspect.isclass)
682
+ for cur_inner_name, cur_inner_class in all_inner_classes:
683
+ if not issubclass(cur_inner_class, Device):
684
+ # ignore this element
685
+ continue
686
+ # do only check the inner classes that inherits from `Device`
687
+ if not issubclass(cur_inner_class, VDevice):
688
+ raise VDeviceResolvingError(
689
+ f"the inner class `{cur_inner_class.__name__}` with name `{cur_inner_name}` is a child "
690
+ f"class of `Device` but not from `VDevice` as expected")
691
+ cur_inner_class_instantiated_features = \
692
+ VDeviceController.get_for(cur_inner_class).get_all_instantiated_feature_objects()
693
+ for _, cur_vdevice_feature in cur_inner_class_instantiated_features.items():
694
+ if cur_vdevice_feature.active_vdevices != {}:
695
+ raise IllegalVDeviceMappingError(
696
+ f"the feature `{cur_vdevice_feature.__class__.__name__}` you have instantiated in your "
697
+ f"vDevice `{cur_inner_class.__name__}` of feature `{self.related_cls.__name__}` "
698
+ f"has a own vDevice mapping - vDevice mappings are allowed for features on Devices "
699
+ f"only")
700
+
701
+ def get_original_vdevice_definitions(self) -> Dict[str, Type[VDevice]]:
702
+ """
703
+ This method returns the :class:`VDevice` definitions that are the original definitions for this feature.
704
+ """
705
+ if self._original_vdevice_definitions is None:
706
+ raise RuntimeError('can not access the original VDevice definitions before they were set with '
707
+ '`save_all_current_vdevice_references_as_originals`')
708
+ return self._original_vdevice_definitions
709
+
710
+ def save_all_current_vdevice_references_as_originals(self):
711
+ """
712
+ This method saves the current existing :class:`VDevice` definitions inside this feature as originals.
713
+ """
714
+ new_originals = self.get_abs_inner_vdevice_classes()
715
+ self._original_vdevice_definitions = {cur_vdevice.__name__: cur_vdevice for cur_vdevice in new_originals}