diff options
Diffstat (limited to 'media/webrtc/trunk/build/android/emulator.py')
-rwxr-xr-x | media/webrtc/trunk/build/android/emulator.py | 321 |
1 files changed, 321 insertions, 0 deletions
diff --git a/media/webrtc/trunk/build/android/emulator.py b/media/webrtc/trunk/build/android/emulator.py new file mode 100755 index 000000000..77c9a75da --- /dev/null +++ b/media/webrtc/trunk/build/android/emulator.py @@ -0,0 +1,321 @@ +#!/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. + +"""Provides an interface to start and stop Android emulator. + +Assumes system environment ANDROID_NDK_ROOT has been set. + + Emulator: The class provides the methods to launch/shutdown the emulator with + the android virtual device named 'avd_armeabi' . +""" + +import logging +import os +import signal +import subprocess +import sys +import time + +from pylib import android_commands +from pylib import cmd_helper + +# adb_interface.py is under ../../third_party/android_testrunner/ +sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), '..', + '..', 'third_party', 'android_testrunner')) +import adb_interface +import errors +import run_command + +class EmulatorLaunchException(Exception): + """Emulator failed to launch.""" + pass + +def _KillAllEmulators(): + """Kill all running emulators that look like ones we started. + + There are odd 'sticky' cases where there can be no emulator process + running but a device slot is taken. A little bot trouble and and + we're out of room forever. + """ + emulators = android_commands.GetEmulators() + if not emulators: + return + for emu_name in emulators: + cmd_helper.GetCmdOutput(['adb', '-s', emu_name, 'emu', 'kill']) + logging.info('Emulator killing is async; give a few seconds for all to die.') + for i in range(5): + if not android_commands.GetEmulators(): + return + time.sleep(1) + + +def DeleteAllTempAVDs(): + """Delete all temporary AVDs which are created for tests. + + If the test exits abnormally and some temporary AVDs created when testing may + be left in the system. Clean these AVDs. + """ + avds = android_commands.GetAVDs() + if not avds: + return + for avd_name in avds: + if 'run_tests_avd' in avd_name: + cmd = ['android', '-s', 'delete', 'avd', '--name', avd_name] + cmd_helper.GetCmdOutput(cmd) + logging.info('Delete AVD %s' % avd_name) + + +class PortPool(object): + """Pool for emulator port starting position that changes over time.""" + _port_min = 5554 + _port_max = 5585 + _port_current_index = 0 + + @classmethod + def port_range(cls): + """Return a range of valid ports for emulator use. + + The port must be an even number between 5554 and 5584. Sometimes + a killed emulator "hangs on" to a port long enough to prevent + relaunch. This is especially true on slow machines (like a bot). + Cycling through a port start position helps make us resilient.""" + ports = range(cls._port_min, cls._port_max, 2) + n = cls._port_current_index + cls._port_current_index = (n + 1) % len(ports) + return ports[n:] + ports[:n] + + +def _GetAvailablePort(): + """Returns an available TCP port for the console.""" + used_ports = [] + emulators = android_commands.GetEmulators() + for emulator in emulators: + used_ports.append(emulator.split('-')[1]) + for port in PortPool.port_range(): + if str(port) not in used_ports: + return port + + +class Emulator(object): + """Provides the methods to lanuch/shutdown the emulator. + + The emulator has the android virtual device named 'avd_armeabi'. + + The emulator could use any even TCP port between 5554 and 5584 for the + console communication, and this port will be part of the device name like + 'emulator-5554'. Assume it is always True, as the device name is the id of + emulator managed in this class. + + Attributes: + emulator: Path of Android's emulator tool. + popen: Popen object of the running emulator process. + device: Device name of this emulator. + """ + + # Signals we listen for to kill the emulator on + _SIGNALS = (signal.SIGINT, signal.SIGHUP) + + # Time to wait for an emulator launch, in seconds. This includes + # the time to launch the emulator and a wait-for-device command. + _LAUNCH_TIMEOUT = 120 + + # Timeout interval of wait-for-device command before bouncing to a a + # process life check. + _WAITFORDEVICE_TIMEOUT = 5 + + # Time to wait for a "wait for boot complete" (property set on device). + _WAITFORBOOT_TIMEOUT = 300 + + def __init__(self, new_avd_name, fast_and_loose): + """Init an Emulator. + + Args: + nwe_avd_name: If set, will create a new temporary AVD. + fast_and_loose: Loosen up the rules for reliable running for speed. + Intended for quick testing or re-testing. + + """ + try: + android_sdk_root = os.environ['ANDROID_SDK_ROOT'] + except KeyError: + logging.critical('The ANDROID_SDK_ROOT must be set to run the test on ' + 'emulator.') + raise + self.emulator = os.path.join(android_sdk_root, 'tools', 'emulator') + self.android = os.path.join(android_sdk_root, 'tools', 'android') + self.popen = None + self.device = None + self.default_avd = True + self.fast_and_loose = fast_and_loose + self.abi = 'armeabi-v7a' + self.avd = 'avd_armeabi' + if 'x86' in os.environ.get('TARGET_PRODUCT', ''): + self.abi = 'x86' + self.avd = 'avd_x86' + if new_avd_name: + self.default_avd = False + self.avd = self._CreateAVD(new_avd_name) + + def _DeviceName(self): + """Return our device name.""" + port = _GetAvailablePort() + return ('emulator-%d' % port, port) + + def _CreateAVD(self, avd_name): + """Creates an AVD with the given name. + + Return avd_name. + """ + avd_command = [ + self.android, + '--silent', + 'create', 'avd', + '--name', avd_name, + '--abi', self.abi, + '--target', 'android-16', + '-c', '128M', + '--force', + ] + avd_process = subprocess.Popen(args=avd_command, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + avd_process.stdin.write('no\n') + avd_process.wait() + logging.info('Create AVD command: %s', ' '.join(avd_command)) + return avd_name + + def _DeleteAVD(self): + """Delete the AVD of this emulator.""" + avd_command = [ + self.android, + '--silent', + 'delete', + 'avd', + '--name', self.avd, + ] + avd_process = subprocess.Popen(args=avd_command, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + logging.info('Delete AVD command: %s', ' '.join(avd_command)) + avd_process.wait() + + def Launch(self, kill_all_emulators): + """Launches the emulator asynchronously. Call ConfirmLaunch() to ensure the + emulator is ready for use. + + If fails, an exception will be raised. + """ + if kill_all_emulators: + _KillAllEmulators() # just to be sure + if not self.fast_and_loose: + self._AggressiveImageCleanup() + (self.device, port) = self._DeviceName() + emulator_command = [ + self.emulator, + # Speed up emulator launch by 40%. Really. + '-no-boot-anim', + # The default /data size is 64M. + # That's not enough for 8 unit test bundles and their data. + '-partition-size', '512', + # Enable GPU by default. + '-gpu', 'on', + # Use a familiar name and port. + '-avd', self.avd, + '-port', str(port)] + if not self.fast_and_loose: + emulator_command.extend([ + # Wipe the data. We've seen cases where an emulator + # gets 'stuck' if we don't do this (every thousand runs or + # so). + '-wipe-data', + ]) + logging.info('Emulator launch command: %s', ' '.join(emulator_command)) + self.popen = subprocess.Popen(args=emulator_command, + stderr=subprocess.STDOUT) + self._InstallKillHandler() + + def _AggressiveImageCleanup(self): + """Aggressive cleanup of emulator images. + + Experimentally it looks like our current emulator use on the bot + leaves image files around in /tmp/android-$USER. If a "random" + name gets reused, we choke with a 'File exists' error. + TODO(jrg): is there a less hacky way to accomplish the same goal? + """ + logging.info('Aggressive Image Cleanup') + emulator_imagedir = '/tmp/android-%s' % os.environ['USER'] + if not os.path.exists(emulator_imagedir): + return + for image in os.listdir(emulator_imagedir): + full_name = os.path.join(emulator_imagedir, image) + if 'emulator' in full_name: + logging.info('Deleting emulator image %s', full_name) + os.unlink(full_name) + + def ConfirmLaunch(self, wait_for_boot=False): + """Confirm the emulator launched properly. + + Loop on a wait-for-device with a very small timeout. On each + timeout, check the emulator process is still alive. + After confirming a wait-for-device can be successful, make sure + it returns the right answer. + """ + seconds_waited = 0 + number_of_waits = 2 # Make sure we can wfd twice + adb_cmd = "adb -s %s %s" % (self.device, 'wait-for-device') + while seconds_waited < self._LAUNCH_TIMEOUT: + try: + run_command.RunCommand(adb_cmd, + timeout_time=self._WAITFORDEVICE_TIMEOUT, + retry_count=1) + number_of_waits -= 1 + if not number_of_waits: + break + except errors.WaitForResponseTimedOutError as e: + seconds_waited += self._WAITFORDEVICE_TIMEOUT + adb_cmd = "adb -s %s %s" % (self.device, 'kill-server') + run_command.RunCommand(adb_cmd) + self.popen.poll() + if self.popen.returncode != None: + raise EmulatorLaunchException('EMULATOR DIED') + if seconds_waited >= self._LAUNCH_TIMEOUT: + raise EmulatorLaunchException('TIMEOUT with wait-for-device') + logging.info('Seconds waited on wait-for-device: %d', seconds_waited) + if wait_for_boot: + # Now that we checked for obvious problems, wait for a boot complete. + # Waiting for the package manager is sometimes problematic. + a = android_commands.AndroidCommands(self.device) + a.WaitForSystemBootCompleted(self._WAITFORBOOT_TIMEOUT) + + def Shutdown(self): + """Shuts down the process started by launch.""" + if not self.default_avd: + self._DeleteAVD() + if self.popen: + self.popen.poll() + if self.popen.returncode == None: + self.popen.kill() + self.popen = None + + def _ShutdownOnSignal(self, signum, frame): + logging.critical('emulator _ShutdownOnSignal') + for sig in self._SIGNALS: + signal.signal(sig, signal.SIG_DFL) + self.Shutdown() + raise KeyboardInterrupt # print a stack + + def _InstallKillHandler(self): + """Install a handler to kill the emulator when we exit unexpectedly.""" + for sig in self._SIGNALS: + signal.signal(sig, self._ShutdownOnSignal) + +def main(argv): + Emulator(None, True).Launch(True) + + +if __name__ == '__main__': + main(sys.argv) |