def _start_mc(self): from mpfmc.core.mc import MpfMc # prevent sleep in clock Clock._max_fps = 0 mpf_config = ConfigProcessor.load_config_file( os.path.abspath( os.path.join(mpfmc.__path__[0], os.pardir, self.get_options()['mcconfigfile'])), 'machine') machine_path = self.getAbsoluteMachinePath() mpf_config = load_machine_config( Util.string_to_list(self.getConfigFile()), machine_path, mpf_config['mpf-mc']['paths']['config'], mpf_config) self.preprocess_config(mpf_config) self.mc = MpfMc(options=self.get_options(), config=mpf_config, machine_path=machine_path) from kivy.core.window import Window Window.create_window() Window.canvas.clear() self._start_app_as_slave()
def _start_mc(self): from mpfmc.core.mc import MpfMc # prevent sleep in clock Clock._max_fps = 0 machine_path = self.getAbsoluteMachinePath() self.mc = MpfMc(options=self.get_options(), machine_path=machine_path) from kivy.core.window import Window Window.create_window() Window.canvas.clear() self._start_app_as_slave()
def run(self, name): Clock._events = [[] for i in range(256)] self._test_started = time() self._test_name = self.id() self._test = name # This setup is done in run() because we need to give control to the # kivy event loop which we can only do by returning from the run() # that's called. So we override run() and setup mpf-mc and then call # our own run_test() on a callback. Then we can wait until the # environment is setup (which can take a few frames), then we call # super().run() to get the actual TestCase.run() method to run and # we return the results. # We have to do this in run() and not setUp() because run actually # calls setUp(), so since we were overriding it ours doesn't call it # so we just do our setup here since if we manually called setUp() then # it would be called again when we call super().run(). from mpf.core.player import Player Player.monitor_enabled = False mpf_config = ConfigProcessor.load_config_file( os.path.abspath( os.path.join(mpfmc.__path__[0], os.pardir, self.get_options()['mcconfigfile'])), 'machine') machine_path = os.path.abspath( os.path.join(mpfmc.__path__[0], os.pardir, 'mpfmc', self.get_machine_path())) mpf_config = load_machine_config( Util.string_to_list(self.get_config_file()), machine_path, mpf_config['mpf-mc']['paths']['config'], mpf_config) self.preprocess_config(mpf_config) self.mc = MpfMc(options=self.get_options(), config=mpf_config, machine_path=machine_path) self.patch_bcp() from kivy.core.window import Window Window.create_window() Window.canvas.clear() Clock.schedule_once(self.run_test, 0) self.mc.run()
def _start_mc(self): from mpfmc.core.mc import MpfMc # prevent sleep in clock Clock._max_fps = 0 machine_path = self.get_absolute_machine_path() config_loader = UnitTestConfigLoader(machine_path, self.machine.options['configfile'], {}, {}, {}) config = config_loader.load_mc_config() self.mc = MpfMc(config=config, options=self.get_options()) from kivy.core.window import Window Window.create_window() Window.canvas.clear() self._start_app_as_slave()
def setUp(self): # Most of the setup is done in run(). Explanation is there. Config._named_configs.pop('app', None) self._start_time = time() self._current_time = self._start_time Clock._start_tick = self._start_time Clock._last_tick = self._start_time Clock.time = self._mc_time # prevent sleep in clock Clock._max_fps = 0 # reset clock Clock._root_event = None from mpf.core.player import Player Player.monitor_enabled = False machine_path = self.get_absolute_machine_path() # load config config_loader = UnitTestConfigLoader(machine_path, [self.get_config_file()], {}, {}, {}) config = config_loader.load_mc_config() try: self.mc = MpfMc(options=self.get_options(), config=config) self.patch_bcp() from kivy.core.window import Window Window.create_window() Window.canvas.clear() self._start_app_as_slave() except Exception: if hasattr(self, "mc") and self.mc: # prevent dead locks with two asset manager threads self.mc.stop() raise
def setUp(self): # Most of the setup is done in run(). Explanation is there. Config._named_configs.pop('app', None) self._start_time = time() self._current_time = self._start_time Clock._start_tick = self._start_time Clock._last_tick = self._start_time Clock.time = self._mc_time # prevent sleep in clock Clock._max_fps = 0 Clock._events = [[] for i in range(256)] self._test_started = self._start_time from mpf.core.player import Player Player.monitor_enabled = False mpf_config = ConfigProcessor.load_config_file( os.path.abspath( os.path.join(mpfmc.__path__[0], os.pardir, self.get_options()['mcconfigfile'])), 'machine') machine_path = self.getAbsoluteMachinePath() mpf_config = load_machine_config( Util.string_to_list(self.get_config_file()), machine_path, mpf_config['mpf-mc']['paths']['config'], mpf_config) self.preprocess_config(mpf_config) self.mc = MpfMc(options=self.get_options(), config=mpf_config, machine_path=machine_path) self.patch_bcp() from kivy.core.window import Window Window.create_window() Window.canvas.clear() self._start_app_as_slave()
def setUp(self): # Most of the setup is done in run(). Explanation is there. Config._named_configs.pop('app', None) self._start_time = time() self._current_time = self._start_time Clock._start_tick = self._start_time Clock._last_tick = self._start_time Clock.time = self._mc_time # prevent sleep in clock Clock._max_fps = 0 Clock._events = [[] for i in range(256)] self._test_started = self._start_time from mpf.core.player import Player Player.monitor_enabled = False machine_path = self.getAbsoluteMachinePath() try: self.mc = MpfMc(options=self.get_options(), machine_path=machine_path) self.patch_bcp() from kivy.core.window import Window Window.create_window() Window.canvas.clear() self._start_app_as_slave() except Exception: if self.mc: # prevent dead locks with two asset manager threads self.mc.stop() raise
class TestBcpClient(MockBcpClient): def __init__(self, machine, name, bcp): super().__init__(machine, name, bcp) self.queue = Queue() self.exit_on_close = False self.fps = 30 self._start_time = time.time() Clock._start_tick = self._start_time Clock._last_tick = self._start_time Clock.time = self._mc_time Clock._events = [[] for i in range(256)] with patch("mpfmc.core.bcp_processor.BCPServer"): self._start_mc() self.mc_task = self.machine.clock.schedule_interval(self._run_mc, 1 / self.fps) bcp_mc = self.mc.bcp_processor bcp_mc.send = self.receive self.queue = bcp_mc.receive_queue self.mc.bcp_processor.enabled = True self.mc.bcp_client_connected = True self.mc.events.post("client_connected") def getAbsoluteMachinePath(self): # creates an absolute path based on machine_path return self.machine.machine_path def get_options(self): return dict(machine_path=self.getAbsoluteMachinePath(), mcconfigfile='mcconfig.yaml', production=False, configfile=self.machine.options['configfile'], no_load_cache=False, create_config_cache=True, bcp=False) def preprocess_config(self, config): # TODO this method is copied from the mc.py launcher. Prob a better way kivy_config = config['kivy_config'] try: kivy_config['graphics'].update(config['displays']['window']) except KeyError: pass try: kivy_config['graphics'].update(config['window']) except KeyError: pass if 'top' in kivy_config['graphics'] and 'left' in kivy_config['graphics']: kivy_config['graphics']['position'] = 'custom' for section, settings in kivy_config.items(): for k, v in settings.items(): try: if k in Config[section]: Config.set(section, k, v) except KeyError: continue def _start_app_as_slave(self): # from app::run if not self.mc.built: self.mc.load_config() self.mc.load_kv(filename=self.mc.kv_file) root = self.mc.build() if root: self.mc.root = root if self.mc.root: if not isinstance(self.mc.root, KivyWidget): Logger.critical('App.root must be an _instance_ of Kivy Widget') raise Exception('Invalid instance in App.root') from kivy.core.window import Window Window.add_widget(self.mc.root) # Check if the window is already created from kivy.base import EventLoop window = EventLoop.window if window: self.mc._app_window = window window.set_title(self.mc.get_application_name()) icon = self.mc.get_application_icon() if icon: window.set_icon(icon) self.mc._install_settings_keys(window) else: Logger.critical("Application: No window is created." " Terminating application run.") return self.mc.dispatch('on_start') runTouchApp(slave=True) # change is here while not self.mc.is_init_done.is_set(): EventLoop.idle() def _start_mc(self): from mpfmc.core.mc import MpfMc # prevent sleep in clock Clock._max_fps = 0 machine_path = self.getAbsoluteMachinePath() self.mc = MpfMc(options=self.get_options(), machine_path=machine_path) from kivy.core.window import Window Window.create_window() Window.canvas.clear() self._start_app_as_slave() def _mc_time(self): return self._start_time + self.machine.clock.loop._time def _run_mc(self): EventLoop.idle() def stop(self): self.mc.stop() self.machine.clock.unschedule(self.mc_task) def send(self, bcp_command, kwargs): self.queue.put((bcp_command, kwargs)) def receive(self, bcp_command, callback=None, rawbytes=None, **kwargs): if rawbytes: kwargs['rawbytes'] = rawbytes self.receive_queue.put_nowait((bcp_command, kwargs)) if callback: callback()
class MpfIntegrationTestCase(MpfTestCase): fps = 30 def get_use_bcp(self): return True def getAbsoluteMachinePath(self): # creates an absolute path based on machine_path return os.path.abspath( os.path.join(mpfmc.core.__path__[0], os.pardir, self.getMachinePath())) def get_options(self): return dict(machine_path=self.getAbsoluteMachinePath(), mcconfigfile='mpfmc/mcconfig.yaml', configfile=Util.string_to_list(self.getConfigFile()), bcp=False) def preprocess_config(self, config): # TODO this method is copied from the mc.py launcher. Prob a better way kivy_config = config['kivy_config'] try: kivy_config['graphics'].update(config['displays']['window']) except KeyError: pass try: kivy_config['graphics'].update(config['window']) except KeyError: pass if 'top' in kivy_config['graphics'] and 'left' in kivy_config[ 'graphics']: kivy_config['graphics']['position'] = 'custom' for section, settings in kivy_config.items(): for k, v in settings.items(): try: if k in Config[section]: Config.set(section, k, v) except KeyError: continue def _start_app_as_slave(self): # from app::run if not self.mc.built: self.mc.load_config() self.mc.load_kv(filename=self.mc.kv_file) root = self.mc.build() if root: self.mc.root = root if self.mc.root: if not isinstance(self.mc.root, Widget): Logger.critical('App.root must be an _instance_ of Widget') raise Exception('Invalid instance in App.root') from kivy.core.window import Window Window.add_widget(self.mc.root) # Check if the window is already created from kivy.base import EventLoop window = EventLoop.window if window: self.mc._app_window = window window.set_title(self.mc.get_application_name()) icon = self.mc.get_application_icon() if icon: window.set_icon(icon) self.mc._install_settings_keys(window) else: Logger.critical("Application: No window is created." " Terminating application run.") return self.mc.dispatch('on_start') runTouchApp(slave=True) # change is here while not self.mc.is_init_done: EventLoop.idle() def _start_mc(self): from mpfmc.core.mc import MpfMc # prevent sleep in clock Clock._max_fps = 0 mpf_config = ConfigProcessor.load_config_file( os.path.abspath( os.path.join(mpfmc.__path__[0], os.pardir, self.get_options()['mcconfigfile'])), 'machine') machine_path = self.getAbsoluteMachinePath() mpf_config = load_machine_config( Util.string_to_list(self.getConfigFile()), machine_path, mpf_config['mpf-mc']['paths']['config'], mpf_config) self.preprocess_config(mpf_config) self.mc = MpfMc(options=self.get_options(), config=mpf_config, machine_path=machine_path) from kivy.core.window import Window Window.create_window() Window.canvas.clear() self._start_app_as_slave() def _mc_time(self): return self._start_time + self.loop._time def _run_mc(self, dt): del dt if self.unittest_verbosity() > 1: time.sleep(.05) EventLoop.idle() def get_enable_plugins(self): return True def __init__(self, methodName): super().__init__(methodName) try: del self.machine_config_patches['mpf']['plugins'] except KeyError: pass self.machine_config_patches['bcp'] = \ {"connections": {"local_display": {"type": "mpfmc.tests.MpfIntegrationTestCase.TestBcpClient"}}} self.machine_config_patches['bcp']['servers'] = [] self.expected_duration = 60 def setUp(self): super().setUp() self._start_time = time.time() Clock._start_tick = self._start_time Clock._last_tick = self._start_time Clock.time = self._mc_time Clock._events = [[] for i in range(256)] with patch("mpfmc.core.bcp_processor.BCPServer"): self._start_mc() self.mc_task = self.clock.schedule_interval(self._run_mc, 1 / self.fps) client = self.machine.bcp.transport.get_named_client("local_display") bcp_mc = self.mc.bcp_processor bcp_mc.send = client.receive self.mc.events.post("client_connected") self.advance_time_and_run() while not client.queue.empty(): bcp_mc.receive_queue.put(client.queue.get()) client.queue = bcp_mc.receive_queue self.advance_time_and_run() def tearDown(self): self.mc.stop() self.clock.unschedule(self.mc_task) super().tearDown() EventLoop.close()
def __init__(self, mpf_path, machine_path, args): """Run MC.""" p = psutil.Process(os.getpid()) # increase priority slightly. this will keep MPF responsive when MC lags if sys.platform == "win32": p.nice(psutil.BELOW_NORMAL_PRIORITY_CLASS) else: p.nice(10) # undo all of Kivy's built-in logging so we can do it our way os.environ['KIVY_NO_FILELOG'] = '1' os.environ['KIVY_NO_CONSOLELOG'] = '1' from kivy.logger import Logger for handler in Logger.handlers: Logger.removeHandler(handler) sys.stderr = sys.__stderr__ # Need to have these in here because we don't want them to load when # the module is loaded as an mpf.command from mpf.core.utility_functions import Util del mpf_path parser = argparse.ArgumentParser( description='Starts the MPF Media Controller') parser.add_argument("-b", action="store_false", dest="bcp", default=True, help="Do not set up the BCP server threads") parser.add_argument( "-c", action="store", dest="configfile", default="config", metavar='config_file(s)', help="The name of a config file to load. Default is " "config.yaml. Multiple files can be used via a comma-" "separated list (no spaces between)") parser.add_argument( "-C", action="store", dest="mcconfigfile", default="mcconfig.yaml", metavar='config_file', help="The MPF framework default config file. Default is " "<mpf-mc install folder>/mcconfig.yaml") parser.add_argument("-f", action="store_true", dest="force_assets_load", default=False, help="Load all assets upon startup. Useful for " "ensuring all assets are set up properly " "during development.") parser.add_argument( "-l", action="store", dest="logfile", metavar='file_name', default=os.path.join( "logs", datetime.now().strftime("%Y-%m-%d-%H-%M-%S-mc-" + socket.gethostname() + ".log")), help="The name (and path) of the log file") parser.add_argument("-L", action="store", dest="mc_logfile", metavar='file_name', default=None, help="The name (and path) of the log file") parser.add_argument("-p", action="store_true", dest="pause", default=False, help="Pause the terminal window on exit. Useful " "when launching in a separate window so you can " "see any errors before the window closes.") parser.add_argument( "-P", action="store_true", dest="production", default=False, help= "Production mode. Will suppress errors, wait for hardware on start and " "try to exit when startup fails. Run this inside a loop.") parser.add_argument("-t", action="store_false", dest='text_ui', default=True, help="Use the ASCII text-based UI") parser.add_argument("-v", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO, help="Enables verbose logging to the" " log file") parser.add_argument( "-V", action="store_true", dest="consoleloglevel", default=logging.INFO, help="Enables verbose logging to the console. Do NOT on " "Windows platforms") parser.add_argument("-a", action="store_true", dest="no_load_cache", help="Forces the config to be loaded from files " "and not cache") parser.add_argument("-A", action="store_false", dest="create_config_cache", help="Does not create the cache config files") # The following are just included for full compatibility with mpf.py # which is needed when using "mpf both". parser.add_argument("-x", action="store_const", dest="force_platform", const='virtual', help=argparse.SUPPRESS) parser.add_argument("-X", action="store_const", dest="force_platform", const='smart_virtual', help=argparse.SUPPRESS) args = parser.parse_args(args) args.configfile = Util.string_to_list(args.configfile) # Configure logging. Creates a logfile and logs to the console. # Formatting options are documented here: # https://docs.python.org/2.7/library/logging.html#logrecord-attributes try: os.makedirs(os.path.join(machine_path, 'logs')) except OSError as exception: if exception.errno != errno.EEXIST: raise if args.mc_logfile: args.logfile = args.mc_logfile full_logfile_path = os.path.join(machine_path, args.logfile) try: os.remove(full_logfile_path) except OSError: pass logging.basicConfig(level=args.loglevel, format='%(asctime)s : %(name)s : %(message)s', filename=full_logfile_path, filemode='a') # define a Handler which writes INFO messages or higher to the # sys.stderr if args.text_ui: console = logging.NullHandler() console.setLevel(logging.ERROR) else: console = logging.StreamHandler() console.setLevel(args.consoleloglevel) # set a format which is simpler for console use formatter = logging.Formatter('%(name)s: %(message)s') # tell the handler to use this format console.setFormatter(formatter) # add the handler to the root logger logging.getLogger('').addHandler(console) from mpfmc.core.mc import MpfMc logging.info("Loading MPF-MC controller") thread_stopper = threading.Event() try: MpfMc(options=vars(args), machine_path=machine_path, thread_stopper=thread_stopper).run() logging.info("MC run loop ended.") except Exception as e: # noqa logging.exception(str(e)) logging.info("Stopping child threads... (%s remaining)", len(threading.enumerate()) - 1) thread_stopper.set() while len(threading.enumerate()) > 1: time.sleep(.1) logging.info("All child threads stopped.") logging.shutdown() if args.pause: input('Press ENTER to continue...') sys.exit()
def __init__(self, mpf_path, machine_path, args): # undo all of Kivy's built-in logging so we can do it our way os.environ['KIVY_NO_FILELOG'] = '1' os.environ['KIVY_NO_CONSOLELOG'] = '1' from kivy.logger import Logger for handler in Logger.handlers: Logger.removeHandler(handler) sys.stderr = sys.__stderr__ # Need to have these in here because we don't want them to load when # the module is loaded as an mpf.command import mpfmc from mpf.core.utility_functions import Util from mpfmc.core.config_processor import ConfigProcessor from mpfmc.core.utils import set_machine_path, load_machine_config del mpf_path parser = argparse.ArgumentParser(description='Starts the MPF Media Controller') parser.add_argument("-b", action="store_false", dest="bcp", default=True, help="Do not set up the BCP server threads") parser.add_argument("-c", action="store", dest="configfile", default="config", metavar='config_file(s)', help="The name of a config file to load. Default is " "config.yaml. Multiple files can be used via a comma-" "separated list (no spaces between)") parser.add_argument("-C", action="store", dest="mcconfigfile", default="mcconfig.yaml", metavar='config_file', help="The MPF framework default config file. Default is " "<mpf-mc install folder>/mcconfig.yaml") parser.add_argument("-f", action="store_true", dest="force_assets_load", default=False, help="Load all assets upon startup. Useful for " "ensuring all assets are set up properly " "during development.") parser.add_argument("-l", action="store", dest="logfile", metavar='file_name', default=os.path.join("logs", datetime.now().strftime( "%Y-%m-%d-%H-%M-%S-mc-" + socket.gethostname() + ".log")), help="The name (and path) of the log file") parser.add_argument("-p", action="store_true", dest="pause", default=False, help="Pause the terminal window on exit. Useful " "when launching in a separate window so you can " "see any errors before the window closes.") parser.add_argument("-v", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO, help="Enables verbose logging to the" " log file") parser.add_argument("-V", action="store_true", dest="consoleloglevel", default=logging.INFO, help="Enables verbose logging to the console. Do NOT on " "Windows platforms") # The following are just included for full compatibility with mpf.py # which is needed when using "mpf both". parser.add_argument("-a", action="store_const", dest="force_platform", const='no_load_cache', help=argparse.SUPPRESS) parser.add_argument("-A", action="store_const", dest="force_platform", const='create_config_cache', help=argparse.SUPPRESS) parser.add_argument("-x", action="store_const", dest="force_platform", const='virtual', help=argparse.SUPPRESS) parser.add_argument("-X", action="store_const", dest="force_platform", const='smart_virtual', help=argparse.SUPPRESS) args = parser.parse_args(args) args.configfile = Util.string_to_list(args.configfile) # Configure logging. Creates a logfile and logs to the console. # Formatting options are documented here: # https://docs.python.org/2.7/library/logging.html#logrecord-attributes try: os.makedirs(os.path.join(machine_path, 'logs')) except OSError as exception: if exception.errno != errno.EEXIST: raise logging.basicConfig(level=args.loglevel, format='%(asctime)s : %(levelname)s : %(name)s : ' '%(message)s', filename=os.path.join(machine_path, args.logfile), filemode='w') # define a Handler which writes INFO messages or higher to the # sys.stderr console = logging.StreamHandler() console.setLevel(args.consoleloglevel) # set a format which is simpler for console use formatter = logging.Formatter('%(levelname)s : %(name)s : %(message)s') # tell the handler to use this format console.setFormatter(formatter) # add the handler to the root logger logging.getLogger('').addHandler(console) mpf_config = ConfigProcessor.load_config_file(os.path.join( mpfmc.__path__[0], args.mcconfigfile), 'machine') machine_path = set_machine_path(machine_path, mpf_config['mpf-mc']['paths'][ 'machine_files']) mpf_config = load_machine_config(args.configfile, machine_path, mpf_config['mpf-mc']['paths'][ 'config'], mpf_config) self.preprocess_config(mpf_config) from mpfmc.core.mc import MpfMc logging.info("Loading MPF-MC controller") thread_stopper = threading.Event() try: MpfMc(options=vars(args), config=mpf_config, machine_path=machine_path, thread_stopper=thread_stopper).run() logging.info("MC run loop ended.") except Exception as e: logging.exception(str(e)) logging.info("Stopping child threads... (%s remaining)", len(threading.enumerate()) - 1) thread_stopper.set() while len(threading.enumerate()) > 1: time.sleep(.1) logging.info("All child threads stopped.") if args.pause: input('Press ENTER to continue...') sys.exit()
class MpfMcTestCase(unittest.TestCase): def __init__(self, *args): self.sent_bcp_commands = list() super().__init__(*args) self._events = dict() self._last_event_kwargs = dict() self.max_test_setup_secs = 30 self._fps = 30 def _mc_time(self): return self._current_time def get_options(self): return dict(machine_path=self.get_machine_path(), mcconfigfile='mcconfig.yaml', production=False, configfile=Util.string_to_list(self.get_config_file()), no_load_cache=False, create_config_cache=True, no_sound=False, bcp=False) def getAbsoluteMachinePath(self): return os.path.abspath( os.path.join(mpfmc.__path__[0], os.pardir, 'mpfmc', self.get_machine_path())) def get_machine_path(self): raise NotImplementedError def get_config_file(self): raise NotImplementedError def get_abs_path(self, path): return os.path.join(os.path.abspath(os.curdir), path) def advance_time(self, secs=.1): start = self._current_time while self._current_time < start + secs: EventLoop.idle() self._current_time += 1 / self._fps def advance_real_time(self, secs=.1): start = self._current_time while self._current_time < start + secs: EventLoop.idle() sleep(1 / self._fps) self._current_time += 1 / self._fps EventLoop.idle() def get_pixel_color(self, x, y): """Returns a binary string of the RGB bytes that make up the slide pixel at the passed x,y coordinates. 2 bytes per pixel, 6 bytes total. This method *does* compensate for different window sizes. Note: This method does not work yet. """ raise NotImplementedError # remove when we fix it. :) # do the Window import here because we don't want to import it at the # top or else we won't be able to set window properties from kivy.core.window import Window # convert the passed x/y to the actual x/y of the Window since it's # possible for the mpf-mc display size to be different than the Window # size x *= Window.width / Window.children[0].width y *= Window.height / Window.children[0].height return glReadPixels(x, y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE) def tearDown(self): self.mc.stop() def patch_bcp(self): # used internally self.orig_bcp_send = self.mc.bcp_processor.send self.mc.bcp_processor.send = self._bcp_send # this is used to send BCP commands to mpf-mc self.send = self.mc.bcp_processor._process_command self.mc.bcp_client_connected = True def _bcp_send(self, bcp_command, callback=None, **kwargs): # used for commands sent from the MC to the PC # print((bcp_command, callback, kwargs)) self.sent_bcp_commands.append((bcp_command, callback, kwargs)) self.orig_bcp_send(bcp_command=bcp_command, callback=callback, **kwargs) def setUp(self): # Most of the setup is done in run(). Explanation is there. Config._named_configs.pop('app', None) self._start_time = time() self._current_time = self._start_time Clock._start_tick = self._start_time Clock._last_tick = self._start_time Clock.time = self._mc_time # prevent sleep in clock Clock._max_fps = 0 # reset clock Clock._root_event = None from mpf.core.player import Player Player.monitor_enabled = False machine_path = self.getAbsoluteMachinePath() try: self.mc = MpfMc(options=self.get_options(), machine_path=machine_path) self.patch_bcp() from kivy.core.window import Window Window.create_window() Window.canvas.clear() self._start_app_as_slave() except Exception: if self.mc: # prevent dead locks with two asset manager threads self.mc.stop() raise def _start_app_as_slave(self): # from app::run if not self.mc.built: self.mc.load_config() self.mc.load_kv(filename=self.mc.kv_file) root = self.mc.build() if root: self.mc.root = root if self.mc.root: if not isinstance(self.mc.root, KivyWidget): Logger.critical( 'App.root must be an _instance_ of Kivy Widget') raise Exception('Invalid instance in App.root') from kivy.core.window import Window Window.add_widget(self.mc.root) # Check if the window is already created from kivy.base import EventLoop window = EventLoop.window if window: self.mc._app_window = window #window.set_title(self.mc.get_application_name() + self._testMethodName) icon = self.mc.get_application_icon() if icon: window.set_icon(icon) self.mc._install_settings_keys(window) else: Logger.critical("Application: No window is created." " Terminating application run.") return self.mc.dispatch('on_start') runTouchApp(slave=True) # change is here # Perform init process tries = 0 while not self.mc.is_init_done.is_set( ) and not self.mc.thread_stopper.is_set(): self.advance_time() sleep(.001) tries += 1 if tries > 1000: self.fail("Test init took too long") # set a nice title window.set_title(self.__class__.__name__ + "::" + self._testMethodName) def dump_clock(self): print("---------") events = [] event = Clock._root_event while event: events.append(event) event = event.next events.sort(key=lambda x: str(x.get_callback())) for event in events: print(event.get_callback(), event.timeout) 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.mc.events.remove_handler_by_event( event=event_name, handler=self._mock_event_handler) self.mc.events.add_handler(event=event_name, handler=self._mock_event_handler, event_name=event_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: 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 reset_mock_events(self): for event in self._events.keys(): self._events[event] = 0
class MpfMcTestCase(unittest.TestCase): def __init__(self, *args): self.sent_bcp_commands = list() super().__init__(*args) self._events = dict() self._last_event_kwargs = dict() self.max_test_setup_secs = 30 def get_options(self): return dict(machine_path=self.get_machine_path(), mcconfigfile='mpfmc/mcconfig.yaml', configfile=Util.string_to_list(self.get_config_file()), bcp=False) def getAbsoluteMachinePath(self): return os.path.abspath( os.path.join(mpfmc.__path__[0], os.pardir, 'mpfmc', self.get_machine_path())) def get_machine_path(self): raise NotImplementedError def get_config_file(self): raise NotImplementedError def get_abs_path(self, path): return os.path.join(os.path.abspath(os.curdir), path) def preprocess_config(self, config): # TODO this method is copied from the mc.py launcher. Prob a better way kivy_config = config['kivy_config'] try: kivy_config['graphics'].update(config['displays']['window']) except KeyError: pass try: kivy_config['graphics'].update(config['window']) except KeyError: pass if 'top' in kivy_config['graphics'] and 'left' in kivy_config[ 'graphics']: kivy_config['graphics']['position'] = 'custom' for section, settings in kivy_config.items(): for k, v in settings.items(): try: if k in Config[section]: Config.set(section, k, v) except KeyError: continue def advance_time(self, secs=.1): start = time() self.mc.events.process_event_queue() while time() < start + secs: sleep(.01) self.mc.events.process_event_queue() EventLoop.idle() def get_pixel_color(self, x, y): """Returns a binary string of the RGB bytes that make up the slide pixel at the passed x,y coordinates. 2 bytes per pixel, 6 bytes total. This method *does* compensate for different window sizes. Note: This method does not work yet. """ raise NotImplementedError # remove when we fix it. :) # do the Window import here because we don't want to import it at the # top or else we won't be able to set window properties from kivy.core.window import Window # convert the passed x/y to the actual x/y of the Window since it's # possible for the mpf-mc display size to be different than the Window # size x *= Window.width / Window.children[0].width y *= Window.height / Window.children[0].height return glReadPixels(x, y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE) def setUp(self): # Most of the setup is done in run(). Explanation is there. Config._named_configs.pop('app', None) def tearDown(self): stopTouchApp() def patch_bcp(self): # used internally self.orig_bcp_send = self.mc.bcp_processor.send self.mc.bcp_processor.send = self._bcp_send # this is used to send BCP commands to mpf-mc self.send = self.mc.bcp_processor._process_command self.mc.bcp_client_connected = True def _bcp_send(self, bcp_command, callback=None, **kwargs): # used for commands sent from the MC to the PC # print((bcp_command, callback, kwargs)) self.sent_bcp_commands.append((bcp_command, callback, kwargs)) self.orig_bcp_send(bcp_command=bcp_command, callback=callback, **kwargs) def run(self, name): Clock._events = [[] for i in range(256)] self._test_started = time() self._test_name = self.id() self._test = name # This setup is done in run() because we need to give control to the # kivy event loop which we can only do by returning from the run() # that's called. So we override run() and setup mpf-mc and then call # our own run_test() on a callback. Then we can wait until the # environment is setup (which can take a few frames), then we call # super().run() to get the actual TestCase.run() method to run and # we return the results. # We have to do this in run() and not setUp() because run actually # calls setUp(), so since we were overriding it ours doesn't call it # so we just do our setup here since if we manually called setUp() then # it would be called again when we call super().run(). from mpf.core.player import Player Player.monitor_enabled = False mpf_config = ConfigProcessor.load_config_file( os.path.abspath( os.path.join(mpfmc.__path__[0], os.pardir, self.get_options()['mcconfigfile'])), 'machine') machine_path = self.getAbsoluteMachinePath() mpf_config = load_machine_config( Util.string_to_list(self.get_config_file()), machine_path, mpf_config['mpf-mc']['paths']['config'], mpf_config) self.preprocess_config(mpf_config) self.mc = MpfMc(options=self.get_options(), config=mpf_config, machine_path=machine_path) self.patch_bcp() from kivy.core.window import Window Window.create_window() Window.canvas.clear() Clock.schedule_once(self.run_test, 0) self.mc.run() def dump_clock(self): print("---------") events = [] for slot in Clock._events: for event in slot: events.append(event) events.sort(key=lambda x: str(x.get_callback())) for event in events: print(event.get_callback(), event.timeout) def run_test(self, dt): # set the title bar, just for fun. :) self.mc.title = str(self._test_name) if not self.mc.is_init_done: if self._test_started + self.max_test_setup_secs < time(): self.dump_clock() self.fail("Test setup took more than {} seconds.".format( self.max_test_setup_secs)) Clock.schedule_once(self.run_test, 0) return return super().run(self._test) 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.mc.events.remove_handler_by_event( event=event_name, handler=self._mock_event_handler) self.mc.events.add_handler(event=event_name, handler=self._mock_event_handler, event_name=event_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: 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 reset_mock_events(self): for event in self._events.keys(): self._events[event] = 0