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