• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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