| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # | 2 # |
| 3 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 3 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 4 # Use of this source code is governed by a BSD-style license that can be | 4 # Use of this source code is governed by a BSD-style license that can be |
| 5 # found in the LICENSE file. | 5 # found in the LICENSE file. |
| 6 | 6 |
| 7 """Provides an interface to start and stop Android emulator. | 7 """Script to launch Android emulators. |
| 8 | 8 |
| 9 Assumes system environment ANDROID_NDK_ROOT has been set. | 9 Assumes system environment ANDROID_NDK_ROOT has been set. |
| 10 | |
| 11 Emulator: The class provides the methods to launch/shutdown the emulator with | |
| 12 the android virtual device named 'avd_armeabi' . | |
| 13 """ | 10 """ |
| 14 | 11 |
| 15 import logging | 12 import optparse |
| 16 import os | |
| 17 import signal | |
| 18 import subprocess | |
| 19 import sys | 13 import sys |
| 20 import time | |
| 21 | 14 |
| 22 from pylib import android_commands | 15 from pylib.utils import emulator |
| 23 from pylib import cmd_helper | |
| 24 | |
| 25 # adb_interface.py is under ../../third_party/android_testrunner/ | |
| 26 sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), '..', | |
| 27 '..', 'third_party', 'android_testrunner')) | |
| 28 import adb_interface | |
| 29 import errors | |
| 30 import run_command | |
| 31 | |
| 32 class EmulatorLaunchException(Exception): | |
| 33 """Emulator failed to launch.""" | |
| 34 pass | |
| 35 | |
| 36 def _KillAllEmulators(): | |
| 37 """Kill all running emulators that look like ones we started. | |
| 38 | |
| 39 There are odd 'sticky' cases where there can be no emulator process | |
| 40 running but a device slot is taken. A little bot trouble and and | |
| 41 we're out of room forever. | |
| 42 """ | |
| 43 emulators = android_commands.GetEmulators() | |
| 44 if not emulators: | |
| 45 return | |
| 46 for emu_name in emulators: | |
| 47 cmd_helper.GetCmdOutput(['adb', '-s', emu_name, 'emu', 'kill']) | |
| 48 logging.info('Emulator killing is async; give a few seconds for all to die.') | |
| 49 for i in range(5): | |
| 50 if not android_commands.GetEmulators(): | |
| 51 return | |
| 52 time.sleep(1) | |
| 53 | 16 |
| 54 | 17 |
| 55 def DeleteAllTempAVDs(): | |
| 56 """Delete all temporary AVDs which are created for tests. | |
| 57 | |
| 58 If the test exits abnormally and some temporary AVDs created when testing may | |
| 59 be left in the system. Clean these AVDs. | |
| 60 """ | |
| 61 avds = android_commands.GetAVDs() | |
| 62 if not avds: | |
| 63 return | |
| 64 for avd_name in avds: | |
| 65 if 'run_tests_avd' in avd_name: | |
| 66 cmd = ['android', '-s', 'delete', 'avd', '--name', avd_name] | |
| 67 cmd_helper.GetCmdOutput(cmd) | |
| 68 logging.info('Delete AVD %s' % avd_name) | |
| 69 | |
| 70 | |
| 71 class PortPool(object): | |
| 72 """Pool for emulator port starting position that changes over time.""" | |
| 73 _port_min = 5554 | |
| 74 _port_max = 5585 | |
| 75 _port_current_index = 0 | |
| 76 | |
| 77 @classmethod | |
| 78 def port_range(cls): | |
| 79 """Return a range of valid ports for emulator use. | |
| 80 | |
| 81 The port must be an even number between 5554 and 5584. Sometimes | |
| 82 a killed emulator "hangs on" to a port long enough to prevent | |
| 83 relaunch. This is especially true on slow machines (like a bot). | |
| 84 Cycling through a port start position helps make us resilient.""" | |
| 85 ports = range(cls._port_min, cls._port_max, 2) | |
| 86 n = cls._port_current_index | |
| 87 cls._port_current_index = (n + 1) % len(ports) | |
| 88 return ports[n:] + ports[:n] | |
| 89 | |
| 90 | |
| 91 def _GetAvailablePort(): | |
| 92 """Returns an available TCP port for the console.""" | |
| 93 used_ports = [] | |
| 94 emulators = android_commands.GetEmulators() | |
| 95 for emulator in emulators: | |
| 96 used_ports.append(emulator.split('-')[1]) | |
| 97 for port in PortPool.port_range(): | |
| 98 if str(port) not in used_ports: | |
| 99 return port | |
| 100 | |
| 101 | |
| 102 class Emulator(object): | |
| 103 """Provides the methods to lanuch/shutdown the emulator. | |
| 104 | |
| 105 The emulator has the android virtual device named 'avd_armeabi'. | |
| 106 | |
| 107 The emulator could use any even TCP port between 5554 and 5584 for the | |
| 108 console communication, and this port will be part of the device name like | |
| 109 'emulator-5554'. Assume it is always True, as the device name is the id of | |
| 110 emulator managed in this class. | |
| 111 | |
| 112 Attributes: | |
| 113 emulator: Path of Android's emulator tool. | |
| 114 popen: Popen object of the running emulator process. | |
| 115 device: Device name of this emulator. | |
| 116 """ | |
| 117 | |
| 118 # Signals we listen for to kill the emulator on | |
| 119 _SIGNALS = (signal.SIGINT, signal.SIGHUP) | |
| 120 | |
| 121 # Time to wait for an emulator launch, in seconds. This includes | |
| 122 # the time to launch the emulator and a wait-for-device command. | |
| 123 _LAUNCH_TIMEOUT = 120 | |
| 124 | |
| 125 # Timeout interval of wait-for-device command before bouncing to a a | |
| 126 # process life check. | |
| 127 _WAITFORDEVICE_TIMEOUT = 5 | |
| 128 | |
| 129 # Time to wait for a "wait for boot complete" (property set on device). | |
| 130 _WAITFORBOOT_TIMEOUT = 300 | |
| 131 | |
| 132 def __init__(self, new_avd_name): | |
| 133 """Init an Emulator. | |
| 134 | |
| 135 Args: | |
| 136 nwe_avd_name: If set, will create a new temporary AVD. | |
| 137 """ | |
| 138 try: | |
| 139 android_sdk_root = os.environ['ANDROID_SDK_ROOT'] | |
| 140 except KeyError: | |
| 141 logging.critical('The ANDROID_SDK_ROOT must be set to run the test on ' | |
| 142 'emulator.') | |
| 143 raise | |
| 144 self.emulator = os.path.join(android_sdk_root, 'tools', 'emulator') | |
| 145 self.android = os.path.join(android_sdk_root, 'tools', 'android') | |
| 146 self.popen = None | |
| 147 self.device = None | |
| 148 self.default_avd = True | |
| 149 self.abi = 'armeabi-v7a' | |
| 150 self.avd = 'avd_armeabi' | |
| 151 if 'x86' in os.environ.get('TARGET_PRODUCT', ''): | |
| 152 self.abi = 'x86' | |
| 153 self.avd = 'avd_x86' | |
| 154 if new_avd_name: | |
| 155 self.default_avd = False | |
| 156 self.avd = self._CreateAVD(new_avd_name) | |
| 157 | |
| 158 def _DeviceName(self): | |
| 159 """Return our device name.""" | |
| 160 port = _GetAvailablePort() | |
| 161 return ('emulator-%d' % port, port) | |
| 162 | |
| 163 def _CreateAVD(self, avd_name): | |
| 164 """Creates an AVD with the given name. | |
| 165 | |
| 166 Return avd_name. | |
| 167 """ | |
| 168 avd_command = [ | |
| 169 self.android, | |
| 170 '--silent', | |
| 171 'create', 'avd', | |
| 172 '--name', avd_name, | |
| 173 '--abi', self.abi, | |
| 174 '--target', 'android-16', | |
| 175 '-c', '128M', | |
| 176 '--force', | |
| 177 ] | |
| 178 avd_process = subprocess.Popen(args=avd_command, | |
| 179 stdin=subprocess.PIPE, | |
| 180 stdout=subprocess.PIPE, | |
| 181 stderr=subprocess.STDOUT) | |
| 182 avd_process.stdin.write('no\n') | |
| 183 avd_process.wait() | |
| 184 logging.info('Create AVD command: %s', ' '.join(avd_command)) | |
| 185 return avd_name | |
| 186 | |
| 187 def _DeleteAVD(self): | |
| 188 """Delete the AVD of this emulator.""" | |
| 189 avd_command = [ | |
| 190 self.android, | |
| 191 '--silent', | |
| 192 'delete', | |
| 193 'avd', | |
| 194 '--name', self.avd, | |
| 195 ] | |
| 196 avd_process = subprocess.Popen(args=avd_command, | |
| 197 stdout=subprocess.PIPE, | |
| 198 stderr=subprocess.STDOUT) | |
| 199 logging.info('Delete AVD command: %s', ' '.join(avd_command)) | |
| 200 avd_process.wait() | |
| 201 | |
| 202 def Launch(self, kill_all_emulators): | |
| 203 """Launches the emulator asynchronously. Call ConfirmLaunch() to ensure the | |
| 204 emulator is ready for use. | |
| 205 | |
| 206 If fails, an exception will be raised. | |
| 207 """ | |
| 208 if kill_all_emulators: | |
| 209 _KillAllEmulators() # just to be sure | |
| 210 self._AggressiveImageCleanup() | |
| 211 (self.device, port) = self._DeviceName() | |
| 212 emulator_command = [ | |
| 213 self.emulator, | |
| 214 # Speed up emulator launch by 40%. Really. | |
| 215 '-no-boot-anim', | |
| 216 # The default /data size is 64M. | |
| 217 # That's not enough for 8 unit test bundles and their data. | |
| 218 '-partition-size', '512', | |
| 219 # Enable GPU by default. | |
| 220 '-gpu', 'on', | |
| 221 # Use a familiar name and port. | |
| 222 '-avd', self.avd, | |
| 223 '-port', str(port)] | |
| 224 emulator_command.extend([ | |
| 225 # Wipe the data. We've seen cases where an emulator | |
| 226 # gets 'stuck' if we don't do this (every thousand runs or | |
| 227 # so). | |
| 228 '-wipe-data', | |
| 229 ]) | |
| 230 logging.info('Emulator launch command: %s', ' '.join(emulator_command)) | |
| 231 self.popen = subprocess.Popen(args=emulator_command, | |
| 232 stderr=subprocess.STDOUT) | |
| 233 self._InstallKillHandler() | |
| 234 | |
| 235 def _AggressiveImageCleanup(self): | |
| 236 """Aggressive cleanup of emulator images. | |
| 237 | |
| 238 Experimentally it looks like our current emulator use on the bot | |
| 239 leaves image files around in /tmp/android-$USER. If a "random" | |
| 240 name gets reused, we choke with a 'File exists' error. | |
| 241 TODO(jrg): is there a less hacky way to accomplish the same goal? | |
| 242 """ | |
| 243 logging.info('Aggressive Image Cleanup') | |
| 244 emulator_imagedir = '/tmp/android-%s' % os.environ['USER'] | |
| 245 if not os.path.exists(emulator_imagedir): | |
| 246 return | |
| 247 for image in os.listdir(emulator_imagedir): | |
| 248 full_name = os.path.join(emulator_imagedir, image) | |
| 249 if 'emulator' in full_name: | |
| 250 logging.info('Deleting emulator image %s', full_name) | |
| 251 os.unlink(full_name) | |
| 252 | |
| 253 def ConfirmLaunch(self, wait_for_boot=False): | |
| 254 """Confirm the emulator launched properly. | |
| 255 | |
| 256 Loop on a wait-for-device with a very small timeout. On each | |
| 257 timeout, check the emulator process is still alive. | |
| 258 After confirming a wait-for-device can be successful, make sure | |
| 259 it returns the right answer. | |
| 260 """ | |
| 261 seconds_waited = 0 | |
| 262 number_of_waits = 2 # Make sure we can wfd twice | |
| 263 adb_cmd = "adb -s %s %s" % (self.device, 'wait-for-device') | |
| 264 while seconds_waited < self._LAUNCH_TIMEOUT: | |
| 265 try: | |
| 266 run_command.RunCommand(adb_cmd, | |
| 267 timeout_time=self._WAITFORDEVICE_TIMEOUT, | |
| 268 retry_count=1) | |
| 269 number_of_waits -= 1 | |
| 270 if not number_of_waits: | |
| 271 break | |
| 272 except errors.WaitForResponseTimedOutError as e: | |
| 273 seconds_waited += self._WAITFORDEVICE_TIMEOUT | |
| 274 adb_cmd = "adb -s %s %s" % (self.device, 'kill-server') | |
| 275 run_command.RunCommand(adb_cmd) | |
| 276 self.popen.poll() | |
| 277 if self.popen.returncode != None: | |
| 278 raise EmulatorLaunchException('EMULATOR DIED') | |
| 279 if seconds_waited >= self._LAUNCH_TIMEOUT: | |
| 280 raise EmulatorLaunchException('TIMEOUT with wait-for-device') | |
| 281 logging.info('Seconds waited on wait-for-device: %d', seconds_waited) | |
| 282 if wait_for_boot: | |
| 283 # Now that we checked for obvious problems, wait for a boot complete. | |
| 284 # Waiting for the package manager is sometimes problematic. | |
| 285 a = android_commands.AndroidCommands(self.device) | |
| 286 a.WaitForSystemBootCompleted(self._WAITFORBOOT_TIMEOUT) | |
| 287 | |
| 288 def Shutdown(self): | |
| 289 """Shuts down the process started by launch.""" | |
| 290 if not self.default_avd: | |
| 291 self._DeleteAVD() | |
| 292 if self.popen: | |
| 293 self.popen.poll() | |
| 294 if self.popen.returncode == None: | |
| 295 self.popen.kill() | |
| 296 self.popen = None | |
| 297 | |
| 298 def _ShutdownOnSignal(self, signum, frame): | |
| 299 logging.critical('emulator _ShutdownOnSignal') | |
| 300 for sig in self._SIGNALS: | |
| 301 signal.signal(sig, signal.SIG_DFL) | |
| 302 self.Shutdown() | |
| 303 raise KeyboardInterrupt # print a stack | |
| 304 | |
| 305 def _InstallKillHandler(self): | |
| 306 """Install a handler to kill the emulator when we exit unexpectedly.""" | |
| 307 for sig in self._SIGNALS: | |
| 308 signal.signal(sig, self._ShutdownOnSignal) | |
| 309 | |
| 310 def main(argv): | 18 def main(argv): |
| 311 Emulator(None, True).Launch(True) | 19 option_parser = optparse.OptionParser() |
| 20 option_parser.add_option('-n', '--num', dest='emulator_count', |
| 21 help='Number of emulators to launch.', |
| 22 type='int', |
| 23 default=1) |
| 24 option_parser.add_option('-w', '--wait', dest='wait_for_boot', |
| 25 action='store_true', |
| 26 help='If set, wait for the emulators to boot.') |
| 27 options, args = option_parser.parse_args(argv) |
| 28 emulator.LaunchEmulators(options.emulator_count, options.wait_for_boot) |
| 312 | 29 |
| 313 | 30 |
| 314 if __name__ == '__main__': | 31 if __name__ == '__main__': |
| 315 main(sys.argv) | 32 main(sys.argv) |
| OLD | NEW |