Index: build/android/emulator.py |
diff --git a/build/android/emulator.py b/build/android/emulator.py |
index 6aeb3fca3b9cbd138ce532004cdd20f84df1eaf3..b5e4436cd12097cebbc300e39d5a745e565259b5 100755 |
--- a/build/android/emulator.py |
+++ b/build/android/emulator.py |
@@ -13,8 +13,10 @@ Assumes system environment ANDROID_NDK_ROOT has been set. |
import logging |
import os |
+import signal |
import subprocess |
import sys |
+import time |
import android_commands |
@@ -22,7 +24,31 @@ import android_commands |
sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), '..', |
'..', 'third_party', 'android', 'testrunner')) |
import adb_interface |
+import cmd_helper |
+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 _GetAvailablePort(): |
"""Returns an available TCP port for the console.""" |
@@ -52,6 +78,16 @@ class Emulator(object): |
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. |
+ _EMULATOR_LAUNCH_TIMEOUT = 120 |
+ |
+ # Timeout interval of wait-for-device command before bouncing to a a |
+ # process life check. |
+ _EMULATOR_WFD_TIMEOUT = 5 |
+ |
def __init__(self): |
try: |
android_sdk_root = os.environ['ANDROID_SDK_ROOT'] |
@@ -63,22 +99,18 @@ class Emulator(object): |
self.popen = None |
self.device = None |
- def Reset(self): |
- """Kill a running emulator. |
- |
- May be needed if the test scripts stopped abnormally and an |
- emulator is left around.""" |
- adb = adb_interface.AdbInterface() |
- logging.info('Killing any existing emulator.') |
- adb.SendCommand('emu kill') |
+ def _DeviceName(self): |
+ """Return our device name.""" |
+ port = _GetAvailablePort() |
+ return ('emulator-%d' % port, port) |
def Launch(self): |
"""Launches the emulator and waits for package manager to startup. |
If fails, an exception will be raised. |
""" |
- port = _GetAvailablePort() |
- self.device = "emulator-%d" % port |
+ _KillAllEmulators() # just to be sure |
+ (self.device, port) = self._DeviceName() |
self.popen = subprocess.Popen(args=[ |
self.emulator, |
# Speed up emulator launch by 40%. Really. |
@@ -88,15 +120,63 @@ class Emulator(object): |
'-partition-size', '256', |
# Use a familiar name and port. |
'-avd', 'buildbot', |
- '-port', str(port)]) |
- # This will not return until device's package manager starts up or an |
- # exception is raised. |
- android_commands.AndroidCommands(self.device, True) |
+ '-port', str(port)], |
+ stderr=subprocess.STDOUT) |
+ self._InstallKillHandler() |
+ self._ConfirmLaunch() |
+ |
+ def _ConfirmLaunch(self): |
+ """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. |
+ """ |
+ a = android_commands.AndroidCommands(self.device, False) |
+ 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._EMULATOR_LAUNCH_TIMEOUT: |
+ try: |
+ run_command.RunCommand(adb_cmd, timeout_time=self._EMULATOR_WFD_TIMEOUT, |
+ retry_count=1) |
+ number_of_waits -= 1 |
+ if not number_of_waits: |
+ break |
+ except errors.WaitForResponseTimedOutError as e: |
+ seconds_waited += self._EMULATOR_WFD_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._EMULATOR_LAUNCH_TIMEOUT: |
+ raise EmulatorLaunchException('TIMEOUT with wait-for-device') |
+ logging.info('Seconds waited on wait-for-device: %d', seconds_waited) |
+ # Now that we checked for obvious problems, wait for a boot complete. |
+ # Waiting for the package manager has been problematic. |
+ a.Adb().WaitForBootComplete() |
def Shutdown(self): |
"""Shuts down the process started by launch.""" |
- self.popen.terminate() |
- |
+ 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().launch() |