diff --git a/riotctrl/ctrl.py b/riotctrl/ctrl.py index aa51b27..dc5dea1 100644 --- a/riotctrl/ctrl.py +++ b/riotctrl/ctrl.py @@ -3,6 +3,7 @@ Define class to abstract a node over the RIOT build system. """ +import abc import os import time import logging @@ -188,3 +189,67 @@ def make_command(self, targets): command.extend(dir_cmd) command.extend(targets) return command + + +class RIOTCtrlFactoryBase(abc.ABC): + # pylint: disable=too-few-public-methods + # A factory usually does not have more methods than one. + """Abstract factory to create different RIOTCtrl.""" + + @abc.abstractmethod + def get_ctrl(self, application_directory='.', env=None): + """ + Returns a RIOTCtrl object of a class specified by the Factory + + :param application_directory: `application_directory` initialization + parameter for the RIOTCtrl object + :param env: `env` initialization parameter for + the RIOTCtrl object. + """ + raise NotImplementedError + + +class RIOTCtrlBoardFactory(RIOTCtrlFactoryBase): + # pylint: disable=too-few-public-methods + # A factory usually does not have more methods than one. + """Factory mixin to create different RIOTCtrl types based on + the BOARD environment variable. + + :param board_cls: A dict that maps the `BOARD` environment variable to a + RIOTCtrl class. + """ + DEFAULT_CLS = RIOTCtrl + BOARD_CLS = {} + + def __init__(self, board_cls=None): + self.board_cls = {} + self.board_cls.update(self.BOARD_CLS) + if board_cls is not None: + self.board_cls.update(board_cls) + + def get_ctrl(self, application_directory='.', env=None): + """ + Returns a RIOTCtrl object of a class as specified in `board_cls` on + initialization. + + :param application_directory: `application_directory` initialization + parameter for the RIOTCtrl object + :param env: `env` initialization parameter for + the RIOTCtrl object. This will also be + used to determine the actual class of + the return value. + + When `BOARD` is set in the environment variables when `env` is provided + in `env`, that value is used to look-up the RIOTCtrl class in the + factory's `board_cls` for that specific `BOARD` value. + """ + the_env = {} + the_env.update(os.environ) + if env: + the_env.update(env) + if 'BOARD' not in the_env or the_env['BOARD'] not in self.board_cls: + cls = self.DEFAULT_CLS + else: + cls = self.board_cls[the_env['BOARD']] + # cls does its own fetching of `os.environ` so only provide `env` here + return cls(application_directory=application_directory, env=env) diff --git a/riotctrl/tests/ctrl_test.py b/riotctrl/tests/ctrl_test.py index d7abc65..57a8162 100644 --- a/riotctrl/tests/ctrl_test.py +++ b/riotctrl/tests/ctrl_test.py @@ -208,3 +208,64 @@ def test_term_cleanup(app_pidfile_env): # File should not exist anymore so no error to create one # File must exist to be cleaned by tempfile open(tmpfile.name, 'x') + + +class CtrlMock1(riotctrl.ctrl.RIOTCtrl): + """Mock to test RIOTCtrlFactoryBase descendents""" + + +class CtrlMock2(riotctrl.ctrl.RIOTCtrl): + """Another mock to test RIOTCtrlFactoryBase descendents""" + + +def test_board_factory_wo_board(): + """Tests if riotctrl.ctrl.RIOTCtrlBoardFactory defaults to + riotctrl.ctrl.RIOTCtrl if no mapping exists for a device""" + factory = riotctrl.ctrl.RIOTCtrlBoardFactory() + assert factory.DEFAULT_CLS is riotctrl.ctrl.RIOTCtrl + ctrl = factory.get_ctrl() + # pylint: disable=unidiomatic-typecheck + # in this case we want to know the exact type + assert type(ctrl) is riotctrl.ctrl.RIOTCtrl + + +def test_w_board_in_default_board_cls(): + """Tests if riotctrl.ctrl.RIOTCtrlBoardFactory returns a class in the + static mapping of the factory exists""" + env = {'BOARD': 'mock'} + riotctrl.ctrl.RIOTCtrlBoardFactory.BOARD_CLS = {'mock': CtrlMock1} + factory = riotctrl.ctrl.RIOTCtrlBoardFactory() + assert 'mock' in factory.board_cls + ctrl = factory.get_ctrl(env=env) + # pylint: disable=unidiomatic-typecheck + # in this case we want to know the exact type + assert type(ctrl) is CtrlMock1 + + +def test_w_board_in_not_default_board_cls(): + """Tests if riotctrl.ctrl.RIOTCtrlBoardFactory defaults to + riotctrl.ctrl.RIOTCtrl if no mapping exists for a device with an existing + mapping""" + env = {'BOARD': 'foobar'} + riotctrl.ctrl.RIOTCtrlBoardFactory.BOARD_CLS = {'mock': CtrlMock1} + factory = riotctrl.ctrl.RIOTCtrlBoardFactory() + assert 'mock' in factory.board_cls + ctrl = factory.get_ctrl(env=env) + # pylint: disable=unidiomatic-typecheck + # in this case we want to know the exact type + assert type(ctrl) is riotctrl.ctrl.RIOTCtrl + + +def test_w_board_custom_board_cls(): + """Tests if riotctrl.ctrl.RIOTCtrlBoardFactory returns a class in the + dynamic mapping of the factory exists""" + env = {'BOARD': 'mock'} + riotctrl.ctrl.RIOTCtrlBoardFactory.BOARD_CLS = {'mock': CtrlMock1} + factory = riotctrl.ctrl.RIOTCtrlBoardFactory( + board_cls={'mock': CtrlMock2} + ) + assert 'mock' in factory.board_cls + ctrl = factory.get_ctrl(env=env) + # pylint: disable=unidiomatic-typecheck + # in this case we want to know the exact type + assert type(ctrl) is CtrlMock2