media/webrtc/trunk/build/android/emulator.py

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/media/webrtc/trunk/build/android/emulator.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,321 @@
     1.4 +#!/usr/bin/env python
     1.5 +#
     1.6 +# Copyright (c) 2012 The Chromium Authors. All rights reserved.
     1.7 +# Use of this source code is governed by a BSD-style license that can be
     1.8 +# found in the LICENSE file.
     1.9 +
    1.10 +"""Provides an interface to start and stop Android emulator.
    1.11 +
    1.12 +Assumes system environment ANDROID_NDK_ROOT has been set.
    1.13 +
    1.14 +  Emulator: The class provides the methods to launch/shutdown the emulator with
    1.15 +            the android virtual device named 'avd_armeabi' .
    1.16 +"""
    1.17 +
    1.18 +import logging
    1.19 +import os
    1.20 +import signal
    1.21 +import subprocess
    1.22 +import sys
    1.23 +import time
    1.24 +
    1.25 +from pylib import android_commands
    1.26 +from pylib import cmd_helper
    1.27 +
    1.28 +# adb_interface.py is under ../../third_party/android_testrunner/
    1.29 +sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), '..',
    1.30 +   '..', 'third_party', 'android_testrunner'))
    1.31 +import adb_interface
    1.32 +import errors
    1.33 +import run_command
    1.34 +
    1.35 +class EmulatorLaunchException(Exception):
    1.36 +  """Emulator failed to launch."""
    1.37 +  pass
    1.38 +
    1.39 +def _KillAllEmulators():
    1.40 +  """Kill all running emulators that look like ones we started.
    1.41 +
    1.42 +  There are odd 'sticky' cases where there can be no emulator process
    1.43 +  running but a device slot is taken.  A little bot trouble and and
    1.44 +  we're out of room forever.
    1.45 +  """
    1.46 +  emulators = android_commands.GetEmulators()
    1.47 +  if not emulators:
    1.48 +    return
    1.49 +  for emu_name in emulators:
    1.50 +    cmd_helper.GetCmdOutput(['adb', '-s', emu_name, 'emu', 'kill'])
    1.51 +  logging.info('Emulator killing is async; give a few seconds for all to die.')
    1.52 +  for i in range(5):
    1.53 +    if not android_commands.GetEmulators():
    1.54 +      return
    1.55 +    time.sleep(1)
    1.56 +
    1.57 +
    1.58 +def DeleteAllTempAVDs():
    1.59 +  """Delete all temporary AVDs which are created for tests.
    1.60 +
    1.61 +  If the test exits abnormally and some temporary AVDs created when testing may
    1.62 +  be left in the system. Clean these AVDs.
    1.63 +  """
    1.64 +  avds = android_commands.GetAVDs()
    1.65 +  if not avds:
    1.66 +    return
    1.67 +  for avd_name in avds:
    1.68 +    if 'run_tests_avd' in avd_name:
    1.69 +      cmd = ['android', '-s', 'delete', 'avd', '--name', avd_name]
    1.70 +      cmd_helper.GetCmdOutput(cmd)
    1.71 +      logging.info('Delete AVD %s' % avd_name)
    1.72 +
    1.73 +
    1.74 +class PortPool(object):
    1.75 +  """Pool for emulator port starting position that changes over time."""
    1.76 +  _port_min = 5554
    1.77 +  _port_max = 5585
    1.78 +  _port_current_index = 0
    1.79 +
    1.80 +  @classmethod
    1.81 +  def port_range(cls):
    1.82 +    """Return a range of valid ports for emulator use.
    1.83 +
    1.84 +    The port must be an even number between 5554 and 5584.  Sometimes
    1.85 +    a killed emulator "hangs on" to a port long enough to prevent
    1.86 +    relaunch.  This is especially true on slow machines (like a bot).
    1.87 +    Cycling through a port start position helps make us resilient."""
    1.88 +    ports = range(cls._port_min, cls._port_max, 2)
    1.89 +    n = cls._port_current_index
    1.90 +    cls._port_current_index = (n + 1) % len(ports)
    1.91 +    return ports[n:] + ports[:n]
    1.92 +
    1.93 +
    1.94 +def _GetAvailablePort():
    1.95 +  """Returns an available TCP port for the console."""
    1.96 +  used_ports = []
    1.97 +  emulators = android_commands.GetEmulators()
    1.98 +  for emulator in emulators:
    1.99 +    used_ports.append(emulator.split('-')[1])
   1.100 +  for port in PortPool.port_range():
   1.101 +    if str(port) not in used_ports:
   1.102 +      return port
   1.103 +
   1.104 +
   1.105 +class Emulator(object):
   1.106 +  """Provides the methods to lanuch/shutdown the emulator.
   1.107 +
   1.108 +  The emulator has the android virtual device named 'avd_armeabi'.
   1.109 +
   1.110 +  The emulator could use any even TCP port between 5554 and 5584 for the
   1.111 +  console communication, and this port will be part of the device name like
   1.112 +  'emulator-5554'. Assume it is always True, as the device name is the id of
   1.113 +  emulator managed in this class.
   1.114 +
   1.115 +  Attributes:
   1.116 +    emulator: Path of Android's emulator tool.
   1.117 +    popen: Popen object of the running emulator process.
   1.118 +    device: Device name of this emulator.
   1.119 +  """
   1.120 +
   1.121 +  # Signals we listen for to kill the emulator on
   1.122 +  _SIGNALS = (signal.SIGINT, signal.SIGHUP)
   1.123 +
   1.124 +  # Time to wait for an emulator launch, in seconds.  This includes
   1.125 +  # the time to launch the emulator and a wait-for-device command.
   1.126 +  _LAUNCH_TIMEOUT = 120
   1.127 +
   1.128 +  # Timeout interval of wait-for-device command before bouncing to a a
   1.129 +  # process life check.
   1.130 +  _WAITFORDEVICE_TIMEOUT = 5
   1.131 +
   1.132 +  # Time to wait for a "wait for boot complete" (property set on device).
   1.133 +  _WAITFORBOOT_TIMEOUT = 300
   1.134 +
   1.135 +  def __init__(self, new_avd_name, fast_and_loose):
   1.136 +    """Init an Emulator.
   1.137 +
   1.138 +    Args:
   1.139 +      nwe_avd_name: If set, will create a new temporary AVD.
   1.140 +      fast_and_loose: Loosen up the rules for reliable running for speed.
   1.141 +        Intended for quick testing or re-testing.
   1.142 +
   1.143 +    """
   1.144 +    try:
   1.145 +      android_sdk_root = os.environ['ANDROID_SDK_ROOT']
   1.146 +    except KeyError:
   1.147 +      logging.critical('The ANDROID_SDK_ROOT must be set to run the test on '
   1.148 +                       'emulator.')
   1.149 +      raise
   1.150 +    self.emulator = os.path.join(android_sdk_root, 'tools', 'emulator')
   1.151 +    self.android = os.path.join(android_sdk_root, 'tools', 'android')
   1.152 +    self.popen = None
   1.153 +    self.device = None
   1.154 +    self.default_avd = True
   1.155 +    self.fast_and_loose = fast_and_loose
   1.156 +    self.abi = 'armeabi-v7a'
   1.157 +    self.avd = 'avd_armeabi'
   1.158 +    if 'x86' in os.environ.get('TARGET_PRODUCT', ''):
   1.159 +      self.abi = 'x86'
   1.160 +      self.avd = 'avd_x86'
   1.161 +    if new_avd_name:
   1.162 +      self.default_avd = False
   1.163 +      self.avd = self._CreateAVD(new_avd_name)
   1.164 +
   1.165 +  def _DeviceName(self):
   1.166 +    """Return our device name."""
   1.167 +    port = _GetAvailablePort()
   1.168 +    return ('emulator-%d' % port, port)
   1.169 +
   1.170 +  def _CreateAVD(self, avd_name):
   1.171 +    """Creates an AVD with the given name.
   1.172 +
   1.173 +    Return avd_name.
   1.174 +    """
   1.175 +    avd_command = [
   1.176 +        self.android,
   1.177 +        '--silent',
   1.178 +        'create', 'avd',
   1.179 +        '--name', avd_name,
   1.180 +        '--abi', self.abi,
   1.181 +        '--target', 'android-16',
   1.182 +        '-c', '128M',
   1.183 +        '--force',
   1.184 +    ]
   1.185 +    avd_process = subprocess.Popen(args=avd_command,
   1.186 +                                   stdin=subprocess.PIPE,
   1.187 +                                   stdout=subprocess.PIPE,
   1.188 +                                   stderr=subprocess.STDOUT)
   1.189 +    avd_process.stdin.write('no\n')
   1.190 +    avd_process.wait()
   1.191 +    logging.info('Create AVD command: %s', ' '.join(avd_command))
   1.192 +    return avd_name
   1.193 +
   1.194 +  def _DeleteAVD(self):
   1.195 +    """Delete the AVD of this emulator."""
   1.196 +    avd_command = [
   1.197 +        self.android,
   1.198 +        '--silent',
   1.199 +        'delete',
   1.200 +        'avd',
   1.201 +        '--name', self.avd,
   1.202 +    ]
   1.203 +    avd_process = subprocess.Popen(args=avd_command,
   1.204 +                                   stdout=subprocess.PIPE,
   1.205 +                                   stderr=subprocess.STDOUT)
   1.206 +    logging.info('Delete AVD command: %s', ' '.join(avd_command))
   1.207 +    avd_process.wait()
   1.208 +
   1.209 +  def Launch(self, kill_all_emulators):
   1.210 +    """Launches the emulator asynchronously. Call ConfirmLaunch() to ensure the
   1.211 +    emulator is ready for use.
   1.212 +
   1.213 +    If fails, an exception will be raised.
   1.214 +    """
   1.215 +    if kill_all_emulators:
   1.216 +      _KillAllEmulators()  # just to be sure
   1.217 +    if not self.fast_and_loose:
   1.218 +      self._AggressiveImageCleanup()
   1.219 +    (self.device, port) = self._DeviceName()
   1.220 +    emulator_command = [
   1.221 +        self.emulator,
   1.222 +        # Speed up emulator launch by 40%.  Really.
   1.223 +        '-no-boot-anim',
   1.224 +        # The default /data size is 64M.
   1.225 +        # That's not enough for 8 unit test bundles and their data.
   1.226 +        '-partition-size', '512',
   1.227 +        # Enable GPU by default.
   1.228 +        '-gpu', 'on',
   1.229 +        # Use a familiar name and port.
   1.230 +        '-avd', self.avd,
   1.231 +        '-port', str(port)]
   1.232 +    if not self.fast_and_loose:
   1.233 +      emulator_command.extend([
   1.234 +          # Wipe the data.  We've seen cases where an emulator
   1.235 +          # gets 'stuck' if we don't do this (every thousand runs or
   1.236 +          # so).
   1.237 +          '-wipe-data',
   1.238 +          ])
   1.239 +    logging.info('Emulator launch command: %s', ' '.join(emulator_command))
   1.240 +    self.popen = subprocess.Popen(args=emulator_command,
   1.241 +                                  stderr=subprocess.STDOUT)
   1.242 +    self._InstallKillHandler()
   1.243 +
   1.244 +  def _AggressiveImageCleanup(self):
   1.245 +    """Aggressive cleanup of emulator images.
   1.246 +
   1.247 +    Experimentally it looks like our current emulator use on the bot
   1.248 +    leaves image files around in /tmp/android-$USER.  If a "random"
   1.249 +    name gets reused, we choke with a 'File exists' error.
   1.250 +    TODO(jrg): is there a less hacky way to accomplish the same goal?
   1.251 +    """
   1.252 +    logging.info('Aggressive Image Cleanup')
   1.253 +    emulator_imagedir = '/tmp/android-%s' % os.environ['USER']
   1.254 +    if not os.path.exists(emulator_imagedir):
   1.255 +      return
   1.256 +    for image in os.listdir(emulator_imagedir):
   1.257 +      full_name = os.path.join(emulator_imagedir, image)
   1.258 +      if 'emulator' in full_name:
   1.259 +        logging.info('Deleting emulator image %s', full_name)
   1.260 +        os.unlink(full_name)
   1.261 +
   1.262 +  def ConfirmLaunch(self, wait_for_boot=False):
   1.263 +    """Confirm the emulator launched properly.
   1.264 +
   1.265 +    Loop on a wait-for-device with a very small timeout.  On each
   1.266 +    timeout, check the emulator process is still alive.
   1.267 +    After confirming a wait-for-device can be successful, make sure
   1.268 +    it returns the right answer.
   1.269 +    """
   1.270 +    seconds_waited = 0
   1.271 +    number_of_waits = 2  # Make sure we can wfd twice
   1.272 +    adb_cmd = "adb -s %s %s" % (self.device, 'wait-for-device')
   1.273 +    while seconds_waited < self._LAUNCH_TIMEOUT:
   1.274 +      try:
   1.275 +        run_command.RunCommand(adb_cmd,
   1.276 +                               timeout_time=self._WAITFORDEVICE_TIMEOUT,
   1.277 +                               retry_count=1)
   1.278 +        number_of_waits -= 1
   1.279 +        if not number_of_waits:
   1.280 +          break
   1.281 +      except errors.WaitForResponseTimedOutError as e:
   1.282 +        seconds_waited += self._WAITFORDEVICE_TIMEOUT
   1.283 +        adb_cmd = "adb -s %s %s" % (self.device, 'kill-server')
   1.284 +        run_command.RunCommand(adb_cmd)
   1.285 +      self.popen.poll()
   1.286 +      if self.popen.returncode != None:
   1.287 +        raise EmulatorLaunchException('EMULATOR DIED')
   1.288 +    if seconds_waited >= self._LAUNCH_TIMEOUT:
   1.289 +      raise EmulatorLaunchException('TIMEOUT with wait-for-device')
   1.290 +    logging.info('Seconds waited on wait-for-device: %d', seconds_waited)
   1.291 +    if wait_for_boot:
   1.292 +      # Now that we checked for obvious problems, wait for a boot complete.
   1.293 +      # Waiting for the package manager is sometimes problematic.
   1.294 +      a = android_commands.AndroidCommands(self.device)
   1.295 +      a.WaitForSystemBootCompleted(self._WAITFORBOOT_TIMEOUT)
   1.296 +
   1.297 +  def Shutdown(self):
   1.298 +    """Shuts down the process started by launch."""
   1.299 +    if not self.default_avd:
   1.300 +      self._DeleteAVD()
   1.301 +    if self.popen:
   1.302 +      self.popen.poll()
   1.303 +      if self.popen.returncode == None:
   1.304 +        self.popen.kill()
   1.305 +      self.popen = None
   1.306 +
   1.307 +  def _ShutdownOnSignal(self, signum, frame):
   1.308 +    logging.critical('emulator _ShutdownOnSignal')
   1.309 +    for sig in self._SIGNALS:
   1.310 +      signal.signal(sig, signal.SIG_DFL)
   1.311 +    self.Shutdown()
   1.312 +    raise KeyboardInterrupt  # print a stack
   1.313 +
   1.314 +  def _InstallKillHandler(self):
   1.315 +    """Install a handler to kill the emulator when we exit unexpectedly."""
   1.316 +    for sig in self._SIGNALS:
   1.317 +      signal.signal(sig, self._ShutdownOnSignal)
   1.318 +
   1.319 +def main(argv):
   1.320 +  Emulator(None, True).Launch(True)
   1.321 +
   1.322 +
   1.323 +if __name__ == '__main__':
   1.324 +  main(sys.argv)

mercurial