1# Lint as: python2, python3 2# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6import gobject, logging, sys, traceback 7 8import common 9 10from autotest_lib.client.common_lib import error 11 12# TODO(rochberg): Take another shot at fixing glib to allow this 13# behavior when desired 14def ExceptionForward(func): 15 """Decorator that saves exceptions for forwarding across a glib 16 mainloop. 17 18 Exceptions thrown by glib callbacks are swallowed if they reach the 19 glib main loop. This decorator collaborates with 20 ExceptionForwardingMainLoop to save those exceptions so that it can 21 reraise them.""" 22 def wrapper(self, *args, **kwargs): 23 try: 24 return func(self, *args, **kwargs) 25 except Exception as e: 26 logging.warning('Saving exception: %s' % e) 27 logging.warning(''.join(traceback.format_exception(*sys.exc_info()))) 28 self._forwarded_exception = e 29 self.main_loop.quit() 30 return False 31 return wrapper 32 33class ExceptionForwardingMainLoop(object): 34 """Wraps a glib mainloop so that exceptions raised by functions 35 called by the mainloop cause the mainloop to terminate and reraise 36 the exception. 37 38 Any function called by the main loop (including dbus callbacks and 39 glib callbacks like add_idle) must be wrapped in the 40 @ExceptionForward decorator.""" 41 42 def __init__(self, main_loop, timeout_s=-1): 43 self._forwarded_exception = None 44 self.main_loop = main_loop 45 if timeout_s == -1: 46 logging.warning('ExceptionForwardingMainLoop: No timeout specified.') 47 logging.warning('(Specify timeout_s=0 explicitly for no timeout.)') 48 self.timeout_s = timeout_s 49 50 def idle(self): 51 raise Exception('idle must be overridden') 52 53 def timeout(self): 54 pass 55 56 @ExceptionForward 57 def _timeout(self): 58 self.timeout() 59 raise error.TestFail('main loop timed out') 60 61 def quit(self): 62 self.main_loop.quit() 63 64 def run(self): 65 gobject.idle_add(self.idle) 66 if self.timeout_s > 0: 67 timeout_source = gobject.timeout_add(self.timeout_s * 1000, self._timeout) 68 self.main_loop.run() 69 if self.timeout_s > 0: 70 gobject.source_remove(timeout_source) 71 72 if self._forwarded_exception: 73 raise self._forwarded_exception 74 75class GenericTesterMainLoop(ExceptionForwardingMainLoop): 76 """Runs a glib mainloop until it times out or all requirements are 77 satisfied.""" 78 79 def __init__(self, test, main_loop, **kwargs): 80 super(GenericTesterMainLoop, self).__init__(main_loop, **kwargs) 81 self.test = test 82 self.property_changed_actions = {} 83 84 def idle(self): 85 self.perform_one_test() 86 87 def perform_one_test(self): 88 """Subclasses override this function to do their testing.""" 89 raise Exception('perform_one_test must be overridden') 90 91 def after_main_loop(self): 92 """Children can override this to clean up after the main loop.""" 93 pass 94 95 def build_error_handler(self, name): 96 """Returns a closure that fails the test with the specified name.""" 97 @ExceptionForward 98 def to_return(self, e): 99 raise error.TestFail('Dbus call %s failed: %s' % (name, e)) 100 # Bind the returned handler function to this object 101 return to_return.__get__(self, GenericTesterMainLoop) 102 103 @ExceptionForward 104 def ignore_handler(*ignored_args, **ignored_kwargs): 105 pass 106 107 def requirement_completed(self, requirement, warn_if_already_completed=True): 108 """Record that a requirement was completed. Exit if all are.""" 109 should_log = True 110 try: 111 self.remaining_requirements.remove(requirement) 112 except KeyError: 113 if warn_if_already_completed: 114 logging.warning('requirement %s was not present to be completed', 115 requirement) 116 else: 117 should_log = False 118 119 if not self.remaining_requirements: 120 logging.info('All requirements satisfied') 121 self.quit() 122 else: 123 if should_log: 124 logging.info('Requirement %s satisfied. Remaining: %s' % 125 (requirement, self.remaining_requirements)) 126 127 def timeout(self): 128 logging.error('Requirements unsatisfied upon timeout: %s' % 129 self.remaining_requirements) 130 131 @ExceptionForward 132 def dispatch_property_changed(self, property, *args, **kwargs): 133 action = self.property_changed_actions.pop(property, None) 134 if action: 135 logging.info('Property_changed dispatching %s' % property) 136 action(property, *args, **kwargs) 137 138 def assert_(self, arg): 139 self.test.assert_(self, arg) 140 141 def run(self, *args, **kwargs): 142 self.test_args = args 143 self.test_kwargs = kwargs 144 ExceptionForwardingMainLoop.run(self) 145 self.after_main_loop() 146