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