| #!/usr/bin/env python | 
 | # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
 | # Use of this source code is governed by a BSD-style license that can be | 
 | # found in the LICENSE file. | 
 |  | 
 | """Runs tests with Xvfb and Openbox on Linux and normally on other platforms.""" | 
 |  | 
 | import os | 
 | import os.path | 
 | import platform | 
 | import signal | 
 | import subprocess | 
 | import sys | 
 | import threading | 
 |  | 
 | import test_env | 
 |  | 
 |  | 
 | def _kill(proc, send_signal): | 
 |   """Kills |proc| and ignores exceptions thrown for non-existent processes.""" | 
 |   try: | 
 |     os.kill(proc.pid, send_signal) | 
 |   except OSError: | 
 |     pass | 
 |  | 
 |  | 
 | def kill(proc, timeout_in_seconds=10): | 
 |   """Tries to kill |proc| gracefully with a timeout for each signal.""" | 
 |   if not proc or not proc.pid: | 
 |     return | 
 |  | 
 |   _kill(proc, signal.SIGTERM) | 
 |   thread = threading.Thread(target=proc.wait) | 
 |   thread.start() | 
 |  | 
 |   thread.join(timeout_in_seconds) | 
 |   if thread.is_alive(): | 
 |     print >> sys.stderr, 'Xvfb running after SIGTERM, trying SIGKILL.' | 
 |     _kill(proc, signal.SIGKILL) | 
 |  | 
 |   thread.join(timeout_in_seconds) | 
 |   if thread.is_alive(): | 
 |     print >> sys.stderr, 'Xvfb running after SIGTERM and SIGKILL; good luck!' | 
 |  | 
 |  | 
 | def run_executable(cmd, env, stdoutfile=None): | 
 |   """Runs an executable within Xvfb on Linux or normally on other platforms. | 
 |  | 
 |   If |stdoutfile| is provided, symbolization via script is disabled and stdout | 
 |   is written to this file as well as to stdout. | 
 |  | 
 |   Returns the exit code of the specified commandline, or 1 on failure. | 
 |   """ | 
 |  | 
 |   # It might seem counterintuitive to support a --no-xvfb flag in a script | 
 |   # whose only job is to start xvfb, but doing so allows us to consolidate | 
 |   # the logic in the layers of buildbot scripts so that we *always* use | 
 |   # xvfb by default and don't have to worry about the distinction, it | 
 |   # can remain solely under the control of the test invocation itself. | 
 |   use_xvfb = True | 
 |   if '--no-xvfb' in cmd: | 
 |     use_xvfb = False | 
 |     cmd.remove('--no-xvfb') | 
 |  | 
 |   if sys.platform == 'linux2' and use_xvfb: | 
 |     if env.get('_CHROMIUM_INSIDE_XVFB') == '1': | 
 |       openbox_proc = None | 
 |       xcompmgr_proc = None | 
 |       try: | 
 |         # Some ChromeOS tests need a window manager. | 
 |         openbox_proc = subprocess.Popen('openbox', stdout=subprocess.PIPE, | 
 |                                         stderr=subprocess.STDOUT, env=env) | 
 |  | 
 |         # Some tests need a compositing wm to make use of transparent visuals. | 
 |         xcompmgr_proc = subprocess.Popen('xcompmgr', stdout=subprocess.PIPE, | 
 |                                          stderr=subprocess.STDOUT, env=env) | 
 |  | 
 |         return test_env.run_executable(cmd, env, stdoutfile) | 
 |       except OSError as e: | 
 |         print >> sys.stderr, 'Failed to start Xvfb or Openbox: %s' % str(e) | 
 |         return 1 | 
 |       finally: | 
 |         kill(openbox_proc) | 
 |         kill(xcompmgr_proc) | 
 |     else: | 
 |       env['_CHROMIUM_INSIDE_XVFB'] = '1' | 
 |       if stdoutfile: | 
 |         env['_XVFB_EXECUTABLE_STDOUTFILE'] = stdoutfile | 
 |       xvfb_script = __file__ | 
 |       if xvfb_script.endswith('.pyc'): | 
 |         xvfb_script = xvfb_script[:-1] | 
 |       return subprocess.call(['xvfb-run', '-a', "--server-args=-screen 0 " | 
 |                               "1280x800x24 -ac -nolisten tcp -dpi 96 " | 
 |                               "+extension RANDR", | 
 |                               xvfb_script] + cmd, env=env) | 
 |   else: | 
 |     return test_env.run_executable(cmd, env, stdoutfile) | 
 |  | 
 |  | 
 | def main(): | 
 |   USAGE = 'Usage: xvfb.py [command [--no-xvfb] args...]' | 
 |   if len(sys.argv) < 2: | 
 |     print >> sys.stderr, USAGE | 
 |     return 2 | 
 |  | 
 |   # If the user still thinks the first argument is the execution directory then | 
 |   # print a friendly error message and quit. | 
 |   if os.path.isdir(sys.argv[1]): | 
 |     print >> sys.stderr, ( | 
 |         'Invalid command: \"%s\" is a directory' % sys.argv[1]) | 
 |     print >> sys.stderr, USAGE | 
 |     return 3 | 
 |  | 
 |   stdoutfile = os.environ.get('_XVFB_EXECUTABLE_STDOUTFILE') | 
 |   if stdoutfile: | 
 |     del os.environ['_XVFB_EXECUTABLE_STDOUTFILE'] | 
 |   return run_executable(sys.argv[1:], os.environ.copy(), stdoutfile) | 
 |  | 
 |  | 
 | if __name__ == "__main__": | 
 |   sys.exit(main()) |