Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions src/_balder/controllers/device_controller.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from __future__ import annotations

import copy
from typing import Dict, List, Type, Union, TYPE_CHECKING

import sys
Expand Down Expand Up @@ -443,16 +445,21 @@ def resolve_mapped_vdevice_strings(self):
if all_instanced_features is None:
# has no features -> skip
return
for _, cur_feature in all_instanced_features.items():
if cur_feature.active_vdevices != {}:
for cur_attr_name, cur_feature in all_instanced_features.items():
# clone feature and its active_device dict to make sure that shared instances in parent classes are handled
# correctly
new_feature = copy.copy(cur_feature)
new_feature.active_vdevices = {**cur_feature.active_vdevices}
setattr(self.related_cls, cur_attr_name, new_feature)
if new_feature.active_vdevices != {}:
# do something only if there exists an internal mapping
for cur_mapped_vdevice, cur_mapped_device in cur_feature.active_vdevices.items():
for cur_mapped_vdevice, cur_mapped_device in new_feature.active_vdevices.items():
if isinstance(cur_mapped_device, str):
resolved_device = \
scenario_or_setup_controller.get_inner_device_class_by_string(cur_mapped_device)
if resolved_device is None:
raise RuntimeError(
f"found no possible matching name while trying to resolve "
f"the given vDevice string `{cur_mapped_vdevice}` in feature "
f"`{cur_feature.__class__.__name__}`")
cur_feature.active_vdevices[cur_mapped_vdevice] = resolved_device
f"`{new_feature.__class__.__name__}`")
new_feature.active_vdevices[cur_mapped_vdevice] = resolved_device
8 changes: 4 additions & 4 deletions src/_balder/executor/variation_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,9 +391,9 @@ class and sets this information to the property `_feature_replacement`. In addit
cur_setup_features = DeviceController.get_for(cur_setup_device).get_all_instantiated_feature_objects()

all_assigned_setup_features = []
cur_scenario_device_orig_features = \
DeviceController.get_for(cur_scenario_device).get_original_instanced_feature_objects()
for cur_attr_name, cur_scenario_feature_obj in cur_scenario_device_orig_features.items():
cur_scenario_device_features = \
DeviceController.get_for(cur_scenario_device).get_all_instantiated_feature_objects()
for cur_attr_name, cur_scenario_feature_obj in cur_scenario_device_features.items():
active_scenario_vdevice, mapped_scenario_device = cur_scenario_feature_obj.active_vdevice_device_mapping

cur_setup_feature_objs = self._get_matching_setup_features_for(
Expand Down Expand Up @@ -677,7 +677,7 @@ def update_vdevice_referenced_feature_instances(self):
cur_vdevice, cur_device = cur_feature.active_vdevice_device_mapping
if cur_vdevice is not None and cur_device is not None:
cur_vdevice_controller = VDeviceController.get_for(cur_vdevice)
cur_vdevice_all_features = cur_vdevice_controller.get_original_instanced_feature_objects()
cur_vdevice_all_features = cur_vdevice_controller.get_all_instantiated_feature_objects()

cur_device_controller = DeviceController.get_for(cur_device)
cur_device_all_features = cur_device_controller.get_all_instantiated_feature_objects()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
from typing import Literal, Union, List
from datetime import datetime

import pathlib
import balder
import argparse
import logging
from multiprocessing import Queue
from .lib.utils import FixtureReturn

logger = logging.getLogger(__file__)


class MyTestException(Exception):
pass


class RuntimeObserver:
"""This is a helper object, that will be used from this test environment to observe the execution order"""
queue: Union[Queue, None] = None

error_throwing = {}

@staticmethod
def add_entry(file: Union[str, pathlib.Path], cls: object, meth: callable, msg: str,
category: Literal["fixture", "testcase", "feature"] = None,
part: Literal["construction", "teardown"] = None):
"""
adds a new entry and sends it over the queue

:param file: the full filepath where the log will be generated

:param cls: the class object, the entry is generated in

:param meth: the method name, the entry is generated in

:param msg: the message that should be inserted into the entry

:param category: optional string of the category the entry is from

:param part: optional string of the sub part the entry is from
"""
if hasattr(meth, 'fn'):
meth = meth.fn
new_dataset = {
"timestamp": datetime.now(), "file": file, "cls": "" if cls is None else cls.__name__,
"meth": meth.__name__, "msg": msg, "category": category, "part": part
}
logger.info("{:22} | {:20} | {:30} | {:12} | {:15} | {}".format(
pathlib.Path(file).parts[-1], "" if cls is None else cls.__name__, "" if meth is None else meth.__name__,
"" if category is None else category, "" if part is None else part, "" if msg is None else msg))

RuntimeObserver.queue.put(new_dataset)
# check if we have to throw the error
error_throwing_required = len(RuntimeObserver.error_throwing) > 0
for cur_key in RuntimeObserver.error_throwing.keys():
new_dataset_val = new_dataset[cur_key]
if callable(new_dataset_val):
new_dataset_val = new_dataset_val.__name__
if new_dataset_val != RuntimeObserver.error_throwing[cur_key]:
error_throwing_required = False
break
if error_throwing_required:
raise MyTestException(f'raise test triggered exception for `{str(RuntimeObserver.error_throwing)}`')


class MyErrorThrowingPlugin(balder.BalderPlugin):
"""
This is a plugin that reads the values from console arguments and sets these values into the
:class:`RuntimeObserver`. The static method `RuntimeObserver.add_entry` will automatically throw an exception on the
given position.
"""

def addoption(self, argument_parser: argparse.ArgumentParser):
argument_parser.add_argument('--test-error-file', help='the file id, the error should be thrown in')
argument_parser.add_argument('--test-error-cls', help='the class id, the error should be thrown in')
argument_parser.add_argument('--test-error-meth', help='the meth id, the error should be thrown in')
argument_parser.add_argument('--test-error-part', help='the part (`construct` or `teardown`), the error should '
'be thrown in - only for fixtures')

def modify_collected_pyfiles(self, pyfiles: List[pathlib.Path]) -> List[pathlib.Path]:
# use this method to set the values
RuntimeObserver.error_throwing = {}
if self.balder_session.parsed_args.test_error_file:
path = pathlib.Path(self.balder_session.parsed_args.test_error_file)
if not path.is_absolute():
path = str(self.balder_session.working_dir.joinpath(path))
RuntimeObserver.error_throwing['file'] = path
if self.balder_session.parsed_args.test_error_cls:
RuntimeObserver.error_throwing['cls'] = self.balder_session.parsed_args.test_error_cls
if self.balder_session.parsed_args.test_error_meth:
RuntimeObserver.error_throwing['meth'] = self.balder_session.parsed_args.test_error_meth
if self.balder_session.parsed_args.test_error_part:
RuntimeObserver.error_throwing['part'] = self.balder_session.parsed_args.test_error_part
return pyfiles


@balder.fixture(level="session")
def balderglob_fixture_session():
RuntimeObserver.add_entry(__file__, None, balderglob_fixture_session, "begin execution CONSTRUCTION of fixture",
category="fixture", part="construction")

yield FixtureReturn.BALDERGLOB_SESSION

RuntimeObserver.add_entry(__file__, None, balderglob_fixture_session, "begin execution TEARDOWN of fixture",
category="fixture", part="teardown")


@balder.fixture(level="setup")
def balderglob_fixture_setup():
RuntimeObserver.add_entry(__file__, None, balderglob_fixture_setup, "begin execution CONSTRUCTION of fixture",
category="fixture", part="construction")

yield FixtureReturn.BALDERGLOB_SETUP

RuntimeObserver.add_entry(__file__, None, balderglob_fixture_setup, "begin execution TEARDOWN of fixture",
category="fixture", part="teardown")


@balder.fixture(level="scenario")
def balderglob_fixture_scenario():
RuntimeObserver.add_entry(__file__, None, balderglob_fixture_scenario, "begin execution CONSTRUCTION of fixture",
category="fixture", part="construction")

yield FixtureReturn.BALDERGLOB_SCENARIO

RuntimeObserver.add_entry(__file__, None, balderglob_fixture_scenario, "begin execution TEARDOWN of fixture",
category="fixture", part="teardown")


@balder.fixture(level="variation")
def balderglob_fixture_variation():
RuntimeObserver.add_entry(__file__, None, balderglob_fixture_variation, "begin execution CONSTRUCTION of fixture",
category="fixture", part="construction")

yield FixtureReturn.BALDERGLOB_VARIATION

RuntimeObserver.add_entry(__file__, None, balderglob_fixture_variation, "begin execution TEARDOWN of fixture",
category="fixture", part="teardown")


@balder.fixture(level="testcase")
def balderglob_fixture_testcase():
RuntimeObserver.add_entry(__file__, None, balderglob_fixture_testcase, "begin execution CONSTRUCTION of fixture",
category="fixture", part="construction")

yield FixtureReturn.BALDERGLOB_TESTCASE

RuntimeObserver.add_entry(__file__, None, balderglob_fixture_testcase, "begin execution TEARDOWN of fixture",
category="fixture", part="teardown")
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import balder


@balder.insert_into_tree()
class AConnection(balder.Connection):
pass


@balder.insert_into_tree()
class BConnection(balder.Connection):
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import balder
from ..balderglob import RuntimeObserver


class FeatureI(balder.Feature):

def do_something(self):
RuntimeObserver.add_entry(
__file__, FeatureI, FeatureI.do_something, "enter `FeatureI.do_something`", category="feature")


class FeatureIOverwritten(FeatureI):

def do_something(self):
RuntimeObserver.add_entry(
__file__, FeatureIOverwritten, FeatureIOverwritten.do_something, "enter `FeatureIChild.do_something`",
category="feature")


class FeatureII(balder.Feature):

class Dev1(balder.VDevice):
feat = FeatureI()

def do_something(self):
RuntimeObserver.add_entry(
__file__, FeatureII, FeatureII.do_something, "enter `FeatureII.do_something`", category="feature")


class NewlyDefinedFeature(balder.Feature):
"""this feature will be instantiated in `ScenarioAChild` (and not in `ScenarioAParent`)"""
def do_something(self):
RuntimeObserver.add_entry(
__file__, NewlyDefinedFeature, NewlyDefinedFeature.do_something, "enter `NewlyDefinedFeature.do_something`",
category="feature")


class FeatureIII(balder.Feature):

def do_something(self):
RuntimeObserver.add_entry(
__file__, FeatureIII, FeatureIII.do_something, "enter `FeatureIII.do_something`", category="feature")


class FeatureIV(balder.Feature):

def do_something(self):
RuntimeObserver.add_entry(
__file__, FeatureIV, FeatureIV.do_something, "enter `FeatureIV.do_something`", category="feature")
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@


class FixtureReturn:
"""helper const class for return values"""
BALDERGLOB_SESSION = "balderglob_session_fixt"
BALDERGLOB_SETUP = "balderglob_setup_fixt"
BALDERGLOB_SCENARIO = "balderglob_scenario_fixt"
BALDERGLOB_VARIATION = "balderglob_variation_fixt"
BALDERGLOB_TESTCASE = "balderglob_testcase_fixt"

SETUP_SESSION = "setup_session_fixt"
SETUP_SETUP = "setup_setup_fixt"
SETUP_SCENARIO = "setup_scenario_fixt"
SETUP_VARIATION = "setup_variation_fixt"
SETUP_TESTCASE = "setup_testcase_fixt"

SCENARIO_SESSION = "scenario_session_fixt"
SCENARIO_SETUP = "scenario_setup_fixt"
SCENARIO_SCENARIO = "scenario_scenario_fixt"
SCENARIO_VARIATION = "scenario_variation_fixt"
SCENARIO_TESTCASE = "scenario_testcase_fixt"

Loading