# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import gobject, logging, sys, traceback import common from autotest_lib.client.common_lib import error # TODO(rochberg): Take another shot at fixing glib to allow this # behavior when desired def ExceptionForward(func): """Decorator that saves exceptions for forwarding across a glib mainloop. Exceptions thrown by glib callbacks are swallowed if they reach the glib main loop. This decorator collaborates with ExceptionForwardingMainLoop to save those exceptions so that it can reraise them.""" def wrapper(self, *args, **kwargs): try: return func(self, *args, **kwargs) except Exception, e: logging.warning('Saving exception: %s' % e) logging.warning(''.join(traceback.format_exception(*sys.exc_info()))) self._forwarded_exception = e self.main_loop.quit() return False return wrapper class ExceptionForwardingMainLoop(object): """Wraps a glib mainloop so that exceptions raised by functions called by the mainloop cause the mainloop to terminate and reraise the exception. Any function called by the main loop (including dbus callbacks and glib callbacks like add_idle) must be wrapped in the @ExceptionForward decorator.""" def __init__(self, main_loop, timeout_s=-1): self._forwarded_exception = None self.main_loop = main_loop if timeout_s == -1: logging.warning('ExceptionForwardingMainLoop: No timeout specified.') logging.warning('(Specify timeout_s=0 explicitly for no timeout.)') self.timeout_s = timeout_s def idle(self): raise Exception('idle must be overridden') def timeout(self): pass @ExceptionForward def _timeout(self): self.timeout() raise error.TestFail('main loop timed out') def quit(self): self.main_loop.quit() def run(self): gobject.idle_add(self.idle) if self.timeout_s > 0: timeout_source = gobject.timeout_add(self.timeout_s * 1000, self._timeout) self.main_loop.run() if self.timeout_s > 0: gobject.source_remove(timeout_source) if self._forwarded_exception: raise self._forwarded_exception class GenericTesterMainLoop(ExceptionForwardingMainLoop): """Runs a glib mainloop until it times out or all requirements are satisfied.""" def __init__(self, test, main_loop, **kwargs): super(GenericTesterMainLoop, self).__init__(main_loop, **kwargs) self.test = test self.property_changed_actions = {} def idle(self): self.perform_one_test() def perform_one_test(self): """Subclasses override this function to do their testing.""" raise Exception('perform_one_test must be overridden') def after_main_loop(self): """Children can override this to clean up after the main loop.""" pass def build_error_handler(self, name): """Returns a closure that fails the test with the specified name.""" @ExceptionForward def to_return(self, e): raise error.TestFail('Dbus call %s failed: %s' % (name, e)) # Bind the returned handler function to this object return to_return.__get__(self, GenericTesterMainLoop) @ExceptionForward def ignore_handler(*ignored_args, **ignored_kwargs): pass def requirement_completed(self, requirement, warn_if_already_completed=True): """Record that a requirement was completed. Exit if all are.""" should_log = True try: self.remaining_requirements.remove(requirement) except KeyError: if warn_if_already_completed: logging.warning('requirement %s was not present to be completed', requirement) else: should_log = False if not self.remaining_requirements: logging.info('All requirements satisfied') self.quit() else: if should_log: logging.info('Requirement %s satisfied. Remaining: %s' % (requirement, self.remaining_requirements)) def timeout(self): logging.error('Requirements unsatisfied upon timeout: %s' % self.remaining_requirements) @ExceptionForward def dispatch_property_changed(self, property, *args, **kwargs): action = self.property_changed_actions.pop(property, None) if action: logging.info('Property_changed dispatching %s' % property) action(property, *args, **kwargs) def assert_(self, arg): self.test.assert_(self, arg) def run(self, *args, **kwargs): self.test_args = args self.test_kwargs = kwargs ExceptionForwardingMainLoop.run(self) self.after_main_loop()