Index: build/android/emulator.py |
diff --git a/build/android/emulator.py b/build/android/emulator.py |
index 0cbe4c946c5c9a30a86528446acec25f7b506500..1c4fb55b18bfee4f6330b997cfd85552e55ddf94 100755 |
--- a/build/android/emulator.py |
+++ b/build/android/emulator.py |
@@ -4,311 +4,28 @@ |
# 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. |
+"""Script to launch Android emulators. |
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 optparse |
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): |
- """Init an Emulator. |
- |
- Args: |
- nwe_avd_name: If set, will create a new temporary AVD. |
- """ |
- 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.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 |
- 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)] |
- 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 |
+from pylib.utils import emulator |
- 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) |
+ option_parser = optparse.OptionParser() |
+ option_parser.add_option('-n', '--num', dest='emulator_count', |
+ help='Number of emulators to launch.', |
+ type='int', |
+ default=1) |
+ option_parser.add_option('-w', '--wait', dest='wait_for_boot', |
+ action='store_true', |
+ help='If set, wait for the emulators to boot.') |
+ options, args = option_parser.parse_args(argv) |
+ emulator.LaunchEmulators(options.emulator_count, options.wait_for_boot) |
if __name__ == '__main__': |