Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(132)

Side by Side Diff: build/android/pylib/utils/emulator.py

Issue 2101243005: Add a snapshot of flutter/engine/src/build to our sdk (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: add README.dart Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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)
OLDNEW
« no previous file with comments | « build/android/pylib/utils/device_temp_file_test.py ('k') | build/android/pylib/utils/findbugs.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698