You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
146 lines
4.6 KiB
146 lines
4.6 KiB
# Lint as: python2, python3
|
|
# 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 as 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()
|