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)