# 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.

"""Functions to setup xvfb, which is used by the linux machines.
"""

import os
import signal
import subprocess
import tempfile
import time


def xvfb_display_index(_child_build_name):
  return '9'


def xvfb_pid_filename(child_build_name):
  """Returns the filename to the Xvfb pid file.  This name is unique for each
  builder. This is used by the linux builders."""
  return os.path.join(
      tempfile.gettempdir(),
      'xvfb-' + xvfb_display_index(child_build_name) + '.pid'
  )


def start_virtual_x(child_build_name, build_dir):
  """Start a virtual X server and set the DISPLAY environment variable so sub
  processes will use the virtual X server.  Also start openbox. This only works
  on Linux and assumes that xvfb and openbox are installed.

  Args:
    child_build_name: The name of the build that we use for the pid file.
        E.g., webkit-rel-linux.
    build_dir: The directory where binaries are produced.  If this is non-empty,
        we try running xdisplaycheck from |build_dir| to verify our X
        connection.
  """
  # We use a pid file to make sure we don't have any xvfb processes running
  # from a previous test run.
  stop_virtual_x(child_build_name)

  xdisplaycheck_path = None
  if build_dir:
    xdisplaycheck_path = os.path.join(build_dir, 'xdisplaycheck')

  display = ':%s' % xvfb_display_index(child_build_name)
  # Note we don't add the optional screen here (+ '.0')
  os.environ['DISPLAY'] = display

  # Parts of Xvfb use a hard-coded "/tmp" for its temporary directory.
  # This can cause a failure when those parts expect to hardlink against
  # files that were created in "TEMPDIR" / "TMPDIR".
  #
  # See: https://crbug.com/715848
  env = os.environ.copy()
  if env.get('TMPDIR') and env['TMPDIR'] != '/tmp':
    print('Overriding TMPDIR to "/tmp" for Xvfb, was: %s' % (env['TMPDIR'],))
    env['TMPDIR'] = '/tmp'

  if xdisplaycheck_path and os.path.exists(xdisplaycheck_path):
    print('Verifying Xvfb is not running ...')
    checkstarttime = time.time()
    xdisplayproc = subprocess.Popen([xdisplaycheck_path, '--noserver'],
                                    stdout=subprocess.PIPE,
                                    stderr=subprocess.STDOUT,
                                    env=env)
    # Wait for xdisplaycheck to exit.
    logs = xdisplayproc.communicate()[0]
    if xdisplayproc.returncode == 0:
      print('xdisplaycheck says there is a display still running, exiting...')
      raise Exception('Display already present.')

    xvfb_lock_filename = '/tmp/.X%s-lock' % xvfb_display_index(child_build_name)
    if os.path.exists(xvfb_lock_filename):
      print('Removing stale xvfb lock file %r' % xvfb_lock_filename)
      try:
        os.unlink(xvfb_lock_filename)
      except OSError as err:
        print('Removing xvfb lock file failed: %s' % err)

  # Figure out which X server to try.
  cmd = 'Xvfb'

  # Start a virtual X server that we run the tests in.  This makes it so we can
  # run the tests even if we didn't start the tests from an X session.
  proc = subprocess.Popen([
      cmd, display, '-screen', '0', '1280x800x24', '-ac', '-dpi', '96',
      '-maxclients', '512', '-extension', 'MIT-SHM'
  ],
                          stdout=subprocess.PIPE,
                          stderr=subprocess.STDOUT,
                          env=env)
  pid_filename = xvfb_pid_filename(child_build_name)
  open(pid_filename, 'w').write(str(proc.pid))

  # Wait for Xvfb to start up.
  time.sleep(10)

  # Verify that Xvfb has started by using xdisplaycheck.
  if xdisplaycheck_path and os.path.exists(xdisplaycheck_path):
    print('Verifying Xvfb has started...')
    checkstarttime = time.time()
    xdisplayproc = subprocess.Popen([xdisplaycheck_path],
                                    stdout=subprocess.PIPE,
                                    stderr=subprocess.STDOUT)
    # Wait for xdisplaycheck to exit.
    logs = xdisplayproc.communicate()[0]
    checktime = time.time() - checkstarttime
    if xdisplayproc.returncode != 0:
      print('xdisplaycheck failed after %d seconds.' % checktime)
      print('xdisplaycheck output:')
      for line in logs.splitlines():
        print('> %s' % line)
      return_code = proc.poll()
      if return_code is None:
        print('Xvfb still running, stopping.')
        proc.terminate()
      else:
        print('Xvfb exited, code %d' % return_code)

      print('Xvfb output:')
      for line in proc.communicate()[0].splitlines():
        print('> %s' % line)
      raise Exception(logs)

    print('xdisplaycheck succeeded after %d seconds.' % checktime)
    print('xdisplaycheck output:')
    for line in logs.splitlines():
      print('> %s' % line)
    print('...OK')

  # Some ChromeOS tests need a window manager.
  subprocess.Popen('openbox', stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
  print('Window manager (openbox) started.')


def stop_virtual_x(child_build_name):
  """Try and stop the virtual X server if one was started with StartVirtualX.
  When the X server dies, it takes down the window manager with it.
  If a virtual x server is not running, this method does nothing."""
  pid_filename = xvfb_pid_filename(child_build_name)
  if os.path.exists(pid_filename):
    xvfb_pid = int(open(pid_filename).read())
    print('Stopping Xvfb with pid %d ...' % xvfb_pid)
    # If the process doesn't exist, we raise an exception that we can ignore.
    try:
      os.kill(xvfb_pid, signal.SIGKILL)
    except OSError:
      print('... killing failed, presuming unnecessary.')
    os.remove(pid_filename)
    print('Xvfb pid file removed')
