baldertest 0.1.0b9__py3-none-any.whl → 0.1.0b11__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 (53) hide show
  1. _balder/_version.py +1 -1
  2. _balder/balder_session.py +4 -2
  3. _balder/cnnrelations/__init__.py +7 -0
  4. _balder/cnnrelations/and_connection_relation.py +149 -0
  5. _balder/cnnrelations/base_connection_relation.py +270 -0
  6. _balder/cnnrelations/or_connection_relation.py +65 -0
  7. _balder/collector.py +116 -14
  8. _balder/connection.py +400 -881
  9. _balder/connection_metadata.py +255 -0
  10. _balder/console/balder.py +1 -1
  11. _balder/controllers/device_controller.py +26 -12
  12. _balder/controllers/feature_controller.py +63 -99
  13. _balder/controllers/normal_scenario_setup_controller.py +7 -7
  14. _balder/controllers/scenario_controller.py +97 -7
  15. _balder/controllers/setup_controller.py +2 -3
  16. _balder/decorator_connect.py +12 -10
  17. _balder/decorator_fixture.py +4 -7
  18. _balder/decorator_for_vdevice.py +21 -31
  19. _balder/decorator_gateway.py +3 -3
  20. _balder/decorator_parametrize.py +31 -0
  21. _balder/decorator_parametrize_by_feature.py +36 -0
  22. _balder/exceptions.py +6 -0
  23. _balder/executor/basic_executable_executor.py +126 -0
  24. _balder/executor/basic_executor.py +1 -78
  25. _balder/executor/executor_tree.py +7 -5
  26. _balder/executor/parametrized_testcase_executor.py +52 -0
  27. _balder/executor/scenario_executor.py +5 -2
  28. _balder/executor/setup_executor.py +5 -2
  29. _balder/executor/testcase_executor.py +41 -9
  30. _balder/executor/unresolved_parametrized_testcase_executor.py +209 -0
  31. _balder/executor/variation_executor.py +148 -123
  32. _balder/feature.py +1 -1
  33. _balder/fixture_definition_scope.py +19 -0
  34. _balder/fixture_execution_level.py +22 -0
  35. _balder/fixture_manager.py +170 -182
  36. _balder/fixture_metadata.py +26 -0
  37. _balder/objects/connections/osi_3_network.py +2 -2
  38. _balder/objects/connections/osi_4_transport.py +2 -2
  39. _balder/parametrization.py +75 -0
  40. _balder/routing_path.py +18 -25
  41. _balder/solver.py +52 -32
  42. _balder/testresult.py +1 -1
  43. _balder/utils.py +27 -1
  44. balder/__init__.py +6 -0
  45. balder/exceptions.py +4 -3
  46. balder/parametrization.py +8 -0
  47. {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b11.dist-info}/METADATA +2 -2
  48. baldertest-0.1.0b11.dist-info/RECORD +83 -0
  49. {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b11.dist-info}/WHEEL +1 -1
  50. baldertest-0.1.0b9.dist-info/RECORD +0 -68
  51. {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b11.dist-info}/LICENSE +0 -0
  52. {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b11.dist-info}/entry_points.txt +0 -0
  53. {baldertest-0.1.0b9.dist-info → baldertest-0.1.0b11.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,18 @@
1
1
  from __future__ import annotations
2
+
3
+ import itertools
2
4
  from typing import Type, Union, List, Dict, Tuple, TYPE_CHECKING
3
5
 
4
6
  import inspect
5
7
  import logging
8
+ from _balder.cnnrelations import OrConnectionRelation
6
9
  from _balder.device import Device
7
10
  from _balder.connection import Connection
11
+ from _balder.fixture_execution_level import FixtureExecutionLevel
8
12
  from _balder.testresult import ResultState, BranchBodyResult, ResultSummary
9
- from _balder.executor.basic_executor import BasicExecutor
13
+ from _balder.executor.basic_executable_executor import BasicExecutableExecutor
10
14
  from _balder.executor.testcase_executor import TestcaseExecutor
15
+ from _balder.executor.unresolved_parametrized_testcase_executor import UnresolvedParametrizedTestcaseExecutor
11
16
  from _balder.previous_executor_mark import PreviousExecutorMark
12
17
  from _balder.routing_path import RoutingPath
13
18
  from _balder.unmapped_vdevice import UnmappedVDevice
@@ -19,6 +24,8 @@ if TYPE_CHECKING:
19
24
  from _balder.feature import Feature
20
25
  from _balder.scenario import Scenario
21
26
  from _balder.vdevice import VDevice
27
+ from _balder.controllers.scenario_controller import ScenarioController
28
+ from _balder.controllers.setup_controller import SetupController
22
29
  from _balder.executor.scenario_executor import ScenarioExecutor
23
30
  from _balder.fixture_manager import FixtureManager
24
31
 
@@ -26,10 +33,11 @@ if TYPE_CHECKING:
26
33
  logger = logging.getLogger(__file__)
27
34
 
28
35
 
29
- class VariationExecutor(BasicExecutor):
36
+ class VariationExecutor(BasicExecutableExecutor):
30
37
  """
31
38
  A VariationExecutor only contains :meth:`TestcaseExecutor` children.
32
39
  """
40
+ fixture_execution_level = FixtureExecutionLevel.VARIATION
33
41
 
34
42
  def __init__(self, device_mapping: Dict[Type[Device], Type[Device]], parent: ScenarioExecutor):
35
43
  super().__init__()
@@ -93,11 +101,25 @@ class VariationExecutor(BasicExecutor):
93
101
  """property returns the current :class:`Scenario` for this variation"""
94
102
  return self._parent_executor.base_scenario_class
95
103
 
104
+ @property
105
+ def cur_scenario_controller(self) -> ScenarioController:
106
+ """
107
+ returns the current :class:`ScenarioController` for this variation
108
+ """
109
+ return self._parent_executor.base_scenario_controller
110
+
96
111
  @property
97
112
  def cur_setup_class(self) -> Setup:
98
113
  """property returns the current :class:`Setup` for this variation"""
99
114
  return self._parent_executor.parent_executor.base_setup_class
100
115
 
116
+ @property
117
+ def cur_setup_controller(self) -> SetupController:
118
+ """
119
+ returns the current :class:`SetupController` for this variation
120
+ """
121
+ return self._parent_executor.parent_executor.base_setup_controller
122
+
101
123
  @property
102
124
  def base_device_mapping(self) -> Dict[Type[Device], Type[Device]]:
103
125
  """
@@ -107,7 +129,7 @@ class VariationExecutor(BasicExecutor):
107
129
  return self._base_device_mapping
108
130
 
109
131
  @property
110
- def all_child_executors(self) -> List[TestcaseExecutor]:
132
+ def all_child_executors(self) -> List[TestcaseExecutor | UnresolvedParametrizedTestcaseExecutor]:
111
133
  return self._testcase_executors
112
134
 
113
135
  @property
@@ -153,6 +175,7 @@ class VariationExecutor(BasicExecutor):
153
175
  self.exchange_unmapped_vdevice_references()
154
176
  self.update_vdevice_referenced_feature_instances()
155
177
  self.set_conn_dependent_methods()
178
+ self.resolve_possible_parametrization()
156
179
 
157
180
  def _body_execution(self, show_discarded):
158
181
  if show_discarded and not self.can_be_applied():
@@ -280,6 +303,39 @@ class VariationExecutor(BasicExecutor):
280
303
  f'can not find a valid routing on setup level for the connection `{scenario_cnn.get_tree_str()}` '
281
304
  f'between scenario devices `{scenario_cnn.from_device}` and `{scenario_cnn.to_device}`')
282
305
 
306
+ def _get_matching_setup_features_for(
307
+ self,
308
+ scenario_feature_obj: Feature,
309
+ in_setup_device: Type[Device]
310
+ ) -> List[Feature]:
311
+ """
312
+ Helper method that returns all matching setup features for the provided scenario feature in the provided setup
313
+ device.
314
+ """
315
+ cur_setup_features = DeviceController.get_for(in_setup_device).get_all_instantiated_feature_objects()
316
+
317
+ replacing_feature_candidates = [
318
+ cur_setup_feature for cur_setup_feature in cur_setup_features.values()
319
+ if isinstance(cur_setup_feature, scenario_feature_obj.__class__)
320
+ ]
321
+ active_scenario_vdev, mapped_scenario_dev = scenario_feature_obj.active_vdevice_device_mapping
322
+
323
+ replacing_features = replacing_feature_candidates.copy()
324
+ if mapped_scenario_dev is not None:
325
+ # get the related setup device for the mapped scenario device (on scenario level)
326
+ setup_dev_of_mapped_scenario_dev = self.get_setup_device_for(mapped_scenario_dev)
327
+
328
+ # now check if there is a mapping on setup level too
329
+ for cur_replacing_feature in replacing_feature_candidates:
330
+ mapped_setup_vdev, mapped_setup_dev = cur_replacing_feature.active_vdevice_device_mapping
331
+ if mapped_setup_vdev is not None and not issubclass(mapped_setup_vdev, active_scenario_vdev):
332
+ # drop this feature matching, because we have different vdevice mapped
333
+ replacing_features.remove(cur_replacing_feature)
334
+ elif mapped_setup_dev is not None and mapped_setup_dev != setup_dev_of_mapped_scenario_dev:
335
+ # drop this feature matching, because it is not applicable here
336
+ replacing_features.remove(cur_replacing_feature)
337
+ return replacing_features
338
+
283
339
  # ---------------------------------- METHODS -----------------------------------------------------------------------
284
340
 
285
341
  def testsummary(self) -> ResultSummary:
@@ -289,14 +345,22 @@ class VariationExecutor(BasicExecutor):
289
345
 
290
346
  def get_testcase_executors(self) -> List[TestcaseExecutor]:
291
347
  """returns all sub testcase executors that belongs to this variation-executor"""
292
- return self._testcase_executors
348
+ result = []
349
+ for cur_executor in self._testcase_executors:
350
+ if (isinstance(cur_executor, UnresolvedParametrizedTestcaseExecutor) and
351
+ cur_executor.parametrization_has_been_resolved):
352
+ result += cur_executor.get_testcase_executors()
353
+ else:
354
+ result.append(cur_executor)
355
+ return result
293
356
 
294
- def add_testcase_executor(self, testcase_executor: TestcaseExecutor):
357
+ def add_testcase_executor(self, testcase_executor: TestcaseExecutor | UnresolvedParametrizedTestcaseExecutor):
295
358
  """
296
359
  This method adds a new TestcaseExecutor to the child element list of this object branch
297
360
  """
298
- if not isinstance(testcase_executor, TestcaseExecutor):
299
- raise TypeError("the given object `testcase_executor` must be of type type `TestcaseExecutor`")
361
+ if not isinstance(testcase_executor, (TestcaseExecutor, UnresolvedParametrizedTestcaseExecutor)):
362
+ raise TypeError("the given object `testcase_executor` must be of type type `TestcaseExecutor` or "
363
+ "`UnresolvedParametrizedTestcaseExecutor`")
300
364
  if testcase_executor in self._testcase_executors:
301
365
  raise ValueError("the given object `testcase_executor` already exists in child list")
302
366
  self._testcase_executors.append(testcase_executor)
@@ -324,75 +388,53 @@ class VariationExecutor(BasicExecutor):
324
388
  :raises NotApplicableVariationError: will be thrown if this variation cannot be applied, because the setup-/
325
389
  scenario-device-features can not be resolved
326
390
  """
327
- feature_replacement = {}
328
- abs_setup_vdevice_mappings = {}
391
+ feature_replacement = {scenario_dev: {} for scenario_dev in self.base_device_mapping.keys()}
392
+ abs_setup_vdevice_mappings = {setup_dev: {} for setup_dev in self.base_device_mapping.values()}
329
393
  for cur_scenario_device, cur_setup_device in self.base_device_mapping.items():
330
394
  cur_setup_features = DeviceController.get_for(cur_setup_device).get_all_instantiated_feature_objects()
331
395
 
332
- if cur_scenario_device not in feature_replacement.keys():
333
- feature_replacement[cur_scenario_device] = {}
334
-
335
- if cur_setup_device not in abs_setup_vdevice_mappings.keys():
336
- abs_setup_vdevice_mappings[cur_setup_device] = {}
337
-
338
396
  all_assigned_setup_features = []
339
397
  cur_scenario_device_orig_features = \
340
398
  DeviceController.get_for(cur_scenario_device).get_original_instanced_feature_objects()
341
- for cur_attr_name, cur_abstract_scenario_feature_obj in cur_scenario_device_orig_features.items():
342
- replacing_features = [cur_setup_feature for _, cur_setup_feature in cur_setup_features.items()
343
- if isinstance(cur_setup_feature, cur_abstract_scenario_feature_obj.__class__)]
344
- used_scenario_vdevice, mapped_scenario_device = \
345
- cur_abstract_scenario_feature_obj.active_vdevice_device_mapping
346
- # check if there is a mapped device in scenario level
347
- cleanup_replacing_features = replacing_features.copy()
348
- if mapped_scenario_device is not None:
349
- # get the related setup device for the mapped scenario device (on scenario level)
350
- to_scenarios_vdevice_mapped_setup_device = self.get_setup_device_for(mapped_scenario_device)
351
-
352
- # now check if there is a mapping on setup level too
353
- for cur_replacing_feature in replacing_features:
354
- mapped_setup_vdevice, mapped_setup_device = cur_replacing_feature.active_vdevice_device_mapping
355
- if mapped_setup_vdevice is not None and \
356
- not issubclass(mapped_setup_vdevice, used_scenario_vdevice):
357
- # drop this feature matching, because we have different vdevice mapped
358
- cleanup_replacing_features.remove(cur_replacing_feature)
359
- elif mapped_setup_device is not None and \
360
- mapped_setup_device != to_scenarios_vdevice_mapped_setup_device:
361
- # drop this feature matching, because it is not applicable here
362
- cleanup_replacing_features.remove(cur_replacing_feature)
363
-
364
- if len(cleanup_replacing_features) != 1:
399
+ for cur_attr_name, cur_scenario_feature_obj in cur_scenario_device_orig_features.items():
400
+ active_scenario_vdevice, mapped_scenario_device = cur_scenario_feature_obj.active_vdevice_device_mapping
401
+
402
+ cur_setup_feature_objs = self._get_matching_setup_features_for(
403
+ scenario_feature_obj=cur_scenario_feature_obj, in_setup_device=cur_setup_device
404
+ )
405
+
406
+ if len(cur_setup_feature_objs) != 1:
365
407
  raise NotApplicableVariationException(
366
408
  f'this variation can not be applicable because there was no setup feature implementation of '
367
- f'`{cur_abstract_scenario_feature_obj.__class__.__name__}` (used by scenario device '
409
+ f'`{cur_scenario_feature_obj.__class__.__name__}` (used by scenario device '
368
410
  f'`{cur_scenario_device.__name__}`) in setup device `{cur_setup_device.__name__}`')
411
+ cur_setup_feature_obj = cur_setup_feature_objs[0]
369
412
 
370
413
  if mapped_scenario_device is None:
371
414
  # we have exactly one matching candidate, but also no vDevice mapping
372
415
  # check if the matching candidate has a vDevice mapping
373
- _, mapped_setup_device = cleanup_replacing_features[0].active_vdevice_device_mapping
374
- cleanup_feature_controller = FeatureController.get_for(cleanup_replacing_features[0].__class__)
416
+ _, mapped_setup_device = cur_setup_feature_obj.active_vdevice_device_mapping
417
+ cleanup_feature_controller = FeatureController.get_for(cur_setup_feature_obj.__class__)
375
418
  if mapped_setup_device is None \
376
419
  and len(cleanup_feature_controller.get_abs_inner_vdevice_classes()) > 0:
377
420
  # there is no vDevice mapping on scenario and no vDevice mapping on setup level, but the
378
421
  # feature defined vDevices -> NOT APPLICABLE
379
422
  logger.warning(
380
423
  f"missing vDevice mapping for feature "
381
- f"`{cur_abstract_scenario_feature_obj.__class__.__name__}` (used in scenario device "
424
+ f"`{cur_scenario_feature_obj.__class__.__name__}` (used in scenario device "
382
425
  f"`{cur_scenario_device.__name__}` and in setup device `{cur_setup_device.__name__}`) - "
383
426
  f"VARIATION CAN NOT BE APPLIED")
384
427
  raise NotApplicableVariationException(
385
- f'this variation can not be applicable because there was no vDevice mapping given on '
428
+ f'this variation can not be applied because there was no vDevice mapping given on '
386
429
  f'scenario or on setup level for the feature '
387
- f'`{cur_abstract_scenario_feature_obj.__class__.__name__}` (used by scenario device '
430
+ f'`{cur_scenario_feature_obj.__class__.__name__}` (used by scenario device '
388
431
  f'`{cur_scenario_device.__name__}`) in setup device `{cur_setup_device.__name__}`')
389
432
 
390
- all_assigned_setup_features.append(cleanup_replacing_features[0])
433
+ all_assigned_setup_features.append(cur_setup_feature_obj)
391
434
  if cur_attr_name not in feature_replacement[cur_scenario_device].keys():
392
- cleanup_feature = cleanup_replacing_features[0]
393
- cleanup_feature_controller = FeatureController.get_for(cleanup_feature.__class__)
435
+ cleanup_feature_controller = FeatureController.get_for(cur_setup_feature_obj.__class__)
394
436
 
395
- used_setup_vdevice, mapped_setup_device = cleanup_feature.active_vdevice_device_mapping
437
+ used_setup_vdevice, mapped_setup_device = cur_setup_feature_obj.active_vdevice_device_mapping
396
438
 
397
439
  # if there is a vDevice mapping on scenario level, but not on setup level, so update the
398
440
  # VDevice-Device-Mapping there
@@ -401,21 +443,21 @@ class VariationExecutor(BasicExecutor):
401
443
  # because check was already done in collector-stage)
402
444
  setup_vdevices = [cur_vdevice for cur_vdevice
403
445
  in cleanup_feature_controller.get_abs_inner_vdevice_classes()
404
- if cur_vdevice.__name__ == used_scenario_vdevice.__name__]
446
+ if cur_vdevice.__name__ == active_scenario_vdevice.__name__]
405
447
  used_setup_vdevice = setup_vdevices[0]
406
448
  # set the mapping
407
- abs_setup_vdevice_mappings[cur_setup_device][cleanup_feature] = {
449
+ abs_setup_vdevice_mappings[cur_setup_device][cur_setup_feature_obj] = {
408
450
  used_setup_vdevice: self.get_setup_device_for(mapped_scenario_device)}
409
451
  # if there is a vDevice mapping on setup level, but not on scenario level, so directly update the
410
452
  # VDevice-Device-Mapping there
411
453
  elif mapped_scenario_device is None and mapped_setup_device is not None:
412
- abs_setup_vdevice_mappings[cur_setup_device][cleanup_feature] = {
454
+ abs_setup_vdevice_mappings[cur_setup_device][cur_setup_feature_obj] = {
413
455
  used_setup_vdevice: mapped_setup_device}
414
456
 
415
457
  feature_replacement[cur_scenario_device][cur_attr_name] = \
416
- (cur_abstract_scenario_feature_obj, cleanup_feature)
458
+ (cur_scenario_feature_obj, cur_setup_feature_obj)
417
459
  # also add all setup features that are not assigned as autonomous features
418
- for _, cur_setup_feature in cur_setup_features.items():
460
+ for cur_setup_feature in cur_setup_features.values():
419
461
  if cur_setup_feature not in all_assigned_setup_features:
420
462
  # determine free name
421
463
  idx = 0
@@ -461,7 +503,7 @@ class VariationExecutor(BasicExecutor):
461
503
  raise KeyError("the requested setup device exists more than one time in `base_device_mapping`")
462
504
  return [cur_key for cur_key, cur_value in self.base_device_mapping.items() if cur_value == setup_device][0]
463
505
 
464
- def get_executor_for_testcase(self, testcase: callable) -> Union[TestcaseExecutor, None]:
506
+ def get_executor_for_testcase(self, testcase: callable) -> TestcaseExecutor | None:
465
507
  """
466
508
  This method searches for a TestcaseExecutor in the internal list for which the given testcase method is
467
509
  contained in
@@ -667,31 +709,16 @@ class VariationExecutor(BasicExecutor):
667
709
 
668
710
  def determine_absolute_scenario_device_connections(self):
669
711
  """
670
- This method determines the variation absolute connections for this variation and sets them to the internal
671
- properties `_abs_variation_*_device_connections`. This will be used to determine the real connection-subtree
712
+ This method determines the absolute connections for this variation and sets the internal properties
713
+ `_abs_variation_*_device_connections` with them. This will be used to determine the real connection-subtree
672
714
  (that can be used for this variation) by the method `create_all_valid_routings()`.
673
715
 
674
716
  The method re-executes the algorithm to determine the absolute connections for a scenario/setup (see the method
675
717
  :meth:`Collector.determine_absolute_device_connections_for`), but it considers the real applied vDevice and
676
718
  their feature restrictions too.
677
719
  """
678
- abs_var_scenario_device_cnns = {}
679
-
680
720
  # first determine all relevant absolute connection depending on the current scenario
681
- for cur_scenario_device, _ in self.base_device_mapping.items():
682
- cur_scenario_device_abs_cnns = \
683
- DeviceController.get_for(cur_scenario_device).get_all_absolute_connections()
684
- for _, cur_cnn_list in cur_scenario_device_abs_cnns.items():
685
- for cur_cnn in cur_cnn_list:
686
- if cur_scenario_device not in abs_var_scenario_device_cnns.keys():
687
- abs_var_scenario_device_cnns[cur_scenario_device] = {}
688
-
689
- cur_to_device, _ = cur_cnn.get_conn_partner_of(cur_scenario_device)
690
-
691
- if cur_to_device not in abs_var_scenario_device_cnns[cur_scenario_device].keys():
692
- abs_var_scenario_device_cnns[cur_scenario_device][cur_to_device] = []
693
-
694
- abs_var_scenario_device_cnns[cur_scenario_device][cur_to_device].append(cur_cnn)
721
+ abs_var_scenario_device_cnns = self.cur_scenario_controller.get_all_abs_connections()
695
722
 
696
723
  # now iterate over every feature, that is used by the scenario and determine the class-based feature connections
697
724
  # of the mapped scenario feature (and its vDevice)
@@ -714,31 +741,34 @@ class VariationExecutor(BasicExecutor):
714
741
 
715
742
  # get relevant class based connections for the current feature on setup level (this is really be used
716
743
  # here)
717
- feature_cnns = \
744
+ feature_cnn = \
718
745
  FeatureController.get_for(
719
746
  cur_setup_feature.__class__).get_abs_class_based_for_vdevice()[cur_setup_feature_vdevice]
720
747
  # connection that are relevant for this feature
721
- relevant_cnns = abs_var_scenario_device_cnns[cur_scenario_device][cur_mapped_scenario_device]
748
+ relevant_cnns = [
749
+ cnn for cnn in abs_var_scenario_device_cnns
750
+ if cnn.has_connection_from_to(cur_scenario_device, end_device=cur_mapped_scenario_device)
751
+ ]
722
752
 
723
753
  relevant_device_cnn = None
724
754
 
725
755
  if len(relevant_cnns) > 1:
726
756
  # we have parallel possibilities -> determine the selected one (only one is allowed to fit)
727
757
  for cur_relevant_cnn in relevant_cnns:
728
- for cur_relevant_single_cnn in cur_relevant_cnn.get_singles():
729
- for cur_feature_cnn in feature_cnns:
730
- for cur_feature_single_cnn in cur_feature_cnn.get_singles():
731
- if cur_feature_single_cnn.contained_in(cur_relevant_single_cnn):
732
- if relevant_device_cnn is not None:
733
- raise UnclearAssignableFeatureConnectionError(
734
- f"the devices {cur_scenario_device.__name__} and "
735
- f"{cur_mapped_scenario_device.__name__} have multiple parallel "
736
- f"connections - the device `{cur_scenario_device.__name__}` uses a "
737
- f"feature `{cur_scenario_feature.__class__.__name__}` that matches "
738
- f"with the device `{cur_mapped_scenario_device.__name__}`, but it is "
739
- f"not clear which of the parallel connection could be used"
740
- )
741
- relevant_device_cnn = cur_relevant_cnn
758
+ for cur_relevant_single_cnn, cur_feature_single_cnn in (
759
+ itertools.product(cur_relevant_cnn.get_singles(), feature_cnn.get_singles())):
760
+ if not cur_feature_single_cnn.contained_in(cur_relevant_single_cnn, ignore_metadata=True):
761
+ continue
762
+ if relevant_device_cnn is not None:
763
+ raise UnclearAssignableFeatureConnectionError(
764
+ f"the devices {cur_scenario_device.__name__} and "
765
+ f"{cur_mapped_scenario_device.__name__} have multiple parallel connections - the "
766
+ f"device `{cur_scenario_device.__name__}` uses a feature "
767
+ f"`{cur_scenario_feature.__class__.__name__}` that matches with the device "
768
+ f"`{cur_mapped_scenario_device.__name__}`, but it is not clear which of the "
769
+ f"parallel connection could be used"
770
+ )
771
+ relevant_device_cnn = cur_relevant_cnn
742
772
  elif len(relevant_cnns) == 1:
743
773
  relevant_device_cnn = relevant_cnns[0]
744
774
  if relevant_device_cnn is None:
@@ -747,36 +777,19 @@ class VariationExecutor(BasicExecutor):
747
777
 
748
778
  # now cleanup the scenario-device connection `relevant_device_cnn` according to the class-based feature
749
779
  # connection
750
- new_cleaned_singles = []
751
- for cur_old_cnn_single in relevant_device_cnn.get_singles():
752
- for cur_feature_cnn in feature_cnns:
753
- if cur_feature_cnn.contained_in(cur_old_cnn_single, ignore_metadata=True):
754
- new_cleaned_singles.append(cur_old_cnn_single)
755
-
756
- new_cnn_to_replace = Connection.based_on(*new_cleaned_singles)
757
- new_cnn_to_replace.set_metadata_for_all_subitems(new_cleaned_singles[0].metadata)
758
-
759
- abs_var_scenario_device_cnns[cur_scenario_device][cur_mapped_scenario_device].remove(
760
- relevant_device_cnn)
761
- abs_var_scenario_device_cnns[cur_scenario_device][cur_mapped_scenario_device].append(new_cnn_to_replace)
762
-
763
- # also search the connection in the other direction
764
- other_dir_relevant_device_cnn = None
765
- for cur_cnn in abs_var_scenario_device_cnns[cur_mapped_scenario_device][cur_scenario_device]:
766
- if cur_cnn.equal_with(relevant_device_cnn):
767
- other_dir_relevant_device_cnn = cur_cnn
768
- break
769
- # and also replace it
770
- abs_var_scenario_device_cnns[cur_mapped_scenario_device][cur_scenario_device].remove(
771
- other_dir_relevant_device_cnn)
772
- abs_var_scenario_device_cnns[cur_mapped_scenario_device][cur_scenario_device].append(new_cnn_to_replace)
780
+ new_cnn_to_replace = Connection.based_on(OrConnectionRelation(*[
781
+ cur_old_cnn_single for cur_old_cnn_single in relevant_device_cnn.get_singles()
782
+ if feature_cnn.contained_in(cur_old_cnn_single, ignore_metadata=True)
783
+ ]))
784
+ new_cnn_to_replace.set_metadata_for_all_subitems(relevant_device_cnn.metadata)
785
+
786
+ abs_var_scenario_device_cnns.remove(relevant_device_cnn)
787
+ abs_var_scenario_device_cnns.append(new_cnn_to_replace)
788
+
789
+ # we do not need to check other direction because `has_connection_from_to()` returns both possibilities
773
790
 
774
791
  # set the determined values in variation object
775
- self._abs_variation_scenario_device_connections = []
776
- for _, from_device_dict in abs_var_scenario_device_cnns.items():
777
- for _, cur_cnn_list in from_device_dict.items():
778
- for cur_cnn in cur_cnn_list:
779
- self._abs_variation_scenario_device_connections.append(cur_cnn)
792
+ self._abs_variation_scenario_device_connections = abs_var_scenario_device_cnns
780
793
 
781
794
  def create_all_valid_routings(self):
782
795
  """
@@ -816,16 +829,18 @@ class VariationExecutor(BasicExecutor):
816
829
  if virtual_routing_cnns[cur_cnn] is None:
817
830
  virtual_routing_cnns[cur_cnn] = Connection.based_on(virtual_cnn)
818
831
  else:
819
- virtual_routing_cnns[cur_cnn] = Connection.based_on(virtual_routing_cnns[cur_cnn], virtual_cnn)
832
+ virtual_routing_cnns[cur_cnn] = Connection.based_on(
833
+ OrConnectionRelation(virtual_routing_cnns[cur_cnn], virtual_cnn))
820
834
  virtual_routing_cnns[cur_cnn].set_metadata_for_all_subitems(virtual_cnn.metadata)
821
835
 
822
836
  self._abs_variation_connections = []
823
837
  for cur_cnn in self._abs_variation_scenario_device_connections:
824
838
  cur_virtual_cnn = virtual_routing_cnns[cur_cnn]
825
839
  new_intersection = cur_cnn.intersection_with(cur_virtual_cnn)
826
- new_intersection.set_metadata_for_all_subitems(None)
827
- # always set the metadata for setup devices
828
- new_intersection.set_metadata_for_all_subitems(cur_virtual_cnn.metadata)
840
+ if new_intersection:
841
+ new_intersection.set_metadata_for_all_subitems(None)
842
+ # always set the metadata for setup devices
843
+ new_intersection.set_metadata_for_all_subitems(cur_virtual_cnn.metadata)
829
844
  self._abs_variation_connections.append(new_intersection)
830
845
 
831
846
  def set_conn_dependent_methods(self):
@@ -861,11 +876,15 @@ class VariationExecutor(BasicExecutor):
861
876
  relevant_abs_conn = []
862
877
  for cur_cnn in self._abs_variation_connections:
863
878
  if cur_cnn.has_connection_from_to(start_device=setup_device, end_device=mapped_setup_device):
864
- relevant_abs_conn.append(cur_cnn)
879
+ # add the children
880
+ relevant_abs_conn.extend(
881
+ cur_cnn.based_on_elements.connections if cur_cnn.__class__ == Connection else [cur_cnn]
882
+ )
883
+
865
884
  if len(relevant_abs_conn) is None:
866
885
  raise RuntimeError(f"detect empty absolute connection between device `{setup_device.__name__}` "
867
886
  f"and device `{mapped_setup_device.__name__}`")
868
- absolute_feature_method_var_cnn = Connection.based_on(*relevant_abs_conn)
887
+ absolute_feature_method_var_cnn = Connection.based_on(OrConnectionRelation(*relevant_abs_conn))
869
888
  cur_method_variation = cur_setup_feature_controller.get_method_variation(
870
889
  of_method_name=cur_method_name, for_vdevice=mapped_vdevice,
871
890
  with_connection=absolute_feature_method_var_cnn)
@@ -876,3 +895,9 @@ class VariationExecutor(BasicExecutor):
876
895
  (mapped_vdevice, absolute_feature_method_var_cnn, cur_method_variation)
877
896
 
878
897
  cur_setup_feature_controller.set_active_method_variation(method_selection=method_var_selection)
898
+
899
+ def resolve_possible_parametrization(self):
900
+ """resolves the parametrization if there are any :class:`UnresolvedParametrizedTestcaseExecutor` in the tree"""
901
+ for cur_child in self._testcase_executors:
902
+ if isinstance(cur_child, UnresolvedParametrizedTestcaseExecutor):
903
+ cur_child.resolve_dynamic_parametrization()
_balder/feature.py CHANGED
@@ -16,7 +16,7 @@ class Feature:
16
16
  :param kwargs: contains a dictionary that references all vDevices of the feature and a real
17
17
  scenario :meth:`Device` as value
18
18
  """
19
- from _balder.controllers import FeatureController
19
+ from _balder.controllers import FeatureController # pylint: disable=import-outside-toplevel
20
20
 
21
21
  #: this property contains the active mapping for the devices
22
22
  self.active_vdevices: Dict[VDevice, Union[Device, str]] = {}
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+ from typing import List
3
+ from enum import Enum
4
+
5
+
6
+ class FixtureDefinitionScope(Enum):
7
+ """
8
+ This enum describes the definition scope of a fixture. A definition scope is the position the fixture was defined.
9
+ If the fixture was defined within the balderglob.py file, it has the definition-scope `GLOB`. If it is defined
10
+ within a scenario or setup it has the equivalent SCENARIO or SETUP scope.
11
+ """
12
+ GLOB = 'glob'
13
+ SETUP = 'setup'
14
+ SCENARIO = 'scenario'
15
+
16
+ @classmethod
17
+ def get_order(cls) -> List[FixtureDefinitionScope]:
18
+ """returns the priority order of the fixture definition scope"""
19
+ return [cls.GLOB, cls.SETUP, cls.SCENARIO]
@@ -0,0 +1,22 @@
1
+ from __future__ import annotations
2
+ from typing import List
3
+ from enum import Enum
4
+
5
+
6
+ class FixtureExecutionLevel(Enum):
7
+ """
8
+ This enum describes the fixture-execution-level of a fixture. It describes when the fixture should be executed. This
9
+ level will be set in the fixture decorator.
10
+ """
11
+ SESSION = 'session'
12
+ SETUP = 'setup'
13
+ SCENARIO = 'scenario'
14
+ VARIATION = 'variation'
15
+ TESTCASE = 'testcase'
16
+
17
+ @classmethod
18
+ def get_order(cls) -> List[FixtureExecutionLevel]:
19
+ """
20
+ returns the execution order of fixtures
21
+ """
22
+ return [cls.SESSION, cls.SETUP, cls.SCENARIO, cls.VARIATION, cls.TESTCASE]