1 # Copyright (C) 2012 Google Inc. All rights reserved.
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions are
7 # * Redistributions of source code must retain the above copyright
8 # notice, this list of conditions and the following disclaimer.
9 # * Redistributions in binary form must reproduce the above
10 # copyright notice, this list of conditions and the following disclaimer
11 # in the documentation and/or other materials provided with the
13 # * Neither the name of Google Inc. nor the names of its
14 # contributors may be used to endorse or promote products derived from
15 # this software without specific prior written permission.
17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 from multiprocessing.pool import ThreadPool
41 from webkitpy.common.system.executive import ScriptError
42 from webkitpy.layout_tests.breakpad.dump_reader_multipart import DumpReaderAndroid
43 from webkitpy.layout_tests.models import test_run_results
44 from webkitpy.layout_tests.port import base
45 from webkitpy.layout_tests.port import linux
46 from webkitpy.layout_tests.port import driver
47 from webkitpy.layout_tests.port import factory
48 from webkitpy.layout_tests.port import server_process
49 from webkitpy.common.system.profiler import SingleFileOutputProfiler
51 _log = logging.getLogger(__name__)
53 # The root directory for test resources, which has the same structure as the
54 # source root directory of Chromium.
55 # This path is defined in Chromium's base/test/test_support_android.cc.
56 DEVICE_SOURCE_ROOT_DIR = '/data/local/tmp/'
58 # The layout tests directory on device, which has two usages:
59 # 1. as a virtual path in file urls that will be bridged to HTTP.
60 # 2. pointing to some files that are pushed to the device for tests that
61 # don't work on file-over-http (e.g. blob protocol tests).
62 DEVICE_WEBKIT_BASE_DIR = DEVICE_SOURCE_ROOT_DIR + 'third_party/WebKit/'
63 DEVICE_LAYOUT_TESTS_DIR = DEVICE_WEBKIT_BASE_DIR + 'LayoutTests/'
65 SCALING_GOVERNORS_PATTERN = "/sys/devices/system/cpu/cpu*/cpufreq/scaling_governor"
66 KPTR_RESTRICT_PATH = "/proc/sys/kernel/kptr_restrict"
68 # All the test cases are still served to the test runner through file protocol,
69 # but we use a file-to-http feature to bridge the file request to host's http
70 # server to get the real test files and corresponding resources.
71 # See webkit/support/platform_support_android.cc for the other side of this bridge.
72 PERF_TEST_PATH_PREFIX = '/all-perf-tests'
73 LAYOUT_TEST_PATH_PREFIX = '/all-tests'
75 # All ports the Android forwarder to forward.
76 # 8000, 8080 and 8443 are for http/https tests.
77 # 8880 and 9323 are for websocket tests
78 # (see http_server.py, apache_http_server.py and websocket_server.py).
79 FORWARD_PORTS = '8000 8080 8443 8880 9323'
81 MS_TRUETYPE_FONTS_DIR = '/usr/share/fonts/truetype/msttcorefonts/'
82 MS_TRUETYPE_FONTS_PACKAGE = 'ttf-mscorefonts-installer'
84 # Timeout in seconds to wait for starting/stopping the driver.
85 DRIVER_START_STOP_TIMEOUT_SECS = 10
88 [[MS_TRUETYPE_FONTS_DIR], 'Arial.ttf', MS_TRUETYPE_FONTS_PACKAGE],
89 [[MS_TRUETYPE_FONTS_DIR], 'Arial_Bold.ttf', MS_TRUETYPE_FONTS_PACKAGE],
90 [[MS_TRUETYPE_FONTS_DIR], 'Arial_Bold_Italic.ttf', MS_TRUETYPE_FONTS_PACKAGE],
91 [[MS_TRUETYPE_FONTS_DIR], 'Arial_Italic.ttf', MS_TRUETYPE_FONTS_PACKAGE],
92 [[MS_TRUETYPE_FONTS_DIR], 'Comic_Sans_MS.ttf', MS_TRUETYPE_FONTS_PACKAGE],
93 [[MS_TRUETYPE_FONTS_DIR], 'Comic_Sans_MS_Bold.ttf', MS_TRUETYPE_FONTS_PACKAGE],
94 [[MS_TRUETYPE_FONTS_DIR], 'Courier_New.ttf', MS_TRUETYPE_FONTS_PACKAGE],
95 [[MS_TRUETYPE_FONTS_DIR], 'Courier_New_Bold.ttf', MS_TRUETYPE_FONTS_PACKAGE],
96 [[MS_TRUETYPE_FONTS_DIR], 'Courier_New_Bold_Italic.ttf', MS_TRUETYPE_FONTS_PACKAGE],
97 [[MS_TRUETYPE_FONTS_DIR], 'Courier_New_Italic.ttf', MS_TRUETYPE_FONTS_PACKAGE],
98 [[MS_TRUETYPE_FONTS_DIR], 'Georgia.ttf', MS_TRUETYPE_FONTS_PACKAGE],
99 [[MS_TRUETYPE_FONTS_DIR], 'Georgia_Bold.ttf', MS_TRUETYPE_FONTS_PACKAGE],
100 [[MS_TRUETYPE_FONTS_DIR], 'Georgia_Bold_Italic.ttf', MS_TRUETYPE_FONTS_PACKAGE],
101 [[MS_TRUETYPE_FONTS_DIR], 'Georgia_Italic.ttf', MS_TRUETYPE_FONTS_PACKAGE],
102 [[MS_TRUETYPE_FONTS_DIR], 'Impact.ttf', MS_TRUETYPE_FONTS_PACKAGE],
103 [[MS_TRUETYPE_FONTS_DIR], 'Trebuchet_MS.ttf', MS_TRUETYPE_FONTS_PACKAGE],
104 [[MS_TRUETYPE_FONTS_DIR], 'Trebuchet_MS_Bold.ttf', MS_TRUETYPE_FONTS_PACKAGE],
105 [[MS_TRUETYPE_FONTS_DIR], 'Trebuchet_MS_Bold_Italic.ttf', MS_TRUETYPE_FONTS_PACKAGE],
106 [[MS_TRUETYPE_FONTS_DIR], 'Trebuchet_MS_Italic.ttf', MS_TRUETYPE_FONTS_PACKAGE],
107 [[MS_TRUETYPE_FONTS_DIR], 'Times_New_Roman.ttf', MS_TRUETYPE_FONTS_PACKAGE],
108 [[MS_TRUETYPE_FONTS_DIR], 'Times_New_Roman_Bold.ttf', MS_TRUETYPE_FONTS_PACKAGE],
109 [[MS_TRUETYPE_FONTS_DIR], 'Times_New_Roman_Bold_Italic.ttf', MS_TRUETYPE_FONTS_PACKAGE],
110 [[MS_TRUETYPE_FONTS_DIR], 'Times_New_Roman_Italic.ttf', MS_TRUETYPE_FONTS_PACKAGE],
111 [[MS_TRUETYPE_FONTS_DIR], 'Verdana.ttf', MS_TRUETYPE_FONTS_PACKAGE],
112 [[MS_TRUETYPE_FONTS_DIR], 'Verdana_Bold.ttf', MS_TRUETYPE_FONTS_PACKAGE],
113 [[MS_TRUETYPE_FONTS_DIR], 'Verdana_Bold_Italic.ttf', MS_TRUETYPE_FONTS_PACKAGE],
114 [[MS_TRUETYPE_FONTS_DIR], 'Verdana_Italic.ttf', MS_TRUETYPE_FONTS_PACKAGE],
115 # The Microsoft font EULA
116 [['/usr/share/doc/ttf-mscorefonts-installer/'], 'READ_ME!.gz', MS_TRUETYPE_FONTS_PACKAGE],
117 # Other fonts: Arabic, CJK, Indic, Thai, etc.
118 [['/usr/share/fonts/truetype/ttf-dejavu/'], 'DejaVuSans.ttf', 'ttf-dejavu'],
119 [['/usr/share/fonts/truetype/kochi/'], 'kochi-mincho.ttf', 'ttf-kochi-mincho'],
120 [['/usr/share/fonts/truetype/ttf-indic-fonts-core/'], 'lohit_hi.ttf', 'ttf-indic-fonts-core'],
121 [['/usr/share/fonts/truetype/ttf-indic-fonts-core/'], 'lohit_ta.ttf', 'ttf-indic-fonts-core'],
122 [['/usr/share/fonts/truetype/ttf-indic-fonts-core/'], 'MuktiNarrow.ttf', 'ttf-indic-fonts-core'],
123 [['/usr/share/fonts/truetype/thai/', '/usr/share/fonts/truetype/tlwg/'], 'Garuda.ttf', 'fonts-tlwg-garuda'],
124 [['/usr/share/fonts/truetype/ttf-indic-fonts-core/', '/usr/share/fonts/truetype/ttf-punjabi-fonts/'], 'lohit_pa.ttf', 'ttf-indic-fonts-core'],
127 # Test resources that need to be accessed as files directly.
128 # Each item can be the relative path of a directory or a file.
129 TEST_RESOURCES_TO_PUSH = [
130 # Blob tests need to access files directly.
131 'editing/pasteboard/resources',
132 'fast/files/resources',
133 'http/tests/local/resources',
134 'http/tests/local/formdata/resources',
135 # User style URLs are accessed as local files in webkit_support.
136 'http/tests/security/resources/cssStyle.css',
137 # Media tests need to access audio/video as files.
139 'compositing/resources/video.mp4',
142 MD5SUM_DEVICE_FILE_NAME = 'md5sum_bin'
143 MD5SUM_HOST_FILE_NAME = 'md5sum_bin_host'
144 MD5SUM_DEVICE_PATH = '/data/local/tmp/' + MD5SUM_DEVICE_FILE_NAME
147 # Information required when running layout tests using content_shell as the test runner.
148 class ContentShellDriverDetails():
149 def device_cache_directory(self):
150 return self.device_directory() + 'cache/'
152 def device_fonts_directory(self):
153 return self.device_directory() + 'fonts/'
155 def device_forwarder_path(self):
156 return self.device_directory() + 'forwarder'
158 def device_fifo_directory(self):
159 return '/data/data/' + self.package_name() + '/files/'
162 return 'apks/ContentShell.apk'
164 def package_name(self):
165 return 'org.chromium.content_shell_apk'
167 def activity_name(self):
168 return self.package_name() + '/.ContentShellActivity'
170 def library_name(self):
171 return 'libcontent_shell_content_view.so'
173 def additional_resources(self):
174 return ['content_resources.pak', 'shell_resources.pak']
176 def command_line_file(self):
177 return '/data/local/tmp/content-shell-command-line'
179 def device_crash_dumps_directory(self):
180 return '/data/local/tmp/content-shell-crash-dumps'
182 def additional_command_line_flags(self, use_breakpad):
183 flags = ['--dump-render-tree', '--encode-binary']
185 flags.extend(['--enable-crash-reporter', '--crash-dumps-dir=%s' % self.device_crash_dumps_directory()])
188 def device_directory(self):
189 return DEVICE_SOURCE_ROOT_DIR + 'content_shell/'
192 # The AndroidCommands class encapsulates commands to communicate with an attached device.
193 class AndroidCommands(object):
194 _adb_command_path = None
195 _adb_command_path_options = []
197 def __init__(self, executive, device_serial, debug_logging):
198 self._executive = executive
199 self._device_serial = device_serial
200 self._debug_logging = debug_logging
202 # Local public methods.
204 def file_exists(self, full_path):
205 assert full_path.startswith('/')
206 return self.run(['shell', 'ls', '-d', full_path]).strip() == full_path
208 def push(self, host_path, device_path, ignore_error=False):
209 return self.run(['push', host_path, device_path], ignore_error=ignore_error)
211 def pull(self, device_path, host_path, ignore_error=False):
212 return self.run(['pull', device_path, host_path], ignore_error=ignore_error)
214 def mkdir(self, device_path, chmod=None):
215 self.run(['shell', 'mkdir', '-p', device_path])
217 self.run(['shell', 'chmod', chmod, device_path])
219 def restart_adb(self):
220 pids = self.extract_pids('adbd')
222 output = self.run(['shell', 'kill', '-' + str(signal.SIGTERM)] + pids)
223 self.run(['wait-for-device'])
225 def restart_as_root(self):
226 output = self.run(['root'])
227 if 'adbd is already running as root' in output:
230 elif not 'restarting adbd as root' in output:
231 self._log_error('Unrecognized output from adb root: %s' % output)
233 self.run(['wait-for-device'])
235 def extract_pids(self, process_name):
237 output = self.run(['shell', 'ps'])
238 for line in output.splitlines():
241 if process_name in data[-1]: # name is in the last column
242 if process_name == data[-1]:
243 pids.insert(0, data[1]) # PID is in the second column
250 def run(self, command, ignore_error=False):
251 self._log_debug('Run adb command: ' + str(command))
253 error_handler = self._executive.ignore_error
257 result = self._executive.run_command(self.adb_command() + command, error_handler=error_handler, debug_logging=self._debug_logging)
259 # We limit the length to avoid outputting too verbose commands, such as "adb logcat".
260 self._log_debug('Run adb result: ' + result[:80])
263 def get_serial(self):
264 return self._device_serial
266 def adb_command(self):
267 return [AndroidCommands.adb_command_path(self._executive, self._debug_logging), '-s', self._device_serial]
270 def set_adb_command_path_options(paths):
271 AndroidCommands._adb_command_path_options = paths
274 def adb_command_path(executive, debug_logging):
275 if AndroidCommands._adb_command_path:
276 return AndroidCommands._adb_command_path
278 assert AndroidCommands._adb_command_path_options, 'No commands paths have been set to look for the "adb" command.'
281 command_version = None
282 for path_option in AndroidCommands._adb_command_path_options:
283 path_version = AndroidCommands._determine_adb_version(path_option, executive, debug_logging)
286 if command_version != None and path_version < command_version:
289 command_path = path_option
290 command_version = path_version
292 assert command_path, 'Unable to locate the "adb" command. Are you using an Android checkout of Chromium?'
294 AndroidCommands._adb_command_path = command_path
297 # Local private methods.
299 def _log_error(self, message):
300 _log.error('[%s] %s' % (self._device_serial, message))
302 def _log_info(self, message):
303 _log.info('[%s] %s' % (self._device_serial, message))
305 def _log_debug(self, message):
306 if self._debug_logging:
307 _log.debug('[%s] %s' % (self._device_serial, message))
310 def _determine_adb_version(adb_command_path, executive, debug_logging):
311 re_version = re.compile('^.*version ([\d\.]+)$')
313 output = executive.run_command([adb_command_path, 'version'], error_handler=executive.ignore_error,
314 debug_logging=debug_logging)
318 result = re_version.match(output)
319 if not output or not result:
322 return [int(n) for n in result.group(1).split('.')]
325 # A class to encapsulate device status and information, such as the AndroidCommands
326 # instances and whether the device has been set up.
327 class AndroidDevices(object):
328 # Percentage of battery a device needs to have in order for it to be considered
329 # to participate in running the layout tests.
330 MINIMUM_BATTERY_PERCENTAGE = 30
332 def __init__(self, executive, default_device=None, debug_logging=False):
333 self._usable_devices = []
334 self._default_device = default_device
335 self._prepared_devices = []
336 self._debug_logging = debug_logging
338 def prepared_devices(self):
339 return self._prepared_devices
341 def usable_devices(self, executive):
342 if self._usable_devices:
343 return self._usable_devices
345 if self._default_device:
346 self._usable_devices = [AndroidCommands(executive, self._default_device, self._debug_logging)]
347 return self._usable_devices
349 # Example "adb devices" command output:
350 # List of devices attached
351 # 0123456789ABCDEF device
352 re_device = re.compile('^([a-zA-Z0-9_:.-]+)\tdevice$', re.MULTILINE)
354 result = executive.run_command([AndroidCommands.adb_command_path(executive, debug_logging=self._debug_logging), 'devices'],
355 error_handler=executive.ignore_error, debug_logging=self._debug_logging)
356 devices = re_device.findall(result)
360 for device_serial in sorted(devices):
361 commands = AndroidCommands(executive, device_serial, self._debug_logging)
362 if self._battery_level_for_device(commands) < AndroidDevices.MINIMUM_BATTERY_PERCENTAGE:
363 _log.warning('Device with serial "%s" skipped because it has less than %d percent battery.'
364 % (commands.get_serial(), AndroidDevices.MINIMUM_BATTERY_PERCENTAGE))
367 if not self._is_device_screen_on(commands):
368 _log.warning('Device with serial "%s" skipped because the screen must be on.' % commands.get_serial())
371 self._usable_devices.append(commands)
373 return self._usable_devices
375 def get_device(self, executive, device_index):
376 devices = self.usable_devices(executive)
377 if device_index >= len(devices):
378 raise AssertionError('Device index exceeds number of usable devices.')
380 return devices[device_index]
382 def is_device_prepared(self, device_serial):
383 return device_serial in self._prepared_devices
385 def set_device_prepared(self, device_serial):
386 self._prepared_devices.append(device_serial)
389 def _battery_level_for_device(self, commands):
390 battery_status = commands.run(['shell', 'dumpsys', 'battery'])
391 if 'Error' in battery_status or "Can't find service: battery" in battery_status:
392 _log.warning('Unable to read the battery level from device with serial "%s".' % commands.get_serial())
395 return int(re.findall('level: (\d+)', battery_status)[0])
397 def _is_device_screen_on(self, commands):
398 power_status = commands.run(['shell', 'dumpsys', 'power'])
399 return 'mScreenOn=true' in power_status or 'mScreenOn=SCREEN_ON_BIT' in power_status
402 class AndroidPort(base.Port):
403 port_name = 'android'
405 # Avoid initializing the adb path [worker count]+1 times by storing it as a static member.
408 SUPPORTED_VERSIONS = ('android')
410 FALLBACK_PATHS = {'icecreamsandwich': ['android'] + linux.LinuxPort.latest_platform_fallback_path()}
412 # Android has aac and mp3 codecs built in.
413 PORT_HAS_AUDIO_CODECS_BUILT_IN = True
415 def __init__(self, host, port_name, **kwargs):
416 super(AndroidPort, self).__init__(host, port_name, **kwargs)
418 self._operating_system = 'android'
419 self._version = 'icecreamsandwich'
421 self._host_port = factory.PortFactory(host).get('chromium', **kwargs)
422 self._server_process_constructor = self._android_server_process_constructor
424 if not self.get_option('disable_breakpad'):
425 self._dump_reader = DumpReaderAndroid(host, self._build_path())
427 if self.driver_name() != self.CONTENT_SHELL_NAME:
428 raise AssertionError('Layout tests on Android only support content_shell as the driver.')
430 self._driver_details = ContentShellDriverDetails()
432 # Initialize the AndroidDevices class which tracks available devices.
433 default_device = None
434 if hasattr(self._options, 'adb_device') and len(self._options.adb_device):
435 default_device = self._options.adb_device
437 self._debug_logging = self.get_option('android_logging')
438 self._devices = AndroidDevices(self._executive, default_device, self._debug_logging)
440 # Tell AndroidCommands where to search for the "adb" command.
441 AndroidCommands.set_adb_command_path_options(['adb',
442 self.path_from_chromium_base('third_party', 'android_tools', 'sdk', 'platform-tools', 'adb')])
444 prepared_devices = self.get_option('prepared_devices', [])
445 for serial in prepared_devices:
446 self._devices.set_device_prepared(serial)
448 def default_smoke_test_only(self):
451 # Local public methods.
452 def path_to_forwarder(self):
453 return self._build_path('forwarder')
455 def path_to_md5sum(self):
456 return self._build_path(MD5SUM_DEVICE_FILE_NAME)
458 def path_to_md5sum_host(self):
459 return self._build_path(MD5SUM_HOST_FILE_NAME)
461 def additional_drt_flag(self):
462 return self._driver_details.additional_command_line_flags(use_breakpad=not self.get_option('disable_breakpad'))
464 def default_timeout_ms(self):
465 # Android platform has less computing power than desktop platforms.
466 # Using 10 seconds allows us to pass most slow tests which are not
467 # marked as slow tests on desktop platforms.
470 def driver_stop_timeout(self):
471 # The driver doesn't respond to closing stdin, so we might as well stop the driver immediately.
474 def default_child_processes(self):
475 usable_devices = self._devices.usable_devices(self._executive)
476 if not usable_devices:
477 raise test_run_results.TestRunException(test_run_results.NO_DEVICES_EXIT_STATUS, "Unable to find any attached Android devices.")
478 return len(usable_devices)
480 def check_wdiff(self, logging=True):
481 return self._host_port.check_wdiff(logging)
483 def check_build(self, needs_http, printer):
484 exit_status = super(AndroidPort, self).check_build(needs_http, printer)
488 result = self._check_file_exists(self.path_to_md5sum(), 'md5sum utility')
489 result = self._check_file_exists(self.path_to_md5sum_host(), 'md5sum host utility') and result
490 result = self._check_file_exists(self.path_to_forwarder(), 'forwarder utility') and result
493 # There is a race condition in adb at least <= 4.3 on Linux that causes it to go offline periodically
494 # We set the processor affinity for any running adb process to attempt to work around this.
495 # See crbug.com/268450
496 if self.host.platform.is_linux():
497 pids = self._executive.running_pids(lambda name: 'adb' in name)
499 # Apparently adb is not running, which is unusual. Running any adb command should start it.
500 self._executive.run_command(['adb', 'devices'])
501 pids = self._executive.running_pids(lambda name: 'adb' in name)
503 _log.error("The adb daemon does not appear to be running.")
507 self._executive.run_command(['taskset', '-p', '-c', '0', str(pid)])
510 _log.error('For complete Android build requirements, please see:')
512 _log.error(' http://code.google.com/p/chromium/wiki/AndroidBuildInstructions')
513 return test_run_results.UNEXPECTED_ERROR_EXIT_STATUS
515 return self._check_devices(printer)
517 def _check_devices(self, printer):
518 # Printer objects aren't threadsafe, so we need to protect calls to them.
519 lock = threading.Lock()
522 # Push the executables and other files to the devices; doing this now
523 # means we can do this in parallel in the manager process and not mix
524 # this in with starting and stopping workers.
525 def setup_device(worker_number):
526 d = self.create_driver(worker_number)
527 serial = d._android_commands.get_serial()
529 def log_safely(msg, throttled=True):
531 callback = printer.write_throttled_update
533 callback = printer.write_update
536 callback("[%s] %s" % (serial, msg))
540 log_safely("preparing device", throttled=False)
542 d._setup_test(log_safely)
543 log_safely("device prepared", throttled=False)
544 except (ScriptError, driver.DeviceFailure) as e:
546 _log.warning("[%s] failed to prepare_device: %s" % (serial, str(e)))
548 except KeyboardInterrupt:
552 # FIXME: It would be nice if we knew how many workers we needed.
553 num_workers = self.default_child_processes()
554 num_child_processes = int(self.get_option('child_processes'))
555 if num_child_processes:
556 num_workers = min(num_workers, num_child_processes)
558 pool = ThreadPool(num_workers)
560 pool.map(setup_device, range(num_workers))
561 except KeyboardInterrupt:
567 if not self._devices.prepared_devices():
568 _log.error('Could not prepare any devices for testing.')
569 return test_run_results.NO_DEVICES_EXIT_STATUS
570 return test_run_results.OK_EXIT_STATUS
572 def setup_test_run(self):
573 super(AndroidPort, self).setup_test_run()
575 # By setting this on the options object, we can propagate the list
576 # of prepared devices to the workers (it is read in __init__()).
577 if self._devices._prepared_devices:
578 self._options.prepared_devices = self._devices.prepared_devices()
580 # We were called with --no-build, so assume the devices are up to date.
581 self._options.prepared_devices = [d.get_serial() for d in self._devices.usable_devices(self.host.executive)]
583 def num_workers(self, requested_num_workers):
584 return min(len(self._options.prepared_devices), requested_num_workers)
586 def check_sys_deps(self, needs_http):
587 for (font_dirs, font_file, package) in HOST_FONT_FILES:
589 for font_dir in font_dirs:
590 font_path = font_dir + font_file
591 if self._check_file_exists(font_path, '', logging=False):
595 _log.error('You are missing %s under %s. Try installing %s. See build instructions.' % (font_file, font_dirs, package))
596 return test_run_results.SYS_DEPS_EXIT_STATUS
597 return test_run_results.OK_EXIT_STATUS
599 def requires_http_server(self):
600 """Chromium Android runs tests on devices, and uses the HTTP server to
601 serve the actual layout tests to the test driver."""
604 def start_http_server(self, additional_dirs=None, number_of_servers=0):
605 if not additional_dirs:
607 additional_dirs[PERF_TEST_PATH_PREFIX] = self.perf_tests_dir()
608 additional_dirs[LAYOUT_TEST_PATH_PREFIX] = self.layout_tests_dir()
609 super(AndroidPort, self).start_http_server(additional_dirs, number_of_servers)
611 def create_driver(self, worker_number, no_timeout=False):
612 return ChromiumAndroidDriver(self, worker_number, pixel_tests=self.get_option('pixel_tests'),
613 driver_details=self._driver_details,
614 android_devices=self._devices,
615 # Force no timeout to avoid test driver timeouts before NRWT.
618 def driver_cmd_line(self):
619 # Override to return the actual test driver's command line.
620 return self.create_driver(0)._android_driver_cmd_line(self.get_option('pixel_tests'), [])
622 def clobber_old_port_specific_results(self):
623 if not self.get_option('disable_breakpad'):
624 self._dump_reader.clobber_old_results()
626 # Overridden protected methods.
628 def _build_path(self, *comps):
629 return self._host_port._build_path(*comps)
631 def _build_path_with_configuration(self, configuration, *comps):
632 return self._host_port._build_path_with_configuration(configuration, *comps)
634 def _path_to_apache(self):
635 return self._host_port._path_to_apache()
637 def _path_to_apache_config_file(self):
638 return self._host_port._path_to_apache_config_file()
640 def _path_to_driver(self, configuration=None):
641 return self._build_path_with_configuration(configuration, self._driver_details.apk_name())
643 def _path_to_helper(self):
646 def _path_to_image_diff(self):
647 return self._host_port._path_to_image_diff()
649 def _path_to_lighttpd(self):
650 return self._host_port._path_to_lighttpd()
652 def _path_to_lighttpd_modules(self):
653 return self._host_port._path_to_lighttpd_modules()
655 def _path_to_lighttpd_php(self):
656 return self._host_port._path_to_lighttpd_php()
658 def _path_to_wdiff(self):
659 return self._host_port._path_to_wdiff()
661 def _shut_down_http_server(self, pid):
662 return self._host_port._shut_down_http_server(pid)
664 def _driver_class(self):
665 return ChromiumAndroidDriver
667 # Local private methods.
670 def _android_server_process_constructor(port, server_name, cmd_line, env=None, logging=False):
671 return server_process.ServerProcess(port, server_name, cmd_line, env,
672 universal_newlines=True, treat_no_data_as_crash=True, logging=logging)
675 class AndroidPerf(SingleFileOutputProfiler):
676 _cached_perf_host_path = None
677 _have_searched_for_perf_host = False
679 def __init__(self, host, executable_path, output_dir, android_commands, symfs_path, kallsyms_path, identifier=None):
680 super(AndroidPerf, self).__init__(host, executable_path, output_dir, "data", identifier)
681 self._android_commands = android_commands
682 self._perf_process = None
683 self._symfs_path = symfs_path
684 self._kallsyms_path = kallsyms_path
686 def check_configuration(self):
687 # Check that perf is installed
688 if not self._android_commands.file_exists('/system/bin/perf'):
689 print "Cannot find /system/bin/perf on device %s" % self._android_commands.get_serial()
692 # Check that the device is a userdebug build (or at least has the necessary libraries).
693 if self._android_commands.run(['shell', 'getprop', 'ro.build.type']).strip() != 'userdebug':
694 print "Device %s is not flashed with a userdebug build of Android" % self._android_commands.get_serial()
697 # FIXME: Check that the binary actually is perf-able (has stackframe pointers)?
698 # objdump -s a function and make sure it modifies the fp?
699 # Instruct users to rebuild after export GYP_DEFINES="profiling=1 $GYP_DEFINES"
702 def print_setup_instructions(self):
704 perf on android requires a 'userdebug' build of Android, see:
705 http://source.android.com/source/building-devices.html"
707 The perf command can be built from:
708 https://android.googlesource.com/platform/external/linux-tools-perf/
709 and requires libefl, libebl, libdw, and libdwfl available in:
710 https://android.googlesource.com/platform/external/elfutils/
712 The test driver must be built with profiling=1, make sure you've done:
713 export GYP_DEFINES="profiling=1 $GYP_DEFINES"
714 update-webkit --chromium-android
715 build-webkit --chromium-android
717 Googlers should read:
718 http://goto.google.com/cr-android-perf-howto
721 def attach_to_pid(self, pid):
723 assert(self._perf_process == None)
724 # FIXME: This can't be a fixed timeout!
725 cmd = self._android_commands.adb_command() + ['shell', 'perf', 'record', '-g', '-p', pid, 'sleep', 30]
726 self._perf_process = self._host.executive.popen(cmd)
728 def _perf_version_string(self, perf_path):
730 return self._host.executive.run_command([perf_path, '--version'])
734 def _find_perfhost_binary(self):
735 perfhost_version = self._perf_version_string('perfhost_linux')
737 return 'perfhost_linux'
738 perf_version = self._perf_version_string('perf')
743 def _perfhost_path(self):
744 if self._have_searched_for_perf_host:
745 return self._cached_perf_host_path
746 self._have_searched_for_perf_host = True
747 self._cached_perf_host_path = self._find_perfhost_binary()
748 return self._cached_perf_host_path
750 def _first_ten_lines_of_profile(self, perf_output):
751 match = re.search("^#[^\n]*\n((?: [^\n]*\n){1,10})", perf_output, re.MULTILINE)
752 return match.group(1) if match else None
754 def profile_after_exit(self):
755 perf_exitcode = self._perf_process.wait()
756 if perf_exitcode != 0:
757 print "Perf failed (exit code: %i), can't process results." % perf_exitcode
760 self._android_commands.pull('/data/perf.data', self._output_path)
762 perfhost_path = self._perfhost_path()
763 perfhost_report_command = [
765 '--input', self._output_path,
766 '--symfs', self._symfs_path,
767 '--kallsyms', self._kallsyms_path,
770 perfhost_args = [perfhost_path] + perfhost_report_command + ['--call-graph', 'none']
771 perf_output = self._host.executive.run_command(perfhost_args)
772 # We could save off the full -g report to a file if users found that useful.
773 print self._first_ten_lines_of_profile(perf_output)
776 Failed to find perfhost_linux binary, can't process samples from the device.
778 perfhost_linux can be built from:
779 https://android.googlesource.com/platform/external/linux-tools-perf/
780 also, modern versions of perf (available from apt-get install goobuntu-kernel-tools-common)
781 may also be able to process the perf.data files from the device.
783 Googlers should read:
784 http://goto.google.com/cr-android-perf-howto
785 for instructions on installing pre-built copies of perfhost_linux
786 http://crbug.com/165250 discusses making these pre-built binaries externally available.
789 perfhost_display_patch = perfhost_path if perfhost_path else 'perfhost_linux'
790 print "To view the full profile, run:"
791 print ' '.join([perfhost_display_patch] + perfhost_report_command)
794 class ChromiumAndroidDriver(driver.Driver):
795 def __init__(self, port, worker_number, pixel_tests, driver_details, android_devices, no_timeout=False):
796 super(ChromiumAndroidDriver, self).__init__(port, worker_number, pixel_tests, no_timeout)
797 self._in_fifo_path = driver_details.device_fifo_directory() + 'stdin.fifo'
798 self._out_fifo_path = driver_details.device_fifo_directory() + 'test.fifo'
799 self._err_fifo_path = driver_details.device_fifo_directory() + 'stderr.fifo'
800 self._read_stdout_process = None
801 self._read_stderr_process = None
802 self._forwarder_process = None
803 self._original_governors = {}
804 self._original_kptr_restrict = None
806 self._android_devices = android_devices
807 self._android_commands = android_devices.get_device(port._executive, worker_number)
808 self._driver_details = driver_details
809 self._debug_logging = self._port._debug_logging
810 self._created_cmd_line = False
811 self._device_failed = False
813 # FIXME: If we taught ProfileFactory about "target" devices we could
814 # just use the logic in Driver instead of duplicating it here.
815 if self._port.get_option("profile"):
816 # FIXME: This should be done once, instead of per-driver!
817 symfs_path = self._find_or_create_symfs()
818 kallsyms_path = self._update_kallsyms_cache(symfs_path)
819 # FIXME: We should pass this some sort of "Bridge" object abstraction around ADB instead of a path/device pair.
820 self._profiler = AndroidPerf(self._port.host, self._port._path_to_driver(), self._port.results_directory(),
821 self._android_commands, symfs_path, kallsyms_path)
822 # FIXME: This is a layering violation and should be moved to Port.check_sys_deps
823 # once we have an abstraction around an adb_path/device_serial pair to make it
824 # easy to make these class methods on AndroidPerf.
825 if not self._profiler.check_configuration():
826 self._profiler.print_setup_instructions()
829 self._profiler = None
832 self._teardown_performance()
833 self._clean_up_cmd_line()
834 super(ChromiumAndroidDriver, self).__del__()
836 def _update_kallsyms_cache(self, output_dir):
837 kallsyms_name = "%s-kallsyms" % self._android_commands.get_serial()
838 kallsyms_cache_path = self._port.host.filesystem.join(output_dir, kallsyms_name)
840 self._android_commands.restart_as_root()
842 saved_kptr_restrict = self._android_commands.run(['shell', 'cat', KPTR_RESTRICT_PATH]).strip()
843 self._android_commands.run(['shell', 'echo', '0', '>', KPTR_RESTRICT_PATH])
845 print "Updating kallsyms file (%s) from device" % kallsyms_cache_path
846 self._android_commands.pull("/proc/kallsyms", kallsyms_cache_path)
848 self._android_commands.run(['shell', 'echo', saved_kptr_restrict, '>', KPTR_RESTRICT_PATH])
850 return kallsyms_cache_path
852 def _find_or_create_symfs(self):
853 environment = self._port.host.copy_current_environment()
854 env = environment.to_dictionary()
855 fs = self._port.host.filesystem
857 if 'ANDROID_SYMFS' in env:
858 symfs_path = env['ANDROID_SYMFS']
860 symfs_path = fs.join(self._port.results_directory(), 'symfs')
861 print "ANDROID_SYMFS not set, using %s" % symfs_path
863 # find the installed path, and the path of the symboled built library
864 # FIXME: We should get the install path from the device!
865 symfs_library_path = fs.join(symfs_path, "data/app-lib/%s-1/%s" % (self._driver_details.package_name(), self._driver_details.library_name()))
866 built_library_path = self._port._build_path('lib', self._driver_details.library_name())
867 assert(fs.exists(built_library_path))
869 # FIXME: Ideally we'd check the sha1's first and make a soft-link instead of copying (since we probably never care about windows).
870 print "Updating symfs libary (%s) from built copy (%s)" % (symfs_library_path, built_library_path)
871 fs.maybe_make_directory(fs.dirname(symfs_library_path))
872 fs.copyfile(built_library_path, symfs_library_path)
876 def _setup_md5sum_and_push_data_if_needed(self, log_callback):
877 self._md5sum_path = self._port.path_to_md5sum()
878 if not self._android_commands.file_exists(MD5SUM_DEVICE_PATH):
879 if not self._android_commands.push(self._md5sum_path, MD5SUM_DEVICE_PATH):
880 self._abort('Could not push md5sum to device')
882 self._push_executable(log_callback)
883 self._push_fonts(log_callback)
884 self._push_test_resources(log_callback)
886 def _setup_test(self, log_callback):
887 # FIXME: Move this routine and its subroutines off of the AndroidDriver
888 # class and onto AndroidCommands or some other helper class, so that we
889 # can initialize the device without needing to create a driver.
891 if self._android_devices.is_device_prepared(self._android_commands.get_serial()):
894 self._android_commands.restart_adb()
895 self._android_commands.restart_as_root()
896 self._setup_md5sum_and_push_data_if_needed(log_callback)
897 self._setup_performance()
899 # Required by webkit_support::GetWebKitRootDirFilePath().
900 # Other directories will be created automatically by adb push.
901 self._android_commands.mkdir(DEVICE_SOURCE_ROOT_DIR + 'chrome')
903 # Allow the test driver to get full read and write access to the directory on the device,
904 # as well as for the FIFOs. We'll need a world writable directory.
905 self._android_commands.mkdir(self._driver_details.device_directory(), chmod='777')
906 self._android_commands.mkdir(self._driver_details.device_fifo_directory(), chmod='777')
908 # Make sure that the disk cache on the device resets to a clean state.
909 self._android_commands.run(['shell', 'rm', '-r', self._driver_details.device_cache_directory()])
911 # Mark this device as having been set up.
912 self._android_devices.set_device_prepared(self._android_commands.get_serial())
914 def _log_error(self, message):
915 _log.error('[%s] %s' % (self._android_commands.get_serial(), message))
917 def _log_warning(self, message):
918 _log.warning('[%s] %s' % (self._android_commands.get_serial(), message))
920 def _log_debug(self, message):
921 if self._debug_logging:
922 _log.debug('[%s] %s' % (self._android_commands.get_serial(), message))
924 def _abort(self, message):
925 self._device_failed = True
926 raise driver.DeviceFailure('[%s] %s' % (self._android_commands.get_serial(), message))
929 def _extract_hashes_from_md5sum_output(md5sum_output):
931 return [line.split(' ')[0] for line in md5sum_output]
933 def _files_match(self, host_file, device_file):
934 assert self._port.host.filesystem.exists(host_file)
935 device_hashes = self._extract_hashes_from_md5sum_output(
936 self._port.host.executive.popen(self._android_commands.adb_command() + ['shell', MD5SUM_DEVICE_PATH, device_file],
937 stdout=subprocess.PIPE).stdout)
938 host_hashes = self._extract_hashes_from_md5sum_output(
939 self._port.host.executive.popen(args=['%s_host' % self._md5sum_path, host_file],
940 stdout=subprocess.PIPE).stdout)
941 return host_hashes and device_hashes == host_hashes
943 def _push_file_if_needed(self, host_file, device_file, log_callback):
944 basename = self._port.host.filesystem.basename(host_file)
945 log_callback("checking %s" % basename)
946 if not self._files_match(host_file, device_file):
947 log_callback("pushing %s" % basename)
948 self._android_commands.push(host_file, device_file)
950 def _push_executable(self, log_callback):
951 self._push_file_if_needed(self._port.path_to_forwarder(), self._driver_details.device_forwarder_path(), log_callback)
952 for resource in self._driver_details.additional_resources():
953 self._push_file_if_needed(self._port._build_path(resource), self._driver_details.device_directory() + resource, log_callback)
955 self._push_file_if_needed(self._port._build_path('android_main_fonts.xml'), self._driver_details.device_directory() + 'android_main_fonts.xml', log_callback)
956 self._push_file_if_needed(self._port._build_path('android_fallback_fonts.xml'), self._driver_details.device_directory() + 'android_fallback_fonts.xml', log_callback)
958 log_callback("checking apk")
959 if self._files_match(self._port._build_path('apks', 'ContentShell.apk'),
960 '/data/app/org.chromium.content_shell_apk-1.apk'):
963 log_callback("uninstalling apk")
964 self._android_commands.run(['uninstall', self._driver_details.package_name()])
965 driver_host_path = self._port._path_to_driver()
966 log_callback("installing apk")
967 install_result = self._android_commands.run(['install', driver_host_path])
968 if install_result.find('Success') == -1:
969 self._abort('Failed to install %s onto device: %s' % (driver_host_path, install_result))
971 def _push_fonts(self, log_callback):
972 path_to_ahem_font = self._port._build_path('AHEM____.TTF')
973 self._push_file_if_needed(path_to_ahem_font, self._driver_details.device_fonts_directory() + 'AHEM____.TTF', log_callback)
974 for (host_dirs, font_file, package) in HOST_FONT_FILES:
975 for host_dir in host_dirs:
976 host_font_path = host_dir + font_file
977 if self._port._check_file_exists(host_font_path, '', logging=False):
978 self._push_file_if_needed(host_font_path, self._driver_details.device_fonts_directory() + font_file, log_callback)
980 def _push_test_resources(self, log_callback):
981 for resource in TEST_RESOURCES_TO_PUSH:
982 self._push_file_if_needed(self._port.layout_tests_dir() + '/' + resource, DEVICE_LAYOUT_TESTS_DIR + resource, log_callback)
984 def _get_last_stacktrace(self):
985 tombstones = self._android_commands.run(['shell', 'ls', '-n', '/data/tombstones/tombstone_*'])
986 if not tombstones or tombstones.startswith('/data/tombstones/tombstone_*: No such file or directory'):
987 self._log_error('The driver crashed, but no tombstone found!')
990 if tombstones.startswith('/data/tombstones/tombstone_*: Permission denied'):
991 # FIXME: crbug.com/321489 ... figure out why this happens.
992 self._log_error('The driver crashed, but we could not read the tombstones!')
995 tombstones = tombstones.rstrip().split('\n')
996 last_tombstone = None
997 for tombstone in tombstones:
1000 # permission uid gid size date time filename
1001 # -rw------- 1000 1000 45859 2011-04-13 06:00 tombstone_00
1002 fields = tombstone.split()
1003 if len(fields) != 7:
1004 self._log_warning("unexpected line in tombstone output, skipping: '%s'" % tombstone)
1007 if not last_tombstone or fields[4] + fields[5] >= last_tombstone[4] + last_tombstone[5]:
1008 last_tombstone = fields
1012 if not last_tombstone:
1013 self._log_error('The driver crashed, but we could not find any valid tombstone!')
1016 # Use Android tool vendor/google/tools/stack to convert the raw
1017 # stack trace into a human readable format, if needed.
1018 # It takes a long time, so don't do it here.
1019 return '%s\n%s' % (' '.join(last_tombstone),
1020 self._android_commands.run(['shell', 'cat', '/data/tombstones/' + last_tombstone[6]]))
1022 def _get_logcat(self):
1023 return self._android_commands.run(['logcat', '-d', '-v', 'threadtime'])
1025 def _setup_performance(self):
1026 # Disable CPU scaling and drop ram cache to reduce noise in tests
1027 if not self._original_governors:
1028 governor_files = self._android_commands.run(['shell', 'ls', SCALING_GOVERNORS_PATTERN])
1029 if governor_files.find('No such file or directory') == -1:
1030 for file in governor_files.split():
1031 self._original_governors[file] = self._android_commands.run(['shell', 'cat', file]).strip()
1032 self._android_commands.run(['shell', 'echo', 'performance', '>', file])
1034 def _teardown_performance(self):
1035 for file, original_content in self._original_governors.items():
1036 self._android_commands.run(['shell', 'echo', original_content, '>', file])
1037 self._original_governors = {}
1039 def _get_crash_log(self, stdout, stderr, newer_than):
1042 stdout += '********* [%s] Logcat:\n%s' % (self._android_commands.get_serial(), self._get_logcat())
1045 stderr += '********* [%s] Tombstone file:\n%s' % (self._android_commands.get_serial(), self._get_last_stacktrace())
1047 if not self._port.get_option('disable_breakpad'):
1048 crashes = self._pull_crash_dumps_from_device()
1049 for crash in crashes:
1050 stderr += '********* [%s] breakpad minidump %s:\n%s' % (self._port.host.filesystem.basename(crash), self._android_commands.get_serial(), self._port._dump_reader._get_stack_from_dump(crash))
1052 return super(ChromiumAndroidDriver, self)._get_crash_log(stdout, stderr, newer_than)
1054 def cmd_line(self, pixel_tests, per_test_args):
1055 # The returned command line is used to start _server_process. In our case, it's an interactive 'adb shell'.
1056 # The command line passed to the driver process is returned by _driver_cmd_line() instead.
1057 return self._android_commands.adb_command() + ['shell']
1059 def _android_driver_cmd_line(self, pixel_tests, per_test_args):
1060 return driver.Driver.cmd_line(self, pixel_tests, per_test_args)
1063 def _loop_with_timeout(condition, timeout_secs):
1064 deadline = time.time() + timeout_secs
1065 while time.time() < deadline:
1070 def _all_pipes_created(self):
1071 return (self._android_commands.file_exists(self._in_fifo_path) and
1072 self._android_commands.file_exists(self._out_fifo_path) and
1073 self._android_commands.file_exists(self._err_fifo_path))
1075 def _remove_all_pipes(self):
1076 for file in [self._in_fifo_path, self._out_fifo_path, self._err_fifo_path]:
1077 self._android_commands.run(['shell', 'rm', file])
1079 return (not self._android_commands.file_exists(self._in_fifo_path) and
1080 not self._android_commands.file_exists(self._out_fifo_path) and
1081 not self._android_commands.file_exists(self._err_fifo_path))
1083 def start(self, pixel_tests, per_test_args):
1084 # We override the default start() so that we can call _android_driver_cmd_line()
1085 # instead of cmd_line().
1086 new_cmd_line = self._android_driver_cmd_line(pixel_tests, per_test_args)
1088 # Since _android_driver_cmd_line() is different than cmd_line() we need to provide
1089 # our own mechanism for detecting when the process should be stopped.
1090 if self._current_cmd_line is None:
1091 self._current_android_cmd_line = None
1092 if new_cmd_line != self._current_android_cmd_line:
1094 self._current_android_cmd_line = new_cmd_line
1096 super(ChromiumAndroidDriver, self).start(pixel_tests, per_test_args)
1098 def _start(self, pixel_tests, per_test_args):
1099 if not self._android_devices.is_device_prepared(self._android_commands.get_serial()):
1100 raise driver.DeviceFailure("%s is not prepared in _start()" % self._android_commands.get_serial())
1102 for retries in range(3):
1104 if self._start_once(pixel_tests, per_test_args):
1106 except ScriptError as e:
1107 self._abort('ScriptError("%s") in _start()' % str(e))
1109 self._log_error('Failed to start the content_shell application. Retries=%d. Log:%s' % (retries, self._get_logcat()))
1112 self._abort('Failed to start the content_shell application multiple times. Giving up.')
1114 def _start_once(self, pixel_tests, per_test_args):
1115 super(ChromiumAndroidDriver, self)._start(pixel_tests, per_test_args, wait_for_ready=False)
1117 self._log_debug('Starting forwarder')
1118 self._forwarder_process = self._port._server_process_constructor(
1119 self._port, 'Forwarder', self._android_commands.adb_command() + ['shell', '%s -D %s' % (self._driver_details.device_forwarder_path(), FORWARD_PORTS)])
1120 self._forwarder_process.start()
1122 deadline = time.time() + DRIVER_START_STOP_TIMEOUT_SECS
1123 if not self._wait_for_server_process_output(self._forwarder_process, deadline, 'Forwarding device port'):
1126 self._android_commands.run(['logcat', '-c'])
1128 cmd_line_file_path = self._driver_details.command_line_file()
1129 original_cmd_line_file_path = cmd_line_file_path + '.orig'
1130 if self._android_commands.file_exists(cmd_line_file_path) and not self._android_commands.file_exists(original_cmd_line_file_path):
1131 # We check for both the normal path and the backup because we do not want to step
1132 # on the backup. Otherwise, we'd clobber the backup whenever we changed the
1133 # command line during the run.
1134 self._android_commands.run(['shell', 'mv', cmd_line_file_path, original_cmd_line_file_path])
1136 self._android_commands.run(['shell', 'echo'] + self._android_driver_cmd_line(pixel_tests, per_test_args) + ['>', self._driver_details.command_line_file()])
1137 self._created_cmd_line = True
1139 self._android_commands.run(['shell', 'rm', '-rf', self._driver_details.device_crash_dumps_directory()])
1140 self._android_commands.mkdir(self._driver_details.device_crash_dumps_directory(), chmod='777')
1142 start_result = self._android_commands.run(['shell', 'am', 'start', '-e', 'RunInSubThread', '-n', self._driver_details.activity_name()])
1143 if start_result.find('Exception') != -1:
1144 self._log_error('Failed to start the content_shell application. Exception:\n' + start_result)
1147 if not ChromiumAndroidDriver._loop_with_timeout(self._all_pipes_created, DRIVER_START_STOP_TIMEOUT_SECS):
1150 # Read back the shell prompt to ensure adb shell ready.
1151 deadline = time.time() + DRIVER_START_STOP_TIMEOUT_SECS
1152 self._server_process.start()
1153 self._read_prompt(deadline)
1154 self._log_debug('Interactive shell started')
1156 # Start a process to read from the stdout fifo of the test driver and print to stdout.
1157 self._log_debug('Redirecting stdout to ' + self._out_fifo_path)
1158 self._read_stdout_process = self._port._server_process_constructor(
1159 self._port, 'ReadStdout', self._android_commands.adb_command() + ['shell', 'cat', self._out_fifo_path])
1160 self._read_stdout_process.start()
1162 # Start a process to read from the stderr fifo of the test driver and print to stdout.
1163 self._log_debug('Redirecting stderr to ' + self._err_fifo_path)
1164 self._read_stderr_process = self._port._server_process_constructor(
1165 self._port, 'ReadStderr', self._android_commands.adb_command() + ['shell', 'cat', self._err_fifo_path])
1166 self._read_stderr_process.start()
1168 self._log_debug('Redirecting stdin to ' + self._in_fifo_path)
1169 self._server_process.write('cat >%s\n' % self._in_fifo_path)
1171 # Combine the stdout and stderr pipes into self._server_process.
1172 self._server_process.replace_outputs(self._read_stdout_process._proc.stdout, self._read_stderr_process._proc.stdout)
1174 def deadlock_detector(processes, normal_startup_event):
1175 if not ChromiumAndroidDriver._loop_with_timeout(lambda: normal_startup_event.is_set(), DRIVER_START_STOP_TIMEOUT_SECS):
1176 # If normal_startup_event is not set in time, the main thread must be blocked at
1177 # reading/writing the fifo. Kill the fifo reading/writing processes to let the
1178 # main thread escape from the deadlocked state. After that, the main thread will
1179 # treat this as a crash.
1180 self._log_error('Deadlock detected. Processes killed.')
1184 # Start a thread to kill the pipe reading/writing processes on deadlock of the fifos during startup.
1185 normal_startup_event = threading.Event()
1186 threading.Thread(name='DeadlockDetector', target=deadlock_detector,
1187 args=([self._server_process, self._read_stdout_process, self._read_stderr_process], normal_startup_event)).start()
1189 # The test driver might crash during startup or when the deadlock detector hits
1190 # a deadlock and kills the fifo reading/writing processes.
1191 if not self._wait_for_server_process_output(self._server_process, deadline, '#READY'):
1194 # Inform the deadlock detector that the startup is successful without deadlock.
1195 normal_startup_event.set()
1196 self._log_debug("content_shell is ready")
1199 def _pid_from_android_ps_output(self, ps_output, package_name):
1200 # ps output seems to be fixed width, we only care about the name and the pid
1201 # u0_a72 21630 125 947920 59364 ffffffff 400beee4 S org.chromium.native_test
1202 for line in ps_output.split('\n'):
1203 if line.find(self._driver_details.package_name()) != -1:
1204 match = re.match(r'\S+\s+(\d+)', line)
1205 return int(match.group(1))
1207 def _pid_on_target(self):
1208 # FIXME: There must be a better way to do this than grepping ps output!
1209 ps_output = self._android_commands.run(['shell', 'ps'])
1210 return self._pid_from_android_ps_output(ps_output, self._driver_details.package_name())
1213 if not self._device_failed:
1214 # Do not try to stop the application if there's something wrong with the device; adb may hang.
1215 # FIXME: crbug.com/305040. Figure out if it's really hanging (and why).
1216 self._android_commands.run(['shell', 'am', 'force-stop', self._driver_details.package_name()])
1218 if self._read_stdout_process:
1219 self._read_stdout_process.kill()
1220 self._read_stdout_process = None
1222 if self._read_stderr_process:
1223 self._read_stderr_process.kill()
1224 self._read_stderr_process = None
1226 super(ChromiumAndroidDriver, self).stop()
1228 if self._forwarder_process:
1229 self._forwarder_process.kill()
1230 self._forwarder_process = None
1232 if self._android_devices.is_device_prepared(self._android_commands.get_serial()):
1233 if not ChromiumAndroidDriver._loop_with_timeout(self._remove_all_pipes, DRIVER_START_STOP_TIMEOUT_SECS):
1234 self._abort('Failed to remove fifo files. May be locked.')
1236 self._clean_up_cmd_line()
1238 def _pull_crash_dumps_from_device(self):
1240 if not self._android_commands.file_exists(self._driver_details.device_crash_dumps_directory()):
1242 dumps = self._android_commands.run(['shell', 'ls', self._driver_details.device_crash_dumps_directory()])
1243 for dump in dumps.splitlines():
1244 device_dump = '%s/%s' % (self._driver_details.device_crash_dumps_directory(), dump)
1245 local_dump = self._port._filesystem.join(self._port._dump_reader.crash_dumps_directory(), dump)
1247 # FIXME: crbug.com/321489. Figure out why these commands would fail ...
1248 err = self._android_commands.run(['shell', 'chmod', '777', device_dump])
1250 self._android_commands.pull(device_dump, local_dump)
1252 self._android_commands.run(['shell', 'rm', '-f', device_dump])
1254 if self._port._filesystem.exists(local_dump):
1255 result.append(local_dump)
1258 def _clean_up_cmd_line(self):
1259 if not self._created_cmd_line:
1262 cmd_line_file_path = self._driver_details.command_line_file()
1263 original_cmd_line_file_path = cmd_line_file_path + '.orig'
1264 if self._android_commands.file_exists(original_cmd_line_file_path):
1265 self._android_commands.run(['shell', 'mv', original_cmd_line_file_path, cmd_line_file_path])
1266 elif self._android_commands.file_exists(cmd_line_file_path):
1267 self._android_commands.run(['shell', 'rm', cmd_line_file_path])
1268 self._created_cmd_line = False
1270 def _command_from_driver_input(self, driver_input):
1271 command = super(ChromiumAndroidDriver, self)._command_from_driver_input(driver_input)
1272 if command.startswith('/'):
1273 fs = self._port._filesystem
1274 # FIXME: what happens if command lies outside of the layout_tests_dir on the host?
1275 relative_test_filename = fs.relpath(command, fs.dirname(self._port.layout_tests_dir()))
1276 command = DEVICE_WEBKIT_BASE_DIR + relative_test_filename
1279 def _read_prompt(self, deadline):
1282 current_char = self._server_process.read_stdout(deadline, 1)
1283 if current_char == ' ':
1284 if last_char in ('#', '$'):
1286 last_char = current_char