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 |