|
1 #!/usr/bin/env python |
|
2 # |
|
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 |
|
5 # found in the LICENSE file. |
|
6 |
|
7 """Provides an interface to start and stop Android emulator. |
|
8 |
|
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 """ |
|
14 |
|
15 import logging |
|
16 import os |
|
17 import signal |
|
18 import subprocess |
|
19 import sys |
|
20 import time |
|
21 |
|
22 from pylib import android_commands |
|
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 |
|
54 |
|
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, fast_and_loose): |
|
133 """Init an Emulator. |
|
134 |
|
135 Args: |
|
136 nwe_avd_name: If set, will create a new temporary AVD. |
|
137 fast_and_loose: Loosen up the rules for reliable running for speed. |
|
138 Intended for quick testing or re-testing. |
|
139 |
|
140 """ |
|
141 try: |
|
142 android_sdk_root = os.environ['ANDROID_SDK_ROOT'] |
|
143 except KeyError: |
|
144 logging.critical('The ANDROID_SDK_ROOT must be set to run the test on ' |
|
145 'emulator.') |
|
146 raise |
|
147 self.emulator = os.path.join(android_sdk_root, 'tools', 'emulator') |
|
148 self.android = os.path.join(android_sdk_root, 'tools', 'android') |
|
149 self.popen = None |
|
150 self.device = None |
|
151 self.default_avd = True |
|
152 self.fast_and_loose = fast_and_loose |
|
153 self.abi = 'armeabi-v7a' |
|
154 self.avd = 'avd_armeabi' |
|
155 if 'x86' in os.environ.get('TARGET_PRODUCT', ''): |
|
156 self.abi = 'x86' |
|
157 self.avd = 'avd_x86' |
|
158 if new_avd_name: |
|
159 self.default_avd = False |
|
160 self.avd = self._CreateAVD(new_avd_name) |
|
161 |
|
162 def _DeviceName(self): |
|
163 """Return our device name.""" |
|
164 port = _GetAvailablePort() |
|
165 return ('emulator-%d' % port, port) |
|
166 |
|
167 def _CreateAVD(self, avd_name): |
|
168 """Creates an AVD with the given name. |
|
169 |
|
170 Return avd_name. |
|
171 """ |
|
172 avd_command = [ |
|
173 self.android, |
|
174 '--silent', |
|
175 'create', 'avd', |
|
176 '--name', avd_name, |
|
177 '--abi', self.abi, |
|
178 '--target', 'android-16', |
|
179 '-c', '128M', |
|
180 '--force', |
|
181 ] |
|
182 avd_process = subprocess.Popen(args=avd_command, |
|
183 stdin=subprocess.PIPE, |
|
184 stdout=subprocess.PIPE, |
|
185 stderr=subprocess.STDOUT) |
|
186 avd_process.stdin.write('no\n') |
|
187 avd_process.wait() |
|
188 logging.info('Create AVD command: %s', ' '.join(avd_command)) |
|
189 return avd_name |
|
190 |
|
191 def _DeleteAVD(self): |
|
192 """Delete the AVD of this emulator.""" |
|
193 avd_command = [ |
|
194 self.android, |
|
195 '--silent', |
|
196 'delete', |
|
197 'avd', |
|
198 '--name', self.avd, |
|
199 ] |
|
200 avd_process = subprocess.Popen(args=avd_command, |
|
201 stdout=subprocess.PIPE, |
|
202 stderr=subprocess.STDOUT) |
|
203 logging.info('Delete AVD command: %s', ' '.join(avd_command)) |
|
204 avd_process.wait() |
|
205 |
|
206 def Launch(self, kill_all_emulators): |
|
207 """Launches the emulator asynchronously. Call ConfirmLaunch() to ensure the |
|
208 emulator is ready for use. |
|
209 |
|
210 If fails, an exception will be raised. |
|
211 """ |
|
212 if kill_all_emulators: |
|
213 _KillAllEmulators() # just to be sure |
|
214 if not self.fast_and_loose: |
|
215 self._AggressiveImageCleanup() |
|
216 (self.device, port) = self._DeviceName() |
|
217 emulator_command = [ |
|
218 self.emulator, |
|
219 # Speed up emulator launch by 40%. Really. |
|
220 '-no-boot-anim', |
|
221 # The default /data size is 64M. |
|
222 # That's not enough for 8 unit test bundles and their data. |
|
223 '-partition-size', '512', |
|
224 # Enable GPU by default. |
|
225 '-gpu', 'on', |
|
226 # Use a familiar name and port. |
|
227 '-avd', self.avd, |
|
228 '-port', str(port)] |
|
229 if not self.fast_and_loose: |
|
230 emulator_command.extend([ |
|
231 # Wipe the data. We've seen cases where an emulator |
|
232 # gets 'stuck' if we don't do this (every thousand runs or |
|
233 # so). |
|
234 '-wipe-data', |
|
235 ]) |
|
236 logging.info('Emulator launch command: %s', ' '.join(emulator_command)) |
|
237 self.popen = subprocess.Popen(args=emulator_command, |
|
238 stderr=subprocess.STDOUT) |
|
239 self._InstallKillHandler() |
|
240 |
|
241 def _AggressiveImageCleanup(self): |
|
242 """Aggressive cleanup of emulator images. |
|
243 |
|
244 Experimentally it looks like our current emulator use on the bot |
|
245 leaves image files around in /tmp/android-$USER. If a "random" |
|
246 name gets reused, we choke with a 'File exists' error. |
|
247 TODO(jrg): is there a less hacky way to accomplish the same goal? |
|
248 """ |
|
249 logging.info('Aggressive Image Cleanup') |
|
250 emulator_imagedir = '/tmp/android-%s' % os.environ['USER'] |
|
251 if not os.path.exists(emulator_imagedir): |
|
252 return |
|
253 for image in os.listdir(emulator_imagedir): |
|
254 full_name = os.path.join(emulator_imagedir, image) |
|
255 if 'emulator' in full_name: |
|
256 logging.info('Deleting emulator image %s', full_name) |
|
257 os.unlink(full_name) |
|
258 |
|
259 def ConfirmLaunch(self, wait_for_boot=False): |
|
260 """Confirm the emulator launched properly. |
|
261 |
|
262 Loop on a wait-for-device with a very small timeout. On each |
|
263 timeout, check the emulator process is still alive. |
|
264 After confirming a wait-for-device can be successful, make sure |
|
265 it returns the right answer. |
|
266 """ |
|
267 seconds_waited = 0 |
|
268 number_of_waits = 2 # Make sure we can wfd twice |
|
269 adb_cmd = "adb -s %s %s" % (self.device, 'wait-for-device') |
|
270 while seconds_waited < self._LAUNCH_TIMEOUT: |
|
271 try: |
|
272 run_command.RunCommand(adb_cmd, |
|
273 timeout_time=self._WAITFORDEVICE_TIMEOUT, |
|
274 retry_count=1) |
|
275 number_of_waits -= 1 |
|
276 if not number_of_waits: |
|
277 break |
|
278 except errors.WaitForResponseTimedOutError as e: |
|
279 seconds_waited += self._WAITFORDEVICE_TIMEOUT |
|
280 adb_cmd = "adb -s %s %s" % (self.device, 'kill-server') |
|
281 run_command.RunCommand(adb_cmd) |
|
282 self.popen.poll() |
|
283 if self.popen.returncode != None: |
|
284 raise EmulatorLaunchException('EMULATOR DIED') |
|
285 if seconds_waited >= self._LAUNCH_TIMEOUT: |
|
286 raise EmulatorLaunchException('TIMEOUT with wait-for-device') |
|
287 logging.info('Seconds waited on wait-for-device: %d', seconds_waited) |
|
288 if wait_for_boot: |
|
289 # Now that we checked for obvious problems, wait for a boot complete. |
|
290 # Waiting for the package manager is sometimes problematic. |
|
291 a = android_commands.AndroidCommands(self.device) |
|
292 a.WaitForSystemBootCompleted(self._WAITFORBOOT_TIMEOUT) |
|
293 |
|
294 def Shutdown(self): |
|
295 """Shuts down the process started by launch.""" |
|
296 if not self.default_avd: |
|
297 self._DeleteAVD() |
|
298 if self.popen: |
|
299 self.popen.poll() |
|
300 if self.popen.returncode == None: |
|
301 self.popen.kill() |
|
302 self.popen = None |
|
303 |
|
304 def _ShutdownOnSignal(self, signum, frame): |
|
305 logging.critical('emulator _ShutdownOnSignal') |
|
306 for sig in self._SIGNALS: |
|
307 signal.signal(sig, signal.SIG_DFL) |
|
308 self.Shutdown() |
|
309 raise KeyboardInterrupt # print a stack |
|
310 |
|
311 def _InstallKillHandler(self): |
|
312 """Install a handler to kill the emulator when we exit unexpectedly.""" |
|
313 for sig in self._SIGNALS: |
|
314 signal.signal(sig, self._ShutdownOnSignal) |
|
315 |
|
316 def main(argv): |
|
317 Emulator(None, True).Launch(True) |
|
318 |
|
319 |
|
320 if __name__ == '__main__': |
|
321 main(sys.argv) |