OLD | NEW |
| (Empty) |
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 # Use of this source code is governed by a BSD-style license that can be | |
3 # found in the LICENSE file. | |
4 | |
5 """Provides an interface to start and stop Android emulator. | |
6 | |
7 Emulator: The class provides the methods to launch/shutdown the emulator with | |
8 the android virtual device named 'avd_armeabi' . | |
9 """ | |
10 | |
11 import logging | |
12 import os | |
13 import signal | |
14 import subprocess | |
15 import time | |
16 | |
17 # TODO(craigdh): Move these pylib dependencies to pylib/utils/. | |
18 from pylib import cmd_helper | |
19 from pylib import constants | |
20 from pylib import pexpect | |
21 from pylib.device import device_errors | |
22 from pylib.device import device_utils | |
23 from pylib.utils import time_profile | |
24 | |
25 import errors | |
26 import run_command | |
27 | |
28 # SD card size | |
29 SDCARD_SIZE = '512M' | |
30 | |
31 # Template used to generate config.ini files for the emulator | |
32 CONFIG_TEMPLATE = """avd.ini.encoding=ISO-8859-1 | |
33 hw.dPad=no | |
34 hw.lcd.density=320 | |
35 sdcard.size=512M | |
36 hw.cpu.arch={hw.cpu.arch} | |
37 hw.device.hash=-708107041 | |
38 hw.camera.back=none | |
39 disk.dataPartition.size=800M | |
40 hw.gpu.enabled=yes | |
41 skin.path=720x1280 | |
42 skin.dynamic=yes | |
43 hw.keyboard=yes | |
44 hw.ramSize=1024 | |
45 hw.device.manufacturer=Google | |
46 hw.sdCard=yes | |
47 hw.mainKeys=no | |
48 hw.accelerometer=yes | |
49 skin.name=720x1280 | |
50 abi.type={abi.type} | |
51 hw.trackBall=no | |
52 hw.device.name=Galaxy Nexus | |
53 hw.battery=yes | |
54 hw.sensors.proximity=yes | |
55 image.sysdir.1=system-images/android-{api.level}/{abi.type}/ | |
56 hw.sensors.orientation=yes | |
57 hw.audioInput=yes | |
58 hw.camera.front=none | |
59 hw.gps=yes | |
60 vm.heapSize=128 | |
61 {extras}""" | |
62 | |
63 CONFIG_REPLACEMENTS = { | |
64 'x86': { | |
65 '{hw.cpu.arch}': 'x86', | |
66 '{abi.type}': 'x86', | |
67 '{extras}': '' | |
68 }, | |
69 'arm': { | |
70 '{hw.cpu.arch}': 'arm', | |
71 '{abi.type}': 'armeabi-v7a', | |
72 '{extras}': 'hw.cpu.model=cortex-a8\n' | |
73 }, | |
74 'mips': { | |
75 '{hw.cpu.arch}': 'mips', | |
76 '{abi.type}': 'mips', | |
77 '{extras}': '' | |
78 } | |
79 } | |
80 | |
81 class EmulatorLaunchException(Exception): | |
82 """Emulator failed to launch.""" | |
83 pass | |
84 | |
85 def _KillAllEmulators(): | |
86 """Kill all running emulators that look like ones we started. | |
87 | |
88 There are odd 'sticky' cases where there can be no emulator process | |
89 running but a device slot is taken. A little bot trouble and we're out of | |
90 room forever. | |
91 """ | |
92 emulators = [d for d in device_utils.DeviceUtils.HealthyDevices() | |
93 if d.adb.is_emulator] | |
94 if not emulators: | |
95 return | |
96 for e in emulators: | |
97 e.adb.Emu(['kill']) | |
98 logging.info('Emulator killing is async; give a few seconds for all to die.') | |
99 for _ in range(5): | |
100 if not any(d.adb.is_emulator for d | |
101 in device_utils.DeviceUtils.HealthyDevices()): | |
102 return | |
103 time.sleep(1) | |
104 | |
105 | |
106 def DeleteAllTempAVDs(): | |
107 """Delete all temporary AVDs which are created for tests. | |
108 | |
109 If the test exits abnormally and some temporary AVDs created when testing may | |
110 be left in the system. Clean these AVDs. | |
111 """ | |
112 avds = device_utils.GetAVDs() | |
113 if not avds: | |
114 return | |
115 for avd_name in avds: | |
116 if 'run_tests_avd' in avd_name: | |
117 cmd = ['android', '-s', 'delete', 'avd', '--name', avd_name] | |
118 cmd_helper.RunCmd(cmd) | |
119 logging.info('Delete AVD %s' % avd_name) | |
120 | |
121 | |
122 class PortPool(object): | |
123 """Pool for emulator port starting position that changes over time.""" | |
124 _port_min = 5554 | |
125 _port_max = 5585 | |
126 _port_current_index = 0 | |
127 | |
128 @classmethod | |
129 def port_range(cls): | |
130 """Return a range of valid ports for emulator use. | |
131 | |
132 The port must be an even number between 5554 and 5584. Sometimes | |
133 a killed emulator "hangs on" to a port long enough to prevent | |
134 relaunch. This is especially true on slow machines (like a bot). | |
135 Cycling through a port start position helps make us resilient.""" | |
136 ports = range(cls._port_min, cls._port_max, 2) | |
137 n = cls._port_current_index | |
138 cls._port_current_index = (n + 1) % len(ports) | |
139 return ports[n:] + ports[:n] | |
140 | |
141 | |
142 def _GetAvailablePort(): | |
143 """Returns an available TCP port for the console.""" | |
144 used_ports = [] | |
145 emulators = [d for d in device_utils.DeviceUtils.HealthyDevices() | |
146 if d.adb.is_emulator] | |
147 for emulator in emulators: | |
148 used_ports.append(emulator.adb.GetDeviceSerial().split('-')[1]) | |
149 for port in PortPool.port_range(): | |
150 if str(port) not in used_ports: | |
151 return port | |
152 | |
153 | |
154 def LaunchTempEmulators(emulator_count, abi, api_level, wait_for_boot=True): | |
155 """Create and launch temporary emulators and wait for them to boot. | |
156 | |
157 Args: | |
158 emulator_count: number of emulators to launch. | |
159 abi: the emulator target platform | |
160 api_level: the api level (e.g., 19 for Android v4.4 - KitKat release) | |
161 wait_for_boot: whether or not to wait for emulators to boot up | |
162 | |
163 Returns: | |
164 List of emulators. | |
165 """ | |
166 emulators = [] | |
167 for n in xrange(emulator_count): | |
168 t = time_profile.TimeProfile('Emulator launch %d' % n) | |
169 # Creates a temporary AVD. | |
170 avd_name = 'run_tests_avd_%d' % n | |
171 logging.info('Emulator launch %d with avd_name=%s and api=%d', | |
172 n, avd_name, api_level) | |
173 emulator = Emulator(avd_name, abi) | |
174 emulator.CreateAVD(api_level) | |
175 emulator.Launch(kill_all_emulators=n == 0) | |
176 t.Stop() | |
177 emulators.append(emulator) | |
178 # Wait for all emulators to boot completed. | |
179 if wait_for_boot: | |
180 for emulator in emulators: | |
181 emulator.ConfirmLaunch(True) | |
182 return emulators | |
183 | |
184 | |
185 def LaunchEmulator(avd_name, abi): | |
186 """Launch an existing emulator with name avd_name. | |
187 | |
188 Args: | |
189 avd_name: name of existing emulator | |
190 abi: the emulator target platform | |
191 | |
192 Returns: | |
193 emulator object. | |
194 """ | |
195 logging.info('Specified emulator named avd_name=%s launched', avd_name) | |
196 emulator = Emulator(avd_name, abi) | |
197 emulator.Launch(kill_all_emulators=True) | |
198 emulator.ConfirmLaunch(True) | |
199 return emulator | |
200 | |
201 | |
202 class Emulator(object): | |
203 """Provides the methods to launch/shutdown the emulator. | |
204 | |
205 The emulator has the android virtual device named 'avd_armeabi'. | |
206 | |
207 The emulator could use any even TCP port between 5554 and 5584 for the | |
208 console communication, and this port will be part of the device name like | |
209 'emulator-5554'. Assume it is always True, as the device name is the id of | |
210 emulator managed in this class. | |
211 | |
212 Attributes: | |
213 emulator: Path of Android's emulator tool. | |
214 popen: Popen object of the running emulator process. | |
215 device: Device name of this emulator. | |
216 """ | |
217 | |
218 # Signals we listen for to kill the emulator on | |
219 _SIGNALS = (signal.SIGINT, signal.SIGHUP) | |
220 | |
221 # Time to wait for an emulator launch, in seconds. This includes | |
222 # the time to launch the emulator and a wait-for-device command. | |
223 _LAUNCH_TIMEOUT = 120 | |
224 | |
225 # Timeout interval of wait-for-device command before bouncing to a a | |
226 # process life check. | |
227 _WAITFORDEVICE_TIMEOUT = 5 | |
228 | |
229 # Time to wait for a "wait for boot complete" (property set on device). | |
230 _WAITFORBOOT_TIMEOUT = 300 | |
231 | |
232 def __init__(self, avd_name, abi): | |
233 """Init an Emulator. | |
234 | |
235 Args: | |
236 avd_name: name of the AVD to create | |
237 abi: target platform for emulator being created, defaults to x86 | |
238 """ | |
239 android_sdk_root = os.path.join(constants.EMULATOR_SDK_ROOT, 'sdk') | |
240 self.emulator = os.path.join(android_sdk_root, 'tools', 'emulator') | |
241 self.android = os.path.join(android_sdk_root, 'tools', 'android') | |
242 self.popen = None | |
243 self.device_serial = None | |
244 self.abi = abi | |
245 self.avd_name = avd_name | |
246 | |
247 @staticmethod | |
248 def _DeviceName(): | |
249 """Return our device name.""" | |
250 port = _GetAvailablePort() | |
251 return ('emulator-%d' % port, port) | |
252 | |
253 def CreateAVD(self, api_level): | |
254 """Creates an AVD with the given name. | |
255 | |
256 Args: | |
257 api_level: the api level of the image | |
258 | |
259 Return avd_name. | |
260 """ | |
261 | |
262 if self.abi == 'arm': | |
263 abi_option = 'armeabi-v7a' | |
264 elif self.abi == 'mips': | |
265 abi_option = 'mips' | |
266 else: | |
267 abi_option = 'x86' | |
268 | |
269 api_target = 'android-%s' % api_level | |
270 | |
271 avd_command = [ | |
272 self.android, | |
273 '--silent', | |
274 'create', 'avd', | |
275 '--name', self.avd_name, | |
276 '--abi', abi_option, | |
277 '--target', api_target, | |
278 '--sdcard', SDCARD_SIZE, | |
279 '--force', | |
280 ] | |
281 avd_cmd_str = ' '.join(avd_command) | |
282 logging.info('Create AVD command: %s', avd_cmd_str) | |
283 avd_process = pexpect.spawn(avd_cmd_str) | |
284 | |
285 # Instead of creating a custom profile, we overwrite config files. | |
286 avd_process.expect('Do you wish to create a custom hardware profile') | |
287 avd_process.sendline('no\n') | |
288 avd_process.expect('Created AVD \'%s\'' % self.avd_name) | |
289 | |
290 # Replace current configuration with default Galaxy Nexus config. | |
291 avds_dir = os.path.join(os.path.expanduser('~'), '.android', 'avd') | |
292 ini_file = os.path.join(avds_dir, '%s.ini' % self.avd_name) | |
293 new_config_ini = os.path.join(avds_dir, '%s.avd' % self.avd_name, | |
294 'config.ini') | |
295 | |
296 # Remove config files with defaults to replace with Google's GN settings. | |
297 os.unlink(ini_file) | |
298 os.unlink(new_config_ini) | |
299 | |
300 # Create new configuration files with Galaxy Nexus by Google settings. | |
301 with open(ini_file, 'w') as new_ini: | |
302 new_ini.write('avd.ini.encoding=ISO-8859-1\n') | |
303 new_ini.write('target=%s\n' % api_target) | |
304 new_ini.write('path=%s/%s.avd\n' % (avds_dir, self.avd_name)) | |
305 new_ini.write('path.rel=avd/%s.avd\n' % self.avd_name) | |
306 | |
307 custom_config = CONFIG_TEMPLATE | |
308 replacements = CONFIG_REPLACEMENTS[self.abi] | |
309 for key in replacements: | |
310 custom_config = custom_config.replace(key, replacements[key]) | |
311 custom_config = custom_config.replace('{api.level}', str(api_level)) | |
312 | |
313 with open(new_config_ini, 'w') as new_config_ini: | |
314 new_config_ini.write(custom_config) | |
315 | |
316 return self.avd_name | |
317 | |
318 | |
319 def _DeleteAVD(self): | |
320 """Delete the AVD of this emulator.""" | |
321 avd_command = [ | |
322 self.android, | |
323 '--silent', | |
324 'delete', | |
325 'avd', | |
326 '--name', self.avd_name, | |
327 ] | |
328 logging.info('Delete AVD command: %s', ' '.join(avd_command)) | |
329 cmd_helper.RunCmd(avd_command) | |
330 | |
331 | |
332 def Launch(self, kill_all_emulators): | |
333 """Launches the emulator asynchronously. Call ConfirmLaunch() to ensure the | |
334 emulator is ready for use. | |
335 | |
336 If fails, an exception will be raised. | |
337 """ | |
338 if kill_all_emulators: | |
339 _KillAllEmulators() # just to be sure | |
340 self._AggressiveImageCleanup() | |
341 (self.device_serial, port) = self._DeviceName() | |
342 emulator_command = [ | |
343 self.emulator, | |
344 # Speed up emulator launch by 40%. Really. | |
345 '-no-boot-anim', | |
346 # The default /data size is 64M. | |
347 # That's not enough for 8 unit test bundles and their data. | |
348 '-partition-size', '512', | |
349 # Use a familiar name and port. | |
350 '-avd', self.avd_name, | |
351 '-port', str(port), | |
352 # Wipe the data. We've seen cases where an emulator gets 'stuck' if we | |
353 # don't do this (every thousand runs or so). | |
354 '-wipe-data', | |
355 # Enable GPU by default. | |
356 '-gpu', 'on', | |
357 '-qemu', '-m', '1024', | |
358 ] | |
359 if self.abi == 'x86': | |
360 emulator_command.extend([ | |
361 # For x86 emulator --enable-kvm will fail early, avoiding accidental | |
362 # runs in a slow mode (i.e. without hardware virtualization support). | |
363 '--enable-kvm', | |
364 ]) | |
365 | |
366 logging.info('Emulator launch command: %s', ' '.join(emulator_command)) | |
367 self.popen = subprocess.Popen(args=emulator_command, | |
368 stderr=subprocess.STDOUT) | |
369 self._InstallKillHandler() | |
370 | |
371 @staticmethod | |
372 def _AggressiveImageCleanup(): | |
373 """Aggressive cleanup of emulator images. | |
374 | |
375 Experimentally it looks like our current emulator use on the bot | |
376 leaves image files around in /tmp/android-$USER. If a "random" | |
377 name gets reused, we choke with a 'File exists' error. | |
378 TODO(jrg): is there a less hacky way to accomplish the same goal? | |
379 """ | |
380 logging.info('Aggressive Image Cleanup') | |
381 emulator_imagedir = '/tmp/android-%s' % os.environ['USER'] | |
382 if not os.path.exists(emulator_imagedir): | |
383 return | |
384 for image in os.listdir(emulator_imagedir): | |
385 full_name = os.path.join(emulator_imagedir, image) | |
386 if 'emulator' in full_name: | |
387 logging.info('Deleting emulator image %s', full_name) | |
388 os.unlink(full_name) | |
389 | |
390 def ConfirmLaunch(self, wait_for_boot=False): | |
391 """Confirm the emulator launched properly. | |
392 | |
393 Loop on a wait-for-device with a very small timeout. On each | |
394 timeout, check the emulator process is still alive. | |
395 After confirming a wait-for-device can be successful, make sure | |
396 it returns the right answer. | |
397 """ | |
398 seconds_waited = 0 | |
399 number_of_waits = 2 # Make sure we can wfd twice | |
400 | |
401 device = device_utils.DeviceUtils(self.device_serial) | |
402 while seconds_waited < self._LAUNCH_TIMEOUT: | |
403 try: | |
404 device.adb.WaitForDevice( | |
405 timeout=self._WAITFORDEVICE_TIMEOUT, retries=1) | |
406 number_of_waits -= 1 | |
407 if not number_of_waits: | |
408 break | |
409 except device_errors.CommandTimeoutError: | |
410 seconds_waited += self._WAITFORDEVICE_TIMEOUT | |
411 device.adb.KillServer() | |
412 self.popen.poll() | |
413 if self.popen.returncode != None: | |
414 raise EmulatorLaunchException('EMULATOR DIED') | |
415 | |
416 if seconds_waited >= self._LAUNCH_TIMEOUT: | |
417 raise EmulatorLaunchException('TIMEOUT with wait-for-device') | |
418 | |
419 logging.info('Seconds waited on wait-for-device: %d', seconds_waited) | |
420 if wait_for_boot: | |
421 # Now that we checked for obvious problems, wait for a boot complete. | |
422 # Waiting for the package manager is sometimes problematic. | |
423 device.WaitUntilFullyBooted(timeout=self._WAITFORBOOT_TIMEOUT) | |
424 | |
425 def Shutdown(self): | |
426 """Shuts down the process started by launch.""" | |
427 self._DeleteAVD() | |
428 if self.popen: | |
429 self.popen.poll() | |
430 if self.popen.returncode == None: | |
431 self.popen.kill() | |
432 self.popen = None | |
433 | |
434 def _ShutdownOnSignal(self, _signum, _frame): | |
435 logging.critical('emulator _ShutdownOnSignal') | |
436 for sig in self._SIGNALS: | |
437 signal.signal(sig, signal.SIG_DFL) | |
438 self.Shutdown() | |
439 raise KeyboardInterrupt # print a stack | |
440 | |
441 def _InstallKillHandler(self): | |
442 """Install a handler to kill the emulator when we exit unexpectedly.""" | |
443 for sig in self._SIGNALS: | |
444 signal.signal(sig, self._ShutdownOnSignal) | |
OLD | NEW |