Пример #1
0
    def setUp(self, machine_path):
        """Set up fuzzer."""
        self.loop = TimeTravelLoop()
        self.loop.set_exception_handler(self._exception_handler)
        self.clock = TestClock(self.loop)

        self.machine = TestMachineController(
            os.path.abspath(os.path.join(
                mpf.core.__path__[0], os.pardir)), machine_path,
            self.get_options(), self.machine_config_patches, self.machine_config_defaults, self.clock, dict(),
            True)

        try:
            self.loop.run_until_complete(self.machine.initialise())
        except RuntimeError as e:
            try:
                self.machine.stop()
            # pylint: disable-msg=broad-except
            except Exception:
                pass
            if self._exception and "exception" in self._exception:
                raise self._exception['exception']
            elif self._exception:
                raise Exception(self._exception, e)
            raise e

        self.machine.events.process_event_queue()
        self.advance_time_and_run(1)

        self.switch_list = sorted(self.machine.switches.keys())

        for switch in self.machine.switches:
            self.machine.switch_controller.process_switch_obj(switch, 0, True)
Пример #2
0
    def setUp(self):
        """Setup test."""
        self._get_event_loop = asyncio.get_event_loop
        asyncio.get_event_loop = None
        self._get_event_loop2 = asyncio.events.get_event_loop
        events.get_event_loop = None

        # we want to reuse config_specs to speed tests up
        mpf.core.config_validator.ConfigValidator.unload_config_spec = (
            MagicMock())

        self._events = {}
        self._last_event_kwargs = {}
        self._exception = None

        # print(threading.active_count())

        self.test_start_time = time.time()
        if self.unittest_verbosity() > 1:
            logging.basicConfig(level=logging.DEBUG,
                                format='%(asctime)s : %(levelname)s : %('
                                'name)s : %(message)s')
        else:
            # no logging by default
            logging.basicConfig(level=99)

        self.save_and_prepare_sys_path()

        # init machine
        machine_path = self.get_absolute_machine_path()

        self.loop = TimeTravelLoop()
        self.loop.set_exception_handler(self._exception_handler)
        self.clock = TestClock(self.loop)
        self._mock_loop()

        try:
            self.machine = TestMachineController(
                os.path.abspath(os.path.join(mpf.core.__path__[0],
                                             os.pardir)), machine_path,
                self.get_options(), self.machine_config_patches,
                self.machine_config_defaults, self.clock,
                self._get_mock_data(), self.get_enable_plugins(),
                self._early_machine_init)

            self._initialise_machine()

        # pylint: disable-msg=broad-except
        except Exception as e:
            try:
                self.machine.stop()
            except AttributeError:
                pass
            if self._exception and 'exception' in self._exception:
                raise self._exception['exception']
            elif self._exception:
                raise Exception(self._exception, e)
            raise e
Пример #3
0
    def setup(self, machine_path):
        """Set up fuzzer."""
        self.loop = TimeTravelLoop()
        self.loop.set_exception_handler(self._exception_handler)
        self.clock = TestClock(self.loop)

        config_loader = UnitTestConfigLoader(machine_path, ["config.yaml"],
                                             self.machine_config_defaults,
                                             self.machine_config_patches, {})

        config = config_loader.load_mpf_config()
        try:
            # remove virtual_platform_start_active_switches as it messes with out switch logic
            config.get_machine_config().pop(
                "virtual_platform_start_active_switches")
        except KeyError:
            pass

        self.machine = TestMachineController(self.get_options(), config,
                                             self.machine_config_patches,
                                             self.machine_config_defaults,
                                             self.clock, dict(), True)

        try:
            self.loop.run_until_complete(self.machine.initialise())
        except RuntimeError as e:
            try:
                self.machine.stop()
            # pylint: disable-msg=broad-except
            except Exception:
                pass
            if self._exception and "exception" in self._exception:
                raise self._exception['exception']
            elif self._exception:
                raise Exception(self._exception, e)
            raise e

        self.machine.events.process_event_queue()
        self.advance_time_and_run(1)

        self.switch_list = sorted(self.machine.switches.keys())

        for switch in self.machine.switches:
            self.machine.switch_controller.process_switch_obj(switch, 0, True)
    def setUp(self):
        # we want to reuse config_specs to speed tests up
        ConfigValidator.unload_config_spec = MagicMock()

        self._events = {}

        # print(threading.active_count())

        self.test_start_time = time.time()
        if self.unittest_verbosity() > 1:
            logging.basicConfig(level=logging.DEBUG,
                                format='%(asctime)s : %(levelname)s : %('
                                'name)s : %(message)s')
        else:
            # no logging by default
            logging.basicConfig(level=99)

        self.save_and_prepare_sys_path()

        # init machine
        machine_path = os.path.abspath(
            os.path.join(mpfmc.__path__[0], os.pardir, 'mpfmc',
                         self.getMachinePath()))

        try:
            self.loop = TimeTravelLoop()
            self.clock = TestClock(self.loop)
            # Note the 'True' for enabling plugins, change from base
            self.machine = TestMachineController(
                os.path.abspath(os.path.join(mpf.core.__path__[0],
                                             os.pardir)), machine_path,
                self.getOptions(), self.machine_config_patches, self.clock, {},
                True)

            while not self.machine.test_init_complete:
                self.advance_time_and_run(0.01)

            self.machine.ball_controller.num_balls_known = 99
            self.advance_time_and_run(300)

        except Exception as e:
            # todo temp until I can figure out how to stop the asset loader
            # thread automatically.
            try:
                self.machine.stop()
            except AttributeError:
                pass
            raise e

        # remove config patches
        self.machine_config_patches = dict()
        # use bcp mock
        self.machine_config_patches['bcp'] = \
            {"connections": {"local_display": {"type": "mpf.tests.MpfTestCase.MockBcpClient"}}}
Пример #5
0
    def setUp(self, machine_path):
        """Set up fuzzer."""
        self.loop = TimeTravelLoop()
        self.loop.set_exception_handler(self._exception_handler)
        self.clock = TestClock(self.loop)

        self.machine = TestMachineController(
            os.path.abspath(os.path.join(mpf.core.__path__[0],
                                         os.pardir)), machine_path,
            self.getOptions(), self.machine_config_patches,
            self.machine_config_defaults, self.clock, dict(), True)

        self.loop.run_until_complete(self.machine.initialise())

        self.machine.events.process_event_queue()
        self.advance_time_and_run(1)

        self.switch_list = sorted(self.machine.switches.keys())

        for switch in self.machine.switches:
            self.machine.switch_controller.process_switch_obj(switch, 0, True)
Пример #6
0
class AflRunner(object):
    """AFL fuzzer."""

    __slots__ = [
        "loop", "clock", "machine", "debug", "machine_config_patches",
        "machine_config_defaults", "switch_list", "use_virtual",
        "_invalid_input", "_exception"
    ]

    def __init__(self, use_virtual, debug):
        """Initialize fuzzer."""
        self.loop = None
        self.clock = None
        self.machine = None  # type: TestMachineController
        self.debug = debug
        self.machine_config_patches = dict()
        self.machine_config_patches['mpf'] = dict()
        self.machine_config_patches['mpf']['default_platform_hz'] = 1
        self.machine_config_patches['bcp'] = []
        self.machine_config_defaults = {}
        self.switch_list = []
        self.use_virtual = use_virtual
        self._invalid_input = False
        self._exception = None

    def _exception_handler(self, loop, context):
        try:
            loop.stop()
        except RuntimeError:
            pass

        self._exception = context

    def get_platform(self):
        """Return platform."""
        if self.use_virtual:
            return "virtual"

        return "smart_virtual"

    @staticmethod
    def get_config_file():
        """Return config file."""
        return "config.yaml"

    def get_options(self):
        """Return option arrays."""
        mpfconfig = os.path.abspath(
            os.path.join(mpf.core.__path__[0], os.pardir, 'mpfconfig.yaml'))

        return {
            'force_platform': self.get_platform(),
            'mpfconfigfile': mpfconfig,
            'configfile': Util.string_to_event_list(self.get_config_file()),
            'debug': False,
            'bcp': False,
            'no_load_cache': False,
            'create_config_cache': True,
            'text_ui': False,
            'production': not self.debug,
        }

    def advance_time_and_run(self, delta=1.0):
        """Advance time and run."""
        try:
            self.loop.run_until_complete(
                asyncio.sleep(delay=delta, loop=self.loop))
            return
        except RuntimeError as e:
            if self._exception and "exception" in self._exception:
                raise self._exception['exception']
            elif self._exception:
                raise Exception(self._exception, e)
            raise e

    def setup(self, machine_path):
        """Set up fuzzer."""
        self.loop = TimeTravelLoop()
        self.loop.set_exception_handler(self._exception_handler)
        self.clock = TestClock(self.loop)

        config_loader = UnitTestConfigLoader(machine_path, ["config.yaml"],
                                             self.machine_config_defaults,
                                             self.machine_config_patches, {})

        config = config_loader.load_mpf_config()
        try:
            # remove virtual_platform_start_active_switches as it messes with out switch logic
            config.get_machine_config().pop(
                "virtual_platform_start_active_switches")
        except KeyError:
            pass

        self.machine = TestMachineController(self.get_options(), config,
                                             self.machine_config_patches,
                                             self.machine_config_defaults,
                                             self.clock, dict(), True)

        try:
            self.loop.run_until_complete(self.machine.initialise())
        except RuntimeError as e:
            try:
                self.machine.stop()
            # pylint: disable-msg=broad-except
            except Exception:
                pass
            if self._exception and "exception" in self._exception:
                raise self._exception['exception']
            elif self._exception:
                raise Exception(self._exception, e)
            raise e

        self.machine.events.process_event_queue()
        self.advance_time_and_run(1)

        self.switch_list = sorted(self.machine.switches.keys())

        for switch in self.machine.switches:
            self.machine.switch_controller.process_switch_obj(switch, 0, True)

    def add_balls(self):
        """Add balls."""
        balls_to_add = 3
        for device in sorted(self.machine.ball_devices.values()):
            if "trough" in device.tags:
                for switch in device.ball_count_handler.counter.config.get(
                        'ball_switches', []):
                    self.machine.switch_controller.process_switch_obj(
                        switch, 1, True)
                    balls_to_add -= 1
                    if balls_to_add == 0:
                        break
                if balls_to_add == 0:
                    break
                if device.ball_count_handler.counter.config.get(
                        'entrance_switch'):
                    self.machine.switch_controller.process_switch_obj(
                        device.ball_count_handler.counter.
                        config['entrance_switch'], 1, True)
                    balls_to_add -= 1
                    if balls_to_add == 0:
                        break

        # let balls settle
        self.advance_time_and_run(10)

    def start_game(self):
        """Start game."""
        found_start_switch = False
        for switch in self.machine.switches:
            if "start" in switch.tags:
                found_start_switch = True
                self.machine.switch_controller.process_switch_obj(
                    switch, 1, True)
                self.machine.switch_controller.process_switch_obj(
                    switch, 0, True)

        if not found_start_switch:
            raise AssertionError(
                "Did not find any switch tagged with tag \"start\".")

    def _abort(self, **kwargs):
        """Abort fuzzer run."""
        del kwargs
        self._invalid_input = True
        if not self.debug:
            os._exit(0)  # NOQA

    def run(self, actions, find_logic_bugs):
        """Run fuzzer."""
        if find_logic_bugs:
            self.machine.events.add_handler("balldevice_ball_missing",
                                            self._abort)
            self.machine.events.add_handler("found_new_ball", self._abort)
            self.machine.events.add_handler("mode_game_stopped", self._abort)

        for action in actions:
            if self._invalid_input:
                # bail out if we hit an invalid input. afl will notice this
                return

            if action & 0b10000000:
                if action == 0b11111111:
                    if self.machine.game and \
                            self.machine.game.balls_in_play < self.machine.ball_controller.num_balls_known:
                        self.machine.playfield.add_ball()
                        self.machine.game.balls_in_play += 1
                    else:
                        self._abort()
                else:
                    ms = int(action & 0b01111111)
                    ms *= ms
                    self.advance_time_and_run(ms / 1000.0)
            else:
                switch = int(action & 0b01111111)
                if switch >= len(self.switch_list):
                    self._abort()
                    return
                switch_obj = self.machine.switches[self.switch_list[switch]]
                state = switch_obj.hw_state ^ 1
                # print(switch_list[switch], state, switch_obj.hw_state)
                self.machine.switch_controller.process_switch_by_num(
                    switch_obj.hw_switch.number, state,
                    self.machine.default_platform)

        if find_logic_bugs:
            self.advance_time_and_run(60)
            if self._invalid_input:
                # might happen late
                return

            balls = 0
            for playfield in self.machine.playfields:
                balls += playfield.balls

            if balls != self.machine.game.balls_in_play:
                print("Balls in play:", self.machine.game.balls_in_play)
                print("Playfields:")
                for playfield in self.machine.playfields:
                    print(playfield.name, playfield.balls)
                print("Devices:")
                for device in self.machine.ball_devices:
                    print(device.name, device.balls, device.available_balls)
                raise AssertionError(
                    "Balls in play do not match balls on playfields.")

    def dump(self, wait, add_balls, start_game, actions):
        """Dump fuzzer input."""
        if add_balls:
            balls_to_add = 3
            for device in sorted(self.machine.ball_devices.values()):
                if "trough" in device.tags:
                    for switch in device.ball_count_handler.counter.config.get(
                            'ball_switches', []):
                        print("Enable switch {}".format(switch.name))
                        switch.hw_state = 1
                        balls_to_add -= 1
                        if balls_to_add == 0:
                            break

                    if balls_to_add == 0:
                        break
                    if device.ball_count_handler.counter.config.get(
                            'entrance_switch'):
                        print("Enable switch {}".format(
                            device.config['entrance_switch'].name))
                        device.config['entrance_switch'].switch.hw_state = 1
                        balls_to_add -= 1
                        if balls_to_add == 0:
                            break
            print("Advance time 10s")

        if start_game:
            for switch in self.machine.switches:
                if "start" in switch.tags:
                    print("Enable/disable switch {}".format(switch.name))

        if wait > 0:
            print("Advance time {}s".format(wait))

        for action in actions:
            if action & 0b10000000:
                if action == 0b11111111:
                    print("Add ball into play")
                else:
                    ms = int(action & 0b01111111)
                    ms *= ms
                    print("Advance time by {} ms".format(ms))
            else:
                switch = int(action & 0b01111111)
                if switch >= len(self.switch_list):
                    print("Invalid switch. Abort!")
                    return
                switch_obj = self.machine.switches[self.switch_list[switch]]
                switch_obj.hw_state = state = switch_obj.hw_state ^ 1
                # print(switch_list[switch], state, switch_obj.hw_state)
                print("Toggle switch {}. New state: {}".format(
                    switch_obj.name, state))
Пример #7
0
    def setUp(self):
        """Setup test."""
        self._get_event_loop = asyncio.get_event_loop
        asyncio.get_event_loop = None
        self._get_event_loop2 = asyncio.events.get_event_loop
        events.get_event_loop = None

        # we want to reuse config_specs to speed tests up
        mpf.core.config_validator.ConfigValidator.unload_config_spec = (
            MagicMock())

        self._events = {}
        self._last_event_kwargs = {}
        self._exception = None

        # print(threading.active_count())

        self.test_start_time = time.time()
        self.save_and_prepare_sys_path()

        # init machine
        machine_path = self.get_absolute_machine_path()

        self.loop = TimeTravelLoop()
        self.loop.set_exception_handler(self._exception_handler)
        self.clock = TestClock(self.loop)
        self._mock_loop()

        if self.unittest_verbosity() > 1:
            class LogRecordClock(logging.LogRecord):
                def __init__(self_inner, *args, **kwargs):
                    self_inner.created_clock = self.clock.get_time()
                    super().__init__(*args, **kwargs)

            logging.setLogRecordFactory(LogRecordClock)

            console_log = logging.StreamHandler()
            console_log.setLevel(logging.DEBUG)
            console_log.setFormatter(MpfUnitTestFormatter(fmt='%(asctime)s : %(levelname)s : %('
                                                              'name)s : %(message)s'))
            logging.basicConfig(level=logging.DEBUG,
                                handlers=[console_log])
        else:
            # no logging by default
            logging.basicConfig(level=99)

        # load config
        config_loader = UnitTestConfigLoader(machine_path, [self._get_config_file()], self.machine_config_defaults,
                                             self.machine_config_patches, self.machine_spec_patches)

        config = config_loader.load_mpf_config()

        try:
            self.machine = TestMachineController(
                self.get_options(), config, self.machine_config_patches, self.machine_config_defaults,
                self.clock, self._get_mock_data(), self.get_enable_plugins())

            self._early_machine_init(self.machine)

            self._initialise_machine()

        # pylint: disable-msg=broad-except
        except Exception as e:
            try:
                self.machine.stop()
            except AttributeError:
                pass
            if self._exception and 'exception' in self._exception:
                raise self._exception['exception']
            elif self._exception:
                raise Exception(self._exception, e)
            raise e
Пример #8
0
class MpfTestCase(unittest.TestCase):

    """Primary TestCase class used for all MPF unit tests."""

    def __init__(self, methodName='runTest'):
        self._get_event_loop = None
        self._get_event_loop2 = None

        LogMixin.unit_test = True

        super().__init__(methodName)
        self.machine = None     # type: TestMachineController
        self.machine_config_patches = dict()
        self.machine_config_patches['mpf'] = dict()
        self.machine_config_patches['mpf']['default_platform_hz'] = 100
        self.machine_config_patches['mpf']['plugins'] = list()
        self.machine_config_patches['bcp'] = []

        self.machine_config_defaults = dict()
        self.machine_config_defaults['playfields'] = dict()
        self.machine_config_defaults['playfields']['playfield'] = dict()
        self.machine_config_defaults['playfields']['playfield']['tags'] = "default"
        self.machine_config_defaults['playfields']['playfield']['default_source_device'] = None

        self.machine_spec_patches = {}

        self._last_event_kwargs = {}
        self._events = {}
        self.expected_duration = 0.5

    def start_mode(self, mode):
        """Start mode."""
        self.assertIn(mode, self.machine.modes)
        self.assertModeNotRunning(mode)
        self.machine.modes[mode].start()
        self.machine_run()
        self.assertModeRunning(mode)

    def stop_mode(self, mode):
        """Stop mode."""
        self.assertModeRunning(mode)
        self.machine.modes[mode].stop()
        self.machine_run()
        self.assertModeNotRunning(mode)

    def get_config_file(self):
        """Return a string name of the machine config file to use for the tests
        in this class.

        You should override this method in your own test class to point to the
        config file you need for your tests.

        Returns:
            A string name of the machine config file to use, complete with the
            ``.yaml`` file extension.

        For example:

        .. code::

            def get_config_file(self):
                return 'my_config.yaml'

        """
        return 'null.yaml'

    def get_machine_path(self):
        """Return a string name of the path to the machine folder to use for
        the tests in this class.

        You should override this method in your own test class to point to the
        machine folder root you need for your tests.

        Returns:
            A string name of the machine path to use

        For example:

        .. code::

            def get_machine_path(self):
                return 'tests/machine_files/my_test/'

        Note that this path is relative to the MPF package root

        """
        return 'tests/machine_files/null/'

    def get_absolute_machine_path(self):
        """Return absolute machine path."""
        # check if there is a decorator
        config_directory = getattr(getattr(self, self._testMethodName), "config_directory", None)
        if not config_directory:
            config_directory = self.get_machine_path()

        # creates an absolute path based on machine_path
        return os.path.abspath(os.path.join(
            mpf.core.__path__[0], os.pardir, config_directory))

    @staticmethod
    def get_abs_path(path):
        """Get absolute path relative to current directory."""
        return os.path.join(os.path.abspath(os.curdir), path)

    def post_event(self, event_name, run_time=0):
        """Post an MPF event and optionally advance the time.

        Args:
            event_name: String name of the event to post
            run_time: How much time (in seconds) the test should advance
                after this event has been posted.

        For example, to post an event called "shot1_hit":

        .. code::

            self.post_event('shot1_hit')

        To post an event called "tilt" and then advance the time 1.5 seconds:

        .. code::

            self.post_event('tilt', 1.5)

        """
        self.machine.events.post(event_name)
        self.advance_time_and_run(run_time)

    def post_event_with_params(self, event_name, **params):
        """Post an MPF event with kwarg parameters.

        Args:
            event_name: String name of the event to post
            **params: One or more kwarg key/value pairs to post with the event.

        For example, to post an event called "jackpot" with the parameters
        ``count=1`` and ``first_time=True``, you would use:

        .. code::

            self.post_event('jackpot', count=1, first_time=True)

        """
        self.machine.events.post(event_name, **params)
        self.machine_run()

    def post_relay_event_with_params(self, event_name, **params):
        """Post a relay event synchronously and return the result."""
        future = self.machine.events.post_relay_async(event_name, **params)
        result = self.machine.clock.loop.run_until_complete(future)
        return result

    def assertPlaceholderEvaluates(self, expected, condition):
        """Assert that a placeholder evaluates to a certain value."""
        result = self.machine.placeholder_manager.build_raw_template(condition).evaluate([],
                                                                                         fail_on_missing_params=True)
        self.assertEqual(expected, result, "{} = {} != {}".format(condition, result, expected))

    def assertNumBallsKnown(self, balls):
        """Assert that a certain number of balls are known in the machine."""
        self.assertEqual(balls, self.machine.ball_controller.num_balls_known)

    def set_num_balls_known(self, balls):
        """Set the ball controller's ``num_balls_known`` attribute.

        This is needed for tests where you don't have any ball devices and
        other situations where you need the ball controller to think the
        machine has a certain amount of balls to run a test.

        Example:

        .. code::

            self.set_num_balls_known(3)


        """
        # in case the test does not have any ball devices
        self.machine.ball_controller.num_balls_known = balls

    def get_platform(self):
        """Force this test class to use a certain platform.

        Returns:
            String name of the platform this test class will use.

        If you don't include this method in your test class, the platform will
        be set to `virtual`. If you want to use the smart virtual platform,
        you would add the following to your test class:

        .. code::

            def get_platform(self):
                return 'smart_virtual`

        """
        return 'virtual'

    def get_use_bcp(self):
        """Control whether tests in this class should use BCP.

        Returns: True or False

        The default is False. To use BCP in your test class, add the following:

         .. code::

            def get_use_bcp(self):
                return True

        """
        return False

    def get_enable_plugins(self):
        """Control whether tests in this class should load MPF plugins.

        Returns: True or False

        The default is False. To load plugins in your test class, add the
        following:

         .. code::

            def get_enable_plugins(self):
                return True

        """
        return False

    def _get_config_file(self):
        """Return test decorator value or the return of get_config_file."""
        config_file = getattr(getattr(self, self._testMethodName), "config_file", None)
        if config_file:
            return config_file
        else:
            return self.get_config_file()

    def get_options(self):
        """Return options for the machine controller."""
        mpfconfig = os.path.abspath(os.path.join(
            mpf.core.__path__[0], os.pardir, 'mpfconfig.yaml'))

        return {
            'force_platform': self.get_platform(),
            'production': False,
            'mpfconfigfile': mpfconfig,
            'configfile': Util.string_to_event_list(self._get_config_file()),
            'debug': True,
            'bcp': self.get_use_bcp(),
            'no_load_cache': False,
            'create_config_cache': True,
            'text_ui': False,
        }

    def advance_time_and_run(self, delta=1.0):
        """Advance the test clock and run anything that should run during that
        time.

        Args:
            delta: How much time to advance the test clock by (in seconds)

        This method will cause anything scheduled during the time to run,
        including things like delays, timers, etc.

        Advancing the clock will happen in multiple small steps if things are
        scheduled to happen during this advance. For example, you can advance
        the clock 10 seconds like this:

        .. code::

            self.advance_time_and_run(10)

        If there is a delay callback that is scheduled to happen in 2 seconds,
        then this method will advance the clock 2 seconds, process that delay,
        and then advance the remaining 8 seconds.

        """
        self.machine.log.info("Advancing time by %s", delta)
        try:
            self.loop.run_until_complete(asyncio.sleep(delay=delta, loop=self.loop))
            return
        except RuntimeError as e:
            try:
                self.machine.stop()
            except Exception:
                pass
            if self._exception and "exception" in self._exception:
                exception = self._exception['exception']
                self._exception = None
                raise exception
            elif self._exception:
                exception = self._exception['exception']
                self._exception = None
                raise Exception(exception, e)
            raise e

    def machine_run(self):
        """Process any delays, timers, or anything else scheduled.

        Note this is the same as:

        .. code::

            self.advance_time_and_run(0)

        """
        self.advance_time_and_run(0)

    @staticmethod
    def unittest_verbosity():
        """Return the verbosity setting of the currently running unittest
        program, or 0 if none is running.

        Returns: An integer value of the current verbosity setting.

        """
        frame = inspect.currentframe()
        while frame:
            obj = frame.f_locals.get('self')
            if isinstance(obj, unittest.TestProgram) or isinstance(obj,
                                                                   unittest.TextTestRunner):
                return obj.verbosity
            frame = frame.f_back
        return 0

    def save_and_prepare_sys_path(self):
        """Save sys path and prepare it for the test."""
        # save path
        self._sys_path = copy.deepcopy(sys.path)

        mpf_path = os.path.abspath(os.path.join(mpf.core.__path__[0], os.pardir))
        if mpf_path in sys.path:
            sys.path.remove(mpf_path)

        # make tests path independent. remove current dir absolue
        if os.curdir in sys.path:
            sys.path.remove(os.curdir)

        # make tests path independent. remove current dir relative
        if "" in sys.path:
            sys.path.remove("")

    def restore_sys_path(self):
        """Restore sys path after test."""
        sys.path = self._sys_path

    def _get_mock_data(self):
        """Return data for MockDataMangager in test."""
        return dict()

    def _mock_loop(self):
        pass

    def _early_machine_init(self, machine):
        pass

    def _exception_handler(self, loop, context):
        try:
            loop.stop()
        except RuntimeError:
            pass

        self._exception = context

    def setUp(self):
        """Setup test."""
        self._get_event_loop = asyncio.get_event_loop
        asyncio.get_event_loop = None
        self._get_event_loop2 = asyncio.events.get_event_loop
        events.get_event_loop = None

        # we want to reuse config_specs to speed tests up
        mpf.core.config_validator.ConfigValidator.unload_config_spec = (
            MagicMock())

        self._events = {}
        self._last_event_kwargs = {}
        self._exception = None

        # print(threading.active_count())

        self.test_start_time = time.time()
        self.save_and_prepare_sys_path()

        # init machine
        machine_path = self.get_absolute_machine_path()

        self.loop = TimeTravelLoop()
        self.loop.set_exception_handler(self._exception_handler)
        self.clock = TestClock(self.loop)
        self._mock_loop()

        if self.unittest_verbosity() > 1:
            class LogRecordClock(logging.LogRecord):
                def __init__(self_inner, *args, **kwargs):
                    self_inner.created_clock = self.clock.get_time()
                    super().__init__(*args, **kwargs)

            logging.setLogRecordFactory(LogRecordClock)

            console_log = logging.StreamHandler()
            console_log.setLevel(logging.DEBUG)
            console_log.setFormatter(MpfUnitTestFormatter(fmt='%(asctime)s : %(levelname)s : %('
                                                              'name)s : %(message)s'))
            logging.basicConfig(level=logging.DEBUG,
                                handlers=[console_log])
        else:
            # no logging by default
            logging.basicConfig(level=99)

        # load config
        config_loader = UnitTestConfigLoader(machine_path, [self._get_config_file()], self.machine_config_defaults,
                                             self.machine_config_patches, self.machine_spec_patches)

        config = config_loader.load_mpf_config()

        try:
            self.machine = TestMachineController(
                self.get_options(), config, self.machine_config_patches, self.machine_config_defaults,
                self.clock, self._get_mock_data(), self.get_enable_plugins())

            self._early_machine_init(self.machine)

            self._initialise_machine()

        # pylint: disable-msg=broad-except
        except Exception as e:
            try:
                self.machine.stop()
            except AttributeError:
                pass
            if self._exception and 'exception' in self._exception:
                raise self._exception['exception']
            elif self._exception:
                raise Exception(self._exception, e)
            raise e

    def _initialise_machine(self):
        init = asyncio.ensure_future(self.machine.initialise(), loop=self.loop)
        self._wait_for_start(init, 20)
        self.machine.events.process_event_queue()
        self.advance_time_and_run(.001)

    def _wait_for_start(self, init, timeout):
        start = time.time()
        while not init.done() and not self._exception:
            self.loop._run_once()
            if time.time() > start + timeout:
                raise AssertionError("Start took more than {}s".format(timeout))

        # trigger exception if there was one
        init.result()

    def _mock_event_handler(self, event_name, **kwargs):
        self._last_event_kwargs[event_name] = kwargs
        self._events[event_name] += 1

    def mock_event(self, event_name):
        """Configure an event to be mocked.

        Args:
            event_name: String name of the event to mock.

        Mocking an event is an easy way to check if an event was called without
        configuring some kind of callback action in your tests.

        Note that an event must be mocked here *before* it's posted in order
        for :meth:`assertEventNotCalled` and :meth:`assertEventCalled` to work.

        Mocking an event will not "break" it. In other words, any other
        registered handlers for this event will also be called even if the
        event has been mocked.

        For example:

        .. code::

            self.mock_event('my_event')
            self.assertEventNotCalled('my_event')  # This will be True
            self.post_event('my_event')
            self.assertEventCalled('my_event')  # This will also be True

        """
        self._events[event_name] = 0
        self._last_event_kwargs.pop(event_name, None)
        self.machine.events.remove_handler_by_event(event=event_name, handler=self._mock_event_handler)
        self.machine.events.add_handler(event=event_name,
                                        handler=self._mock_event_handler,
                                        event_name=event_name)

    def assertBallsOnPlayfield(self, balls, playfield="playfield"):
        """Assert that a certain number of ball is on a playfield."""
        self.assertEqual(balls, self.machine.playfields[playfield].balls,
                         "Playfield {} contains {} balls but should be {}".format(
                             playfield, self.machine.playfields[playfield].balls, balls))

    def assertAvailableBallsOnPlayfield(self, balls, playfield="playfield"):
        """Assert that a certain number of ball is available on a playfield."""
        self.assertEqual(balls, self.machine.playfields[playfield].available_balls)

    def assertMachineVarEqual(self, value, machine_var):
        """Assert that a machine variable exists and has a certain value."""
        self.assertTrue(self.machine.variables.is_machine_var(machine_var), "Machine Var {} does not exist.".format(machine_var))
        self.assertEqual(value, self.machine.variables.get_machine_var(machine_var))

    def assertPlayerVarEqual(self, value, player_var):
        """Assert that a player variable exists and has a certain value."""
        self.assertIsNotNone(self.machine.game, "There is no game.")
        self.assertEqual(value, self.machine.game.player[player_var], "Value of player var {} is {} but should be {}".
                         format(player_var, self.machine.game.player[player_var], value))

    def assertSwitchState(self, name, state):
        """Assert that a switch exists and has a certain state."""
        self.assertIn(name, self.machine.switches, "Switch {} does not exist.".format(name))
        self.assertEqual(state, self.machine.switch_controller.is_active(self.machine.switches[name]),
                         "Switch {} is in state {} != {}".format(
                             name, self.machine.switch_controller.is_active(self.machine.switches[name]), state))

    def assertLightChannel(self, light_name, brightness, channel="white"):
        """Assert that a light channel has a certain brightness."""
        self.assertIn(light_name, self.machine.lights, "Light {} does not exist".format(light_name))
        self.assertAlmostEqual(brightness / 255.0, self.machine.lights[light_name].hw_drivers[channel][0].
                               current_brightness)

    def assertNotLightChannel(self, light_name, brightness, channel="white"):
        """Assert that a light channel does not have a certain brightness."""
        self.assertIn(light_name, self.machine.lights, "Light {} does not exist".format(light_name))
        self.assertNotEqual(brightness, self.machine.lights[light_name].hw_drivers[channel][0].
                            current_brightness)

    def assertLightColor(self, light_name, color):
        """Assert that a light exists and shows one color."""
        self.assertIn(light_name, self.machine.lights, "Light {} does not exist".format(light_name))
        if isinstance(color, str) and color.lower() == 'on':
            color = self.machine.lights[light_name].config['default_on_color']

        self.assertEqual(RGBColor(color), self.machine.lights[light_name].get_color(),
                         "Light {}: {} != {}. Stack: {}".format(
                             light_name, RGBColor(color).name, self.machine.lights[light_name].get_color().name,
                             self.machine.lights[light_name].stack))

    def assertNotLightColor(self, light_name, color):
        """Assert that a light exists and does not show a certain color."""
        self.assertIn(light_name, self.machine.lights, "Light {} does not exist".format(light_name))
        if isinstance(color, str) and color.lower() == 'on':
            color = self.machine.lights[light_name].config['default_on_color']

        self.assertNotEqual(RGBColor(color), self.machine.lights[light_name].get_color(),
                            "{} == {}".format(RGBColor(color).name, self.machine.lights[light_name].get_color().name))

    def assertLightColors(self, light_name, color_list, secs=1, check_delta=.1):
        """Assert that a light exists and shows all colors in a list over a period."""
        self.assertIn(light_name, self.machine.lights, "Light {} does not exist".format(light_name))
        colors = list()

        # have to do it this weird way because `if 'on' in color_list:` doesn't
        # work since it tries to convert it to a color
        for color in color_list[:]:
            if isinstance(color, str) and color.lower() == 'on':
                color_list.remove('on')
                color_list.append(self.machine.lights[light_name].config['default_on_color'])
                break

        for x in range(int(secs / check_delta)):
            color = self.machine.lights[light_name].get_color()
            colors.append(color)
            self.advance_time_and_run(check_delta)

        for color in color_list:
            self.assertIn(RGBColor(color), colors)

    def assertLightOn(self, light_name):
        """Assert that a light exists and is on."""
        self.assertIn(light_name, self.machine.lights, "Light {} does not exist".format(light_name))
        self.assertEqual(255,
                         self.machine.lights[
                             light_name].hw_driver.current_brightness)

    def assertLightOff(self, light_name):
        """Assert that a light exists and is off."""
        self.assertIn(light_name, self.machine.lights, "Light {} does not exist".format(light_name))
        self.assertEqual(0, self.machine.lights[light_name].hw_driver.current_brightness)

    def assertLightFlashing(self, light_name, color=None, secs=1, check_delta=.1):
        """Assert that a light exists and is flashing."""
        self.assertIn(light_name, self.machine.lights, "Light {} does not exist".format(light_name))
        brightness_values = list()
        if not color:
            color = [255, 255, 255]

        for _ in range(int(secs / check_delta)):
            brightness_values.append(
                self.machine.lights[light_name].get_color())
            self.advance_time_and_run(check_delta)

        self.assertIn([0, 0, 0], brightness_values)
        self.assertIn(color, brightness_values)

    def assertModeRunning(self, mode_name):
        """Assert that a mode exists and is running."""
        if mode_name not in self.machine.modes:
            raise AssertionError("Mode {} not known.".format(mode_name))
        self.assertIn(self.machine.modes[mode_name], self.machine.mode_controller.active_modes,
                      "Mode {} not running.".format(mode_name))

    def assertModeNotRunning(self, mode_name):
        """Assert that a mode exists and is not running."""
        if mode_name not in self.machine.modes:
            raise AssertionError("Mode {} not known.".format(mode_name))
        self.assertNotIn(self.machine.modes[mode_name], self.machine.mode_controller.active_modes,
                         "Mode {} running but should not.".format(mode_name))

    def assertEventNotCalled(self, event_name):
        """Assert that event was not called.

        Args:
            event_name: String name of the event to check.

        Note that the event must be mocked via ``self.mock_event()`` first in
        order to use this method.

        """
        if event_name not in self._events:
            raise AssertionError("Event {} not mocked.".format(event_name))

        if self._events[event_name] != 0:
            raise AssertionError("Event {} was called {} times but was not expected to "
                                 "be called.".format(event_name, self._events[event_name]))

    def assertEventCalled(self, event_name, times=None):
        """Assert that event was called.

        Args:
            event_name: String name of the event to check.
            times: An optional value to confirm the number of times the event
                was called. Default of *None* means this method will pass as
                long as the event has been called at least once.

        If you want to reset the ``times`` count, you can mock the event
        again.

        Note that the event must be mocked via ``self.mock_event()`` first in
        order to use this method.

        For example:

        .. code::

            self.mock_event('my_event')
            self.assertEventNotCalled('my_event')  # This will pass

            self.post_event('my_event')
            self.assertEventCalled('my_event')     # This will pass
            self.assertEventCalled('my_event', 1)  # This will pass

            self.post_event('my_event')
            self.assertEventCalled('my_event')     # This will pass
            self.assertEventCalled('my_event', 2)  # This will pass

        """
        if event_name not in self._events:
            raise AssertionError("Event {} not mocked.".format(event_name))

        if self._events[event_name] == 0 and times != 0:
            if times is None:
                raise AssertionError("Event {} was not called.".format(event_name))

            raise AssertionError("Event {} was not called but we expected {} calls.".format(event_name, times))

        if times is not None and self._events[event_name] != times:
            raise AssertionError("Event {} was called {} times instead of {} times expected.".format(
                event_name, self._events[event_name], times))

    def assertEventCalledWith(self, event_name, **kwargs):
        """Assert an event was called with certain kwargs.

        Args:
            event_name: String name of the event to check.
            **kwargs: Name/value parameters to check.

        For example:

        .. code::

            self.mock_event('jackpot')

            self.post_event('jackpot', count=1, first_time=True)
            self.assertEventCalled('jackpot')  # This will pass
            self.assertEventCalledWith('jackpot', count=1, first_time=True)  # This will also pass
            self.assertEventCalledWith('jackpot', count=1, first_time=False)  # This will fail


        """
        self.assertEventCalled(event_name)
        self.assertEqual(kwargs, self._last_event_kwargs[event_name], "Args for {} differ.".format(event_name))

    def assertColorAlmostEqual(self, color1, color2, delta=6):
        """Assert that two color are almost equal.

        Args:
            color1: The first color, as an RGBColor instance or 3-item iterable.
            color2: The second color, as an RGBColor instance or 3-item iterable.
            delta: How close the colors have to be. The deltas between red,
                green, and blue are added together and must be less or equal
                to this value for this assertion to succeed.

        """
        if isinstance(color1, RGBColor) and isinstance(color2, RGBColor):
            difference = abs(color1.red - color2.red) +\
                abs(color1.blue - color2.blue) +\
                abs(color1.green - color2.green)
        else:
            difference = abs(color1[0] - color2[0]) +\
                abs(color1[1] - color2[1]) +\
                abs(color1[2] - color2[2])
        self.assertLessEqual(difference, delta, "Colors do not match: " + str(color1) + " " + str(color2))

    def reset_mock_events(self):
        """Reset all mocked events.

        This will reset the count of number of times called every mocked
        event is.

        """
        for event in self._events.keys():
            self._events[event] = 0

    def hit_switch_and_run(self, name, delta):
        """Activates a switch and advances the time.

        Args:
            name: The name of the switch to activate.
            delta: The time (in seconds) to advance the clock.

        Note that this method does not deactivate the switch once the time
        has been advanced, meaning the switch stays active. To make the
        switch inactive, use the :meth:`release_switch_and_run`.

        """
        self.machine.switch_controller.process_switch(name, state=1, logical=True)
        self.advance_time_and_run(delta)

    def release_switch_and_run(self, name, delta):
        """Deactivates a switch and advances the time.

        Args:
            name: The name of the switch to activate.
            delta: The time (in seconds) to advance the clock.

        """
        self.machine.switch_controller.process_switch(name, state=0, logical=True)
        self.advance_time_and_run(delta)

    def hit_and_release_switch(self, name):
        """Momentarily activates and then deactivates a switch.

        Args:
            name: The name of the switch to hit.

        This method immediately activates and deactivates a switch with no
        time in between.

        """
        self.machine.switch_controller.process_switch(name, state=1, logical=True)
        self.machine.switch_controller.process_switch(name, state=0, logical=True)
        self.machine_run()

    def hit_and_release_switches_simultaneously(self, names):
        """Momentarily activates and then deactivates multiple switches.

        Switches are hit sequentially and then released sequentially.
        Events are only processed at the end of the sequence which is useful
        to reproduce race conditions when processing nearly simultaneous hits.

        Args:
            names: The names of the switches to hit and release.

        """
        for name in names:
            self.machine.switch_controller.process_switch(name, state=1, logical=True)
        for name in names:
            self.machine.switch_controller.process_switch(name, state=0, logical=True)
        self.machine_run()

    def tearDown(self):
        """Tear down test."""
        if self._exception:
            try:
                self.machine.shutdown()
            except:
                pass

            if self._exception and 'exception' in self._exception:
                raise self._exception['exception']
            elif self._exception:
                raise Exception(self._exception)

        duration = time.time() - self.test_start_time
        if duration > self.expected_duration:
            print("Test {}.{} took {} > {}s".format(self.__class__,
                  self._testMethodName, round(duration, 2),
                  self.expected_duration))

        self.machine.log.debug("Test ended")
        if sys.exc_info != (None, None, None):
            # disable teardown logging after error
            logging.basicConfig(level=99)
        else:
            # fire all delays
            self.advance_time_and_run(300)
        self.machine._do_stop()
        self.machine = None

        self.restore_sys_path()
        asyncio.get_event_loop = self._get_event_loop
        self._get_event_loop = None
        events.get_event_loop = self._get_event_loop2
        self._get_event_loop2 = None

    @staticmethod
    def add_to_config_validator(machine, key, new_dict):
        """Add config dict to validator."""
        machine.config_validator.get_config_spec()[key] = new_dict
Пример #9
0
class MpfTestCase(unittest.TestCase):
    def __init__(self, methodName='runTest'):
        self._get_event_loop = None
        self._get_event_loop2 = None

        super().__init__(methodName)
        self.machine = None  # type: TestMachineController
        self.machine_config_patches = dict()
        self.machine_config_patches['mpf'] = dict()
        self.machine_config_patches['mpf']['default_platform_hz'] = 100
        self.machine_config_patches['mpf']['plugins'] = list()
        self.machine_config_patches['bcp'] = []
        self._last_event_kwargs = {}
        self._events = {}
        self.expected_duration = 0.5
        self.min_frame_time = 1 / 30  # test with default Hz

    def getConfigFile(self):
        """Override this method in your own test class to point to the config
        file you need for your tests.

        """
        return 'null.yaml'

    def getMachinePath(self):
        """Override this method in your own test class to point to the machine
        folder you need for your tests.

        Path is related to the MPF package root

        """
        return 'tests/machine_files/null/'

    def getAbsoluteMachinePath(self):
        # creates an absolute path based on machine_path
        return os.path.abspath(
            os.path.join(mpf.core.__path__[0], os.pardir,
                         self.getMachinePath()))

    def get_abs_path(self, path):
        return os.path.join(os.path.abspath(os.curdir), path)

    def post_event(self, event_name, run_time=0):
        self.machine.events.post(event_name)
        self.advance_time_and_run(run_time)

    def post_event_with_params(self, event_name, **params):
        self.machine.events.post(event_name, **params)
        self.machine_run()

    def set_num_balls_known(self, balls):
        # in case the test does not have any ball devices
        self.machine.ball_controller.num_balls_known = balls

    def get_platform(self):
        return 'virtual'

    def get_use_bcp(self):
        return False

    def get_enable_plugins(self):
        return False

    def getOptions(self):

        mpfconfig = os.path.abspath(
            os.path.join(mpf.core.__path__[0], os.pardir, 'mpfconfig.yaml'))

        return {
            'force_platform': self.get_platform(),
            'mpfconfigfile': mpfconfig,
            'configfile': Util.string_to_list(self.getConfigFile()),
            'debug': True,
            'bcp': self.get_use_bcp(),
            'no_load_cache': False,
            'create_config_cache': True,
        }

    def advance_time_and_run(self, delta=1.0):
        self.machine.log.info("Advancing time by %s", delta)
        try:
            self.loop.run_until_complete(
                asyncio.sleep(delay=delta, loop=self.loop))
            return
        except RuntimeError as e:
            if self._exception and "exception" in self._exception:
                raise self._exception['exception']
            elif self._exception:
                raise Exception(self._exception, e)
            raise e

    def machine_run(self):
        self.advance_time_and_run(0)

    def unittest_verbosity(self):
        """Return the verbosity setting of the currently running unittest
        program, or 0 if none is running.

        """
        frame = inspect.currentframe()
        while frame:
            obj = frame.f_locals.get('self')
            if isinstance(obj, unittest.TestProgram) or isinstance(
                    obj, unittest.TextTestRunner):
                return obj.verbosity
            frame = frame.f_back
        return 0

    def save_and_prepare_sys_path(self):
        # save path
        self._sys_path = copy.deepcopy(sys.path)

        mpf_path = os.path.abspath(
            os.path.join(mpf.core.__path__[0], os.pardir))
        if mpf_path in sys.path:
            sys.path.remove(mpf_path)

        # make tests path independent. remove current dir absolue
        if os.curdir in sys.path:
            sys.path.remove(os.curdir)

        # make tests path independent. remove current dir relative
        if "" in sys.path:
            sys.path.remove("")

    def restore_sys_path(self):
        # restore sys path
        sys.path = self._sys_path

    def _get_mock_data(self):
        """Return data for MockDataMangager in test."""
        return dict()

    def _mock_loop(self):
        pass

    def _exception_handler(self, loop, context):
        try:
            loop.stop()
        except RuntimeError:
            pass

        self._exception = context

    def setUp(self):
        self._get_event_loop = asyncio.get_event_loop
        asyncio.get_event_loop = None
        self._get_event_loop2 = asyncio.events.get_event_loop
        events.get_event_loop = None

        # we want to reuse config_specs to speed tests up
        mpf.core.config_validator.ConfigValidator.unload_config_spec = (
            MagicMock())

        self._events = {}
        self._last_event_kwargs = {}
        self._exception = None

        # print(threading.active_count())

        self.test_start_time = time.time()
        if self.unittest_verbosity() > 1:
            logging.basicConfig(level=logging.DEBUG,
                                format='%(asctime)s : %(levelname)s : %('
                                'name)s : %(message)s')
        else:
            # no logging by default
            logging.basicConfig(level=99)

        self.save_and_prepare_sys_path()

        # init machine
        machine_path = self.getAbsoluteMachinePath()

        self.loop = TimeTravelLoop()
        self.loop.set_exception_handler(self._exception_handler)
        self.clock = TestClock(self.loop)
        self._mock_loop()

        try:
            self.machine = TestMachineController(
                os.path.abspath(os.path.join(mpf.core.__path__[0],
                                             os.pardir)), machine_path,
                self.getOptions(), self.machine_config_patches, self.clock,
                self._get_mock_data(), self.get_enable_plugins())

            start = time.time()
            while not self.machine.test_init_complete and time.time(
            ) < start + 20:
                self.advance_time_and_run(0.01)

            self.machine.events.process_event_queue()
            self.advance_time_and_run(1)

        except Exception as e:
            # todo temp until I can figure out how to stop the asset loader
            # thread automatically.
            try:
                self.machine.stop()
            except AttributeError:
                pass
            if self._exception and 'exception' in self._exception:
                raise self._exception['exception']
            elif self._exception:
                raise Exception(self._exception, e)
            raise e

        self.assertTrue(self.machine.test_init_complete,
                        "Machine crashed during start")

    def _mock_event_handler(self, event_name, **kwargs):
        self._last_event_kwargs[event_name] = kwargs
        self._events[event_name] += 1

    def mock_event(self, event_name):
        self._events[event_name] = 0
        self.machine.events.remove_handler_by_event(
            event=event_name, handler=self._mock_event_handler)
        self.machine.events.add_handler(event=event_name,
                                        handler=self._mock_event_handler,
                                        event_name=event_name)

    def assertBallsOnPlayfield(self, balls, playfield="playfield"):
        self.assertEqual(balls, self.machine.playfields[playfield].balls)

    def assertAvailableBallsOnPlayfield(self, balls, playfield="playfield"):
        self.assertEqual(balls,
                         self.machine.playfields[playfield].available_balls)

    def assertMachineVarEqual(self, value, machine_var):
        self.assertTrue(self.machine.is_machine_var(machine_var),
                        "Machine Var {} does not exist.".format(machine_var))
        self.assertEqual(value, self.machine.get_machine_var(machine_var))

    def assertPlayerVarEqual(self, value, player_var):
        self.assertIsNotNone(self.machine.game, "There is no game.")
        self.assertEqual(
            value, self.machine.game.player[player_var],
            "Value of player var {} is {} but should be {}".format(
                player_var, self.machine.game.player[player_var], value))

    def assertSwitchState(self, name, state):
        self.assertIn(name, self.machine.switch_controller.switches,
                      "Switch {} does not exist.".format(name))
        self.assertEqual(state, self.machine.switch_controller.is_active(name))

    def assertLedColor(self, led_name, color):
        if isinstance(color, str) and color.lower() == 'on':
            color = self.machine.leds[led_name].config['default_color']

        self.assertEqual(list(RGBColor(color).rgb),
                         self.machine.leds[led_name].hw_driver.current_color)

    def assertLedColors(self, led_name, color_list, secs=1, check_delta=.1):
        colors = list()

        # have to do it this weird way because `if 'on' in color_list:` doesn't
        # work since it tries to convert it to a color
        for color in color_list[:]:
            if isinstance(color, str) and color.lower() == 'on':
                color_list.remove('on')
                color_list.append(
                    self.machine.leds[led_name].config['default_color'])
                break

        for x in range(int(secs / check_delta)):
            colors.append(
                RGBColor(self.machine.leds[led_name].hw_driver.current_color))
            self.advance_time_and_run(check_delta)

        for color in color_list:
            self.assertIn(RGBColor(color), colors)

    def assertLightOn(self, light_name):
        self.assertEqual(
            255, self.machine.lights[light_name].hw_driver.current_brightness)

    def assertLightOff(self, light_name):
        self.assertEqual(
            0, self.machine.lights[light_name].hw_driver.current_brightness)

    def assertLightFlashing(self, light_name, secs=1, check_delta=.1):
        brightness_values = list()

        for _ in range(int(secs / check_delta)):
            brightness_values.append(
                self.machine.lights[light_name].hw_driver.current_brightness)
            self.advance_time_and_run(check_delta)

        self.assertIn(0, brightness_values)
        self.assertIn(255, brightness_values)

    def assertModeRunning(self, mode_name):
        if mode_name not in self.machine.modes:
            raise AssertionError("Mode {} not known.".format(mode_name))
        self.assertIn(self.machine.modes[mode_name],
                      self.machine.mode_controller.active_modes,
                      "Mode {} not running.".format(mode_name))

    def assertModeNotRunning(self, mode_name):
        if mode_name not in self.machine.modes:
            raise AssertionError("Mode {} not known.".format(mode_name))
        self.assertNotIn(self.machine.modes[mode_name],
                         self.machine.mode_controller.active_modes,
                         "Mode {} running but should not.".format(mode_name))

    def assertEventNotCalled(self, event_name):
        """Assert that event was not called."""
        if event_name not in self._events:
            raise AssertionError("Event {} not mocked.".format(event_name))

        if self._events[event_name] != 0:
            raise AssertionError("Event {} was called {} times.".format(
                event_name, self._events[event_name]))

    def assertEventCalled(self, event_name, times=None):
        """Assert that event was called."""
        if event_name not in self._events:
            raise AssertionError("Event {} not mocked.".format(event_name))

        if self._events[event_name] == 0 and times != 0:
            raise AssertionError("Event {} was not called.".format(event_name))

        if times is not None and self._events[event_name] != times:
            raise AssertionError(
                "Event {} was called {} instead of {}.".format(
                    event_name, self._events[event_name], times))

    def assertEventCalledWith(self, event_name, **kwargs):
        """Assert that event was called with kwargs."""
        self.assertEventCalled(event_name)
        self.assertEqual(kwargs, self._last_event_kwargs[event_name],
                         "Args for {} differ.".format(event_name))

    def assertShotShow(self, shot_name, show_name):
        """Assert that the highest priority running show for a shot is a
        certain show name."""
        if shot_name not in self.machine.shots:
            raise AssertionError(
                "Shot {} is not a valid shot".format(shot_name))

        if show_name:
            self.assertIsNotNone(self.machine.shots[shot_name].profiles)
            self.assertIsNotNone(
                self.machine.shots[shot_name].profiles[0]['running_show'])
            self.assertEqual(
                show_name,
                self.machine.shots[shot_name].profiles[0]['running_show'].name)
        else:
            self.assertIsNone(
                self.machine.shots[shot_name].profiles[0]['running_show'])

    def assertShotProfile(self, shot_name, profile_name):
        """Assert that the highest priority profile for a shot is a
        certain profile name."""
        if shot_name not in self.machine.shots:
            raise AssertionError(
                "Shot {} is not a valid shot".format(shot_name))

        if profile_name:
            self.assertIsNotNone(self.machine.shots[shot_name].profiles)
            self.assertIsNotNone(
                self.machine.shots[shot_name].profiles[0]['profile'])
            self.assertEqual(
                profile_name,
                self.machine.shots[shot_name].profiles[0]['profile'])
        else:
            self.assertIsNone(
                self.machine.shots[shot_name].profiles[0]['profile'])

    def assertShotProfileState(self, shot_name, state_name):
        """Assert that the highest priority profile for a shot is in a certain
        state."""
        if shot_name not in self.machine.shots:
            raise AssertionError(
                "Shot {} is not a valid shot".format(shot_name))

        if state_name:
            self.assertIsNotNone(self.machine.shots[shot_name].profiles)
            self.assertIsNotNone(self.machine.shots[shot_name].profiles[0]
                                 ['current_state_name'])
            self.assertEqual(
                state_name, self.machine.shots[shot_name].profiles[0]
                ['current_state_name'])
        else:
            self.assertIsNone(self.machine.shots[shot_name].profiles[0]
                              ['current_state_name'])

    def assertShotEnabled(self, shot_name):
        if shot_name not in self.machine.shots:
            raise AssertionError(
                "Shot {} is not a valid shot".format(shot_name))

        self.assertTrue(self.machine.shots[shot_name].profiles[0]['enable'])

    def assertShowRunning(self, show_name):
        for running_show in self.machine.show_controller.running_shows:
            if self.machine.shows[show_name] == running_show.show:
                return

        self.fail("Show {} not running".format(show_name))

    def assertShowNotRunning(self, show_name):
        for running_show in self.machine.show_controller.running_shows:
            if self.machine.shows[show_name] == running_show.show:
                self.fail("Show {} should not be running".format(show_name))

    def assertColorAlmostEqual(self, color1, color2, delta=6):
        if isinstance(color1, RGBColor) and isinstance(color2, RGBColor):
            difference = abs(color1.red - color2.red) +\
                abs(color1.blue - color2.blue) +\
                abs(color1.green - color2.green)
        else:
            difference = abs(color1[0] - color2[0]) +\
                abs(color1[1] - color2[1]) +\
                abs(color1[2] - color2[2])
        self.assertLessEqual(
            difference, delta,
            "Colors do not match: " + str(color1) + " " + str(color2))

    def get_timer(self, timer):
        for mode in self.machine.modes:
            for t in mode.timers:
                if t == timer:
                    return mode.timers[t]

        raise AssertionError("Timer {} not found".format(timer))

    def reset_mock_events(self):
        for event in self._events.keys():
            self._events[event] = 0

    def hit_switch_and_run(self, name, delta):
        self.machine.switch_controller.process_switch(name, 1, True)
        self.advance_time_and_run(delta)

    def release_switch_and_run(self, name, delta):
        self.machine.switch_controller.process_switch(name, 0, True)
        self.advance_time_and_run(delta)

    def hit_and_release_switch(self, name):
        self.machine.switch_controller.process_switch(name, 1, True)
        self.machine.switch_controller.process_switch(name, 0, True)
        self.machine_run()

    def tearDown(self):
        duration = time.time() - self.test_start_time
        if duration > self.expected_duration:
            print("Test {}.{} took {} > {}s".format(self.__class__,
                                                    self._testMethodName,
                                                    round(duration, 2),
                                                    self.expected_duration))

        self.machine.log.debug("Test ended")
        if sys.exc_info != (None, None, None):
            # disable teardown logging after error
            logging.basicConfig(level=99)
        else:
            # fire all delays
            self.min_frame_time = 20.0
            self.advance_time_and_run(300)
        self.machine.stop()
        self.machine._do_stop()
        self.machine.clock.loop.close()
        self.machine = None

        self.restore_sys_path()
        asyncio.get_event_loop = self._get_event_loop
        self._get_event_loop = None
        events.get_event_loop = self._get_event_loop2
        self._get_event_loop2 = None

    def add_to_config_validator(self, key, new_dict):
        if mpf.core.config_validator.ConfigValidator.config_spec:
            mpf.core.config_validator.ConfigValidator.config_spec[key] = (
                new_dict)
        else:
            mpf.core.config_validator.mpf_config_spec += '\n' + yaml.dump(
                {key: new_dict}, default_flow_style=False)
Пример #10
0
    def setUp(self):
        self._get_event_loop = asyncio.get_event_loop
        asyncio.get_event_loop = None
        self._get_event_loop2 = asyncio.events.get_event_loop
        events.get_event_loop = None

        # we want to reuse config_specs to speed tests up
        mpf.core.config_validator.ConfigValidator.unload_config_spec = (
            MagicMock())

        self._events = {}
        self._last_event_kwargs = {}
        self._exception = None

        # print(threading.active_count())

        self.test_start_time = time.time()
        if self.unittest_verbosity() > 1:
            logging.basicConfig(level=logging.DEBUG,
                                format='%(asctime)s : %(levelname)s : %('
                                'name)s : %(message)s')
        else:
            # no logging by default
            logging.basicConfig(level=99)

        self.save_and_prepare_sys_path()

        # init machine
        machine_path = self.getAbsoluteMachinePath()

        self.loop = TimeTravelLoop()
        self.loop.set_exception_handler(self._exception_handler)
        self.clock = TestClock(self.loop)
        self._mock_loop()

        try:
            self.machine = TestMachineController(
                os.path.abspath(os.path.join(mpf.core.__path__[0],
                                             os.pardir)), machine_path,
                self.getOptions(), self.machine_config_patches, self.clock,
                self._get_mock_data(), self.get_enable_plugins())

            start = time.time()
            while not self.machine.test_init_complete and time.time(
            ) < start + 20:
                self.advance_time_and_run(0.01)

            self.machine.events.process_event_queue()
            self.advance_time_and_run(1)

        except Exception as e:
            # todo temp until I can figure out how to stop the asset loader
            # thread automatically.
            try:
                self.machine.stop()
            except AttributeError:
                pass
            if self._exception and 'exception' in self._exception:
                raise self._exception['exception']
            elif self._exception:
                raise Exception(self._exception, e)
            raise e

        self.assertTrue(self.machine.test_init_complete,
                        "Machine crashed during start")
Пример #11
0
class AflRunner(object):
    """AFL fuzzer."""
    def __init__(self, use_virtual):
        """Initialize fuzzer."""
        self.loop = None
        self.clock = None
        self.machine = None  # type: TestMachineController
        self.machine_config_patches = dict()
        self.machine_config_patches['mpf'] = dict()
        self.machine_config_patches['mpf']['default_platform_hz'] = 1
        self.machine_config_patches['bcp'] = []
        self.machine_config_defaults = {}
        self.switch_list = []
        self.use_virtual = use_virtual
        self._invalid_input = False

    def _exception_handler(self, loop, context):
        try:
            loop.stop()
        except RuntimeError:
            pass

        self._exception = context

    def get_platform(self):
        """Return platform."""
        if self.use_virtual:
            return "virtual"
        else:
            return "smart_virtual"

    def getConfigFile(self):  # noqa
        """Return config file."""
        return "config.yaml"

    def getAbsoluteMachinePath(self):  # noqa
        """Return an absolute path based on machine_path."""
        return "/home/kantert/cloud/flipper/src/good_vs_evil"

    def getOptions(self):  # noqa
        """Return option arrays."""
        mpfconfig = os.path.abspath(
            os.path.join(mpf.core.__path__[0], os.pardir, 'mpfconfig.yaml'))

        return {
            'force_platform': self.get_platform(),
            'mpfconfigfile': mpfconfig,
            'configfile': Util.string_to_list(self.getConfigFile()),
            'debug': True,
            'bcp': False,
            'no_load_cache': False,
            'create_config_cache': True,
            'text_ui': False,
        }

    def advance_time_and_run(self, delta=1.0):
        """Advance time and run."""
        try:
            self.loop.run_until_complete(
                asyncio.sleep(delay=delta, loop=self.loop))
            return
        except RuntimeError as e:
            if self._exception and "exception" in self._exception:
                raise self._exception['exception']
            elif self._exception:
                raise Exception(self._exception, e)
            raise e

    def setUp(self, machine_path):
        """Set up fuzzer."""
        self.loop = TimeTravelLoop()
        self.loop.set_exception_handler(self._exception_handler)
        self.clock = TestClock(self.loop)

        self.machine = TestMachineController(
            os.path.abspath(os.path.join(mpf.core.__path__[0],
                                         os.pardir)), machine_path,
            self.getOptions(), self.machine_config_patches,
            self.machine_config_defaults, self.clock, dict(), True)

        self.loop.run_until_complete(self.machine.initialise())

        self.machine.events.process_event_queue()
        self.advance_time_and_run(1)

        self.switch_list = sorted(self.machine.switches.keys())

        for switch in self.machine.switches:
            self.machine.switch_controller.process_switch_obj(switch, 0, True)

    def add_balls(self):
        """Add balls."""
        for device in self.machine.ball_devices:
            if "trough" in device.tags:
                for switch in device.config['ball_switches']:
                    self.machine.switch_controller.process_switch_obj(
                        switch, 1, True)
                if device.config['entrance_switch']:
                    self.machine.switch_controller.process_switch_obj(
                        device.config['entrance_switch'], 1, True)

        # let balls settle
        self.advance_time_and_run(10)

    def start_game(self):
        """Start game."""
        for switch in self.machine.switches:
            if "start" in switch.tags:
                self.machine.switch_controller.process_switch_obj(
                    switch, 1, True)
                self.machine.switch_controller.process_switch_obj(
                    switch, 0, True)

    def _abort(self, **kwargs):
        """Abort fuzzer run."""
        del kwargs
        self._invalid_input = True

    def run(self, actions, find_logic_bugs):
        """Run fuzzer."""
        if find_logic_bugs:
            self.machine.events.add_handler("balldevice_ball_missing",
                                            self._abort)
            self.machine.events.add_handler("found_new_ball", self._abort)
            self.machine.events.add_handler("mode_game_stopped", self._abort)

        for action in actions:
            if self._invalid_input:
                # bail out if we hit an invalid input. afl will notice this
                return

            if action & 0b10000000:
                ms = int(action & 0b01111111)
                ms *= ms
                self.advance_time_and_run(ms / 1000.0)
            else:
                switch = int(action & 0b01111111)
                if switch >= len(self.switch_list):
                    continue
                switch_obj = self.machine.switches[self.switch_list[switch]]
                state = switch_obj.hw_state ^ 1
                # print(switch_list[switch], state, switch_obj.hw_state)
                self.machine.switch_controller.process_switch_by_num(
                    switch_obj.hw_switch.number, state,
                    self.machine.default_platform)

        if find_logic_bugs:
            self.advance_time_and_run(60)
            if self._invalid_input:
                # might happen late
                return

            balls = 0
            for playfield in self.machine.playfields:
                balls += playfield.balls

            if balls != self.machine.game.balls_in_play:
                print("Balls in play:", self.machine.game.balls_in_play)
                print("Playfields:")
                for playfield in self.machine.playfields:
                    print(playfield.name, playfield.balls)
                print("Devices:")
                for device in self.machine.ball_devices:
                    print(device.name, device.balls, device.available_balls)
                raise AssertionError(
                    "Balls in play do not match balls on playfields.")

    def dump(self, wait, add_balls, start_game, actions):
        """Dump fuzzer input."""
        if add_balls:
            for device in self.machine.ball_devices:
                if "trough" in device.tags:
                    for switch in device.config['ball_switches']:
                        print("Enable switch {}".format(switch.name))
                    if device.config['entrance_switch']:
                        print("Enable switch {}".format(switch.name))
            print("Advance time 10s")

        if start_game:
            for switch in self.machine.switches:
                if "start" in switch.tags:
                    print("Enable/disable switch {}".format(switch.name))

        if wait > 0:
            print("Advance time {}s".format(wait))

        for action in actions:
            if action & 0b10000000:
                ms = int(action & 0b01111111)
                ms *= ms
                print("Advance time by {} ms".format(ms))
            else:
                switch = int(action & 0b01111111)
                if switch >= len(self.switch_list):
                    continue
                switch_obj = self.machine.switches[self.switch_list[switch]]
                state = switch_obj.hw_state ^ 1
                # print(switch_list[switch], state, switch_obj.hw_state)
                print("Toggle switch {}. New state: {}".format(
                    switch_obj.name, state))
                self.machine.switch_controller.process_switch_by_num(
                    switch_obj.hw_switch.number, state,
                    self.machine.default_platform)