1 # Copyright (C) 2012 Google Inc. All rights reserved. 2 # 3 # Redistribution and use in source and binary forms, with or without 4 # modification, are permitted provided that the following conditions are 5 # met: 6 # 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 12 # distribution. 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. 16 # 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. 28 29 import copy 30 import logging 31 import os 32 import re 33 import signal 34 import sys 35 import subprocess 36 import threading 37 import time 38 39 from multiprocessing.pool import ThreadPool 40 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 50 51 _log = logging.getLogger(__name__) 52 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/' 57 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/' 64 65 SCALING_GOVERNORS_PATTERN = "/sys/devices/system/cpu/cpu*/cpufreq/scaling_governor" 66 KPTR_RESTRICT_PATH = "/proc/sys/kernel/kptr_restrict" 67 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' 74 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' 80 81 MS_TRUETYPE_FONTS_DIR = '/usr/share/fonts/truetype/msttcorefonts/' 82 MS_TRUETYPE_FONTS_PACKAGE = 'ttf-mscorefonts-installer' 83 84 # Timeout in seconds to wait for starting/stopping the driver. 85 DRIVER_START_STOP_TIMEOUT_SECS = 10 86 87 HOST_FONT_FILES = [ 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'], 125 ] 126 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. 138 'media/content', 139 'compositing/resources/video.mp4', 140 ] 141 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 145 146 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/' 151 152 def device_fonts_directory(self): 153 return self.device_directory() + 'fonts/' 154 155 def device_forwarder_path(self): 156 return self.device_directory() + 'forwarder' 157 158 def device_fifo_directory(self): 159 return '/data/data/' + self.package_name() + '/files/' 160 161 def apk_name(self): 162 return 'apks/ContentShell.apk' 163 164 def package_name(self): 165 return 'org.chromium.content_shell_apk' 166 167 def activity_name(self): 168 return self.package_name() + '/.ContentShellActivity' 169 170 def library_name(self): 171 return 'libcontent_shell_content_view.so' 172 173 def additional_resources(self): 174 return ['content_resources.pak', 'shell_resources.pak'] 175 176 def command_line_file(self): 177 return '/data/local/tmp/content-shell-command-line' 178 179 def device_crash_dumps_directory(self): 180 return '/data/local/tmp/content-shell-crash-dumps' 181 182 def additional_command_line_flags(self, use_breakpad): 183 flags = ['--dump-render-tree', '--encode-binary'] 184 if use_breakpad: 185 flags.extend(['--enable-crash-reporter', '--crash-dumps-dir=%s' % self.device_crash_dumps_directory()]) 186 return flags 187 188 def device_directory(self): 189 return DEVICE_SOURCE_ROOT_DIR + 'content_shell/' 190 191 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 = [] 196 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 201 202 # Local public methods. 203 204 def file_exists(self, full_path): 205 assert full_path.startswith('/') 206 return self.run(['shell', 'ls', '-d', full_path]).strip() == full_path 207 208 def push(self, host_path, device_path, ignore_error=False): 209 return self.run(['push', host_path, device_path], ignore_error=ignore_error) 210 211 def pull(self, device_path, host_path, ignore_error=False): 212 return self.run(['pull', device_path, host_path], ignore_error=ignore_error) 213 214 def mkdir(self, device_path, chmod=None): 215 self.run(['shell', 'mkdir', '-p', device_path]) 216 if chmod: 217 self.run(['shell', 'chmod', chmod, device_path]) 218 219 def restart_adb(self): 220 pids = self.extract_pids('adbd') 221 if pids: 222 output = self.run(['shell', 'kill', '-' + str(signal.SIGTERM)] + pids) 223 self.run(['wait-for-device']) 224 225 def restart_as_root(self): 226 output = self.run(['root']) 227 if 'adbd is already running as root' in output: 228 return 229 230 elif not 'restarting adbd as root' in output: 231 self._log_error('Unrecognized output from adb root: %s' % output) 232 233 self.run(['wait-for-device']) 234 235 def extract_pids(self, process_name): 236 pids = [] 237 output = self.run(['shell', 'ps']) 238 for line in output.splitlines(): 239 data = line.split() 240 try: 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 244 else: 245 pids.append(data[1]) 246 except IndexError: 247 pass 248 return pids 249 250 def run(self, command, ignore_error=False): 251 self._log_debug('Run adb command: ' + str(command)) 252 if ignore_error: 253 error_handler = self._executive.ignore_error 254 else: 255 error_handler = None 256 257 result = self._executive.run_command(self.adb_command() + command, error_handler=error_handler, debug_logging=self._debug_logging) 258 259 # We limit the length to avoid outputting too verbose commands, such as "adb logcat". 260 self._log_debug('Run adb result: ' + result[:80]) 261 return result 262 263 def get_serial(self): 264 return self._device_serial 265 266 def adb_command(self): 267 return [AndroidCommands.adb_command_path(self._executive, self._debug_logging), '-s', self._device_serial] 268 269 @staticmethod 270 def set_adb_command_path_options(paths): 271 AndroidCommands._adb_command_path_options = paths 272 273 @staticmethod 274 def adb_command_path(executive, debug_logging): 275 if AndroidCommands._adb_command_path: 276 return AndroidCommands._adb_command_path 277 278 assert AndroidCommands._adb_command_path_options, 'No commands paths have been set to look for the "adb" command.' 279 280 command_path = None 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) 284 if not path_version: 285 continue 286 if command_version != None and path_version < command_version: 287 continue 288 289 command_path = path_option 290 command_version = path_version 291 292 assert command_path, 'Unable to locate the "adb" command. Are you using an Android checkout of Chromium?' 293 294 AndroidCommands._adb_command_path = command_path 295 return command_path 296 297 # Local private methods. 298 299 def _log_error(self, message): 300 _log.error('[%s] %s' % (self._device_serial, message)) 301 302 def _log_info(self, message): 303 _log.info('[%s] %s' % (self._device_serial, message)) 304 305 def _log_debug(self, message): 306 if self._debug_logging: 307 _log.debug('[%s] %s' % (self._device_serial, message)) 308 309 @staticmethod 310 def _determine_adb_version(adb_command_path, executive, debug_logging): 311 re_version = re.compile('^.*version ([\d\.]+)$') 312 try: 313 output = executive.run_command([adb_command_path, 'version'], error_handler=executive.ignore_error, 314 debug_logging=debug_logging) 315 except OSError: 316 return None 317 318 result = re_version.match(output) 319 if not output or not result: 320 return None 321 322 return [int(n) for n in result.group(1).split('.')] 323 324 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 331 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 337 338 def prepared_devices(self): 339 return self._prepared_devices 340 341 def usable_devices(self, executive): 342 if self._usable_devices: 343 return self._usable_devices 344 345 if self._default_device: 346 self._usable_devices = [AndroidCommands(executive, self._default_device, self._debug_logging)] 347 return self._usable_devices 348 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) 353 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) 357 if not devices: 358 return [] 359 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)) 365 continue 366 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()) 369 continue 370 371 self._usable_devices.append(commands) 372 373 return self._usable_devices 374 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.') 379 380 return devices[device_index] 381 382 def is_device_prepared(self, device_serial): 383 return device_serial in self._prepared_devices 384 385 def set_device_prepared(self, device_serial): 386 self._prepared_devices.append(device_serial) 387 388 # Private methods 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()) 393 return 0 394 395 return int(re.findall('level: (\d+)', battery_status)[0]) 396 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 400 401 402 class AndroidPort(base.Port): 403 port_name = 'android' 404 405 # Avoid initializing the adb path [worker count]+1 times by storing it as a static member. 406 _adb_path = None 407 408 SUPPORTED_VERSIONS = ('android') 409 410 FALLBACK_PATHS = {'icecreamsandwich': ['android'] + linux.LinuxPort.latest_platform_fallback_path()} 411 412 # Android has aac and mp3 codecs built in. 413 PORT_HAS_AUDIO_CODECS_BUILT_IN = True 414 415 BUILD_REQUIREMENTS_URL = 'https://code.google.com/p/chromium/wiki/AndroidBuildInstructions' 416 417 def __init__(self, host, port_name, **kwargs): 418 super(AndroidPort, self).__init__(host, port_name, **kwargs) 419 420 self._operating_system = 'android' 421 self._version = 'icecreamsandwich' 422 423 self._host_port = factory.PortFactory(host).get('chromium', **kwargs) 424 self._server_process_constructor = self._android_server_process_constructor 425 426 if not self.get_option('disable_breakpad'): 427 self._dump_reader = DumpReaderAndroid(host, self._build_path()) 428 429 if self.driver_name() != self.CONTENT_SHELL_NAME: 430 raise AssertionError('Layout tests on Android only support content_shell as the driver.') 431 432 self._driver_details = ContentShellDriverDetails() 433 434 # Initialize the AndroidDevices class which tracks available devices. 435 default_device = None 436 if hasattr(self._options, 'adb_device') and len(self._options.adb_device): 437 default_device = self._options.adb_device 438 439 self._debug_logging = self.get_option('android_logging') 440 self._devices = AndroidDevices(self._executive, default_device, self._debug_logging) 441 442 # Tell AndroidCommands where to search for the "adb" command. 443 AndroidCommands.set_adb_command_path_options(['adb', 444 self.path_from_chromium_base('third_party', 'android_tools', 'sdk', 'platform-tools', 'adb')]) 445 446 prepared_devices = self.get_option('prepared_devices', []) 447 for serial in prepared_devices: 448 self._devices.set_device_prepared(serial) 449 450 def default_smoke_test_only(self): 451 return True 452 453 # Local public methods. 454 def path_to_forwarder(self): 455 return self._build_path('forwarder') 456 457 def path_to_md5sum(self): 458 return self._build_path(MD5SUM_DEVICE_FILE_NAME) 459 460 def path_to_md5sum_host(self): 461 return self._build_path(MD5SUM_HOST_FILE_NAME) 462 463 def additional_drt_flag(self): 464 return self._driver_details.additional_command_line_flags(use_breakpad=not self.get_option('disable_breakpad')) 465 466 def default_timeout_ms(self): 467 # Android platform has less computing power than desktop platforms. 468 # Using 10 seconds allows us to pass most slow tests which are not 469 # marked as slow tests on desktop platforms. 470 return 10 * 1000 471 472 def driver_stop_timeout(self): 473 # The driver doesn't respond to closing stdin, so we might as well stop the driver immediately. 474 return 0.0 475 476 def default_child_processes(self): 477 usable_devices = self._devices.usable_devices(self._executive) 478 if not usable_devices: 479 raise test_run_results.TestRunException(test_run_results.NO_DEVICES_EXIT_STATUS, "Unable to find any attached Android devices.") 480 return len(usable_devices) 481 482 def check_wdiff(self, logging=True): 483 return self._host_port.check_wdiff(logging) 484 485 def check_build(self, needs_http, printer): 486 exit_status = super(AndroidPort, self).check_build(needs_http, printer) 487 if exit_status: 488 return exit_status 489 490 result = self._check_file_exists(self.path_to_md5sum(), 'md5sum utility') 491 result = self._check_file_exists(self.path_to_md5sum_host(), 'md5sum host utility') and result 492 result = self._check_file_exists(self.path_to_forwarder(), 'forwarder utility') and result 493 494 if not result: 495 # There is a race condition in adb at least <= 4.3 on Linux that causes it to go offline periodically 496 # We set the processor affinity for any running adb process to attempt to work around this. 497 # See crbug.com/268450 498 if self.host.platform.is_linux(): 499 pids = self._executive.running_pids(lambda name: 'adb' in name) 500 if not pids: 501 # Apparently adb is not running, which is unusual. Running any adb command should start it. 502 self._executive.run_command(['adb', 'devices']) 503 pids = self._executive.running_pids(lambda name: 'adb' in name) 504 if not pids: 505 _log.error("The adb daemon does not appear to be running.") 506 return False 507 508 for pid in pids: 509 self._executive.run_command(['taskset', '-p', '-c', '0', str(pid)]) 510 511 if not result: 512 _log.error('For complete Android build requirements, please see:') 513 _log.error('') 514 _log.error(' http://code.google.com/p/chromium/wiki/AndroidBuildInstructions') 515 return test_run_results.UNEXPECTED_ERROR_EXIT_STATUS 516 517 return self._check_devices(printer) 518 519 def _check_devices(self, printer): 520 # Printer objects aren't threadsafe, so we need to protect calls to them. 521 lock = threading.Lock() 522 pool = None 523 524 # Push the executables and other files to the devices; doing this now 525 # means we can do this in parallel in the manager process and not mix 526 # this in with starting and stopping workers. 527 def setup_device(worker_number): 528 d = self.create_driver(worker_number) 529 serial = d._android_commands.get_serial() 530 531 def log_safely(msg, throttled=True): 532 if throttled: 533 callback = printer.write_throttled_update 534 else: 535 callback = printer.write_update 536 lock.acquire() 537 try: 538 callback("[%s] %s" % (serial, msg)) 539 finally: 540 lock.release() 541 542 log_safely("preparing device", throttled=False) 543 try: 544 d._setup_test(log_safely) 545 log_safely("device prepared", throttled=False) 546 except (ScriptError, driver.DeviceFailure) as e: 547 lock.acquire() 548 _log.warning("[%s] failed to prepare_device: %s" % (serial, str(e))) 549 lock.release() 550 except KeyboardInterrupt: 551 if pool: 552 pool.terminate() 553 554 # FIXME: It would be nice if we knew how many workers we needed. 555 num_workers = self.default_child_processes() 556 num_child_processes = int(self.get_option('child_processes')) 557 if num_child_processes: 558 num_workers = min(num_workers, num_child_processes) 559 if num_workers > 1: 560 pool = ThreadPool(num_workers) 561 try: 562 pool.map(setup_device, range(num_workers)) 563 except KeyboardInterrupt: 564 pool.terminate() 565 raise 566 else: 567 setup_device(0) 568 569 if not self._devices.prepared_devices(): 570 _log.error('Could not prepare any devices for testing.') 571 return test_run_results.NO_DEVICES_EXIT_STATUS 572 return test_run_results.OK_EXIT_STATUS 573 574 def setup_test_run(self): 575 super(AndroidPort, self).setup_test_run() 576 577 # By setting this on the options object, we can propagate the list 578 # of prepared devices to the workers (it is read in __init__()). 579 if self._devices._prepared_devices: 580 self._options.prepared_devices = self._devices.prepared_devices() 581 else: 582 # We were called with --no-build, so assume the devices are up to date. 583 self._options.prepared_devices = [d.get_serial() for d in self._devices.usable_devices(self.host.executive)] 584 585 def num_workers(self, requested_num_workers): 586 return min(len(self._options.prepared_devices), requested_num_workers) 587 588 def check_sys_deps(self, needs_http): 589 for (font_dirs, font_file, package) in HOST_FONT_FILES: 590 exists = False 591 for font_dir in font_dirs: 592 font_path = font_dir + font_file 593 if self._check_file_exists(font_path, '', logging=False): 594 exists = True 595 break 596 if not exists: 597 _log.error('You are missing %s under %s. Try installing %s. See build instructions.' % (font_file, font_dirs, package)) 598 return test_run_results.SYS_DEPS_EXIT_STATUS 599 return test_run_results.OK_EXIT_STATUS 600 601 def requires_http_server(self): 602 """Chromium Android runs tests on devices, and uses the HTTP server to 603 serve the actual layout tests to the test driver.""" 604 return True 605 606 def start_http_server(self, additional_dirs, number_of_drivers): 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_drivers) 610 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. 616 no_timeout=True) 617 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'), []) 621 622 def clobber_old_port_specific_results(self): 623 if not self.get_option('disable_breakpad'): 624 self._dump_reader.clobber_old_results() 625 626 # Overridden protected methods. 627 628 def _build_path(self, *comps): 629 return self._host_port._build_path(*comps) 630 631 def _build_path_with_configuration(self, configuration, *comps): 632 return self._host_port._build_path_with_configuration(configuration, *comps) 633 634 def path_to_apache(self): 635 return self._host_port.path_to_apache() 636 637 def path_to_apache_config_file(self): 638 return self._host_port.path_to_apache_config_file() 639 640 def _path_to_driver(self, configuration=None): 641 return self._build_path_with_configuration(configuration, self._driver_details.apk_name()) 642 643 def _path_to_helper(self): 644 return None 645 646 def _path_to_image_diff(self): 647 return self._host_port._path_to_image_diff() 648 649 def path_to_lighttpd(self): 650 return self._host_port._path_to_lighttpd() 651 652 def path_to_lighttpd_modules(self): 653 return self._host_port._path_to_lighttpd_modules() 654 655 def path_to_lighttpd_php(self): 656 return self._host_port._path_to_lighttpd_php() 657 658 def _path_to_wdiff(self): 659 return self._host_port._path_to_wdiff() 660 661 def _shut_down_http_server(self, pid): 662 return self._host_port._shut_down_http_server(pid) 663 664 def _driver_class(self): 665 return ChromiumAndroidDriver 666 667 # Local private methods. 668 669 @staticmethod 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) 673 674 675 class AndroidPerf(SingleFileOutputProfiler): 676 _cached_perf_host_path = None 677 _have_searched_for_perf_host = False 678 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 685 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() 690 return False 691 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() 695 return False 696 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" 700 return True 701 702 def print_setup_instructions(self): 703 print """ 704 perf on android requires a 'userdebug' build of Android, see: 705 http://source.android.com/source/building-devices.html" 706 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/ 711 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 716 717 Googlers should read: 718 http://goto.google.com/cr-android-perf-howto 719 """ 720 721 def attach_to_pid(self, pid): 722 assert(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) 727 728 def _perf_version_string(self, perf_path): 729 try: 730 return self._host.executive.run_command([perf_path, '--version']) 731 except: 732 return None 733 734 def _find_perfhost_binary(self): 735 perfhost_version = self._perf_version_string('perfhost_linux') 736 if perfhost_version: 737 return 'perfhost_linux' 738 perf_version = self._perf_version_string('perf') 739 if perf_version: 740 return 'perf' 741 return None 742 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 749 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 753 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 758 return 759 760 self._android_commands.pull('/data/perf.data', self._output_path) 761 762 perfhost_path = self._perfhost_path() 763 perfhost_report_command = [ 764 'report', 765 '--input', self._output_path, 766 '--symfs', self._symfs_path, 767 '--kallsyms', self._kallsyms_path, 768 ] 769 if perfhost_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) 774 else: 775 print """ 776 Failed to find perfhost_linux binary, can't process samples from the device. 777 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. 782 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. 787 """ 788 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) 792 793 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 805 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 812 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() 827 sys.exit(1) 828 else: 829 self._profiler = None 830 831 def __del__(self): 832 self._teardown_performance() 833 self._clean_up_cmd_line() 834 super(ChromiumAndroidDriver, self).__del__() 835 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) 839 840 self._android_commands.restart_as_root() 841 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]) 844 845 print "Updating kallsyms file (%s) from device" % kallsyms_cache_path 846 self._android_commands.pull("/proc/kallsyms", kallsyms_cache_path) 847 848 self._android_commands.run(['shell', 'echo', saved_kptr_restrict, '>', KPTR_RESTRICT_PATH]) 849 850 return kallsyms_cache_path 851 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 856 857 if 'ANDROID_SYMFS' in env: 858 symfs_path = env['ANDROID_SYMFS'] 859 else: 860 symfs_path = fs.join(self._port.results_directory(), 'symfs') 861 print "ANDROID_SYMFS not set, using %s" % symfs_path 862 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)) 868 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) 873 874 return symfs_path 875 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') 881 882 self._push_executable(log_callback) 883 self._push_fonts(log_callback) 884 self._push_test_resources(log_callback) 885 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. 890 891 if self._android_devices.is_device_prepared(self._android_commands.get_serial()): 892 return 893 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() 898 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') 902 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') 907 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()]) 910 911 # Mark this device as having been set up. 912 self._android_devices.set_device_prepared(self._android_commands.get_serial()) 913 914 def _log_error(self, message): 915 _log.error('[%s] %s' % (self._android_commands.get_serial(), message)) 916 917 def _log_warning(self, message): 918 _log.warning('[%s] %s' % (self._android_commands.get_serial(), message)) 919 920 def _log_debug(self, message): 921 if self._debug_logging: 922 _log.debug('[%s] %s' % (self._android_commands.get_serial(), message)) 923 924 def _abort(self, message): 925 self._device_failed = True 926 raise driver.DeviceFailure('[%s] %s' % (self._android_commands.get_serial(), message)) 927 928 @staticmethod 929 def _extract_hashes_from_md5sum_output(md5sum_output): 930 assert md5sum_output 931 return [line.split(' ')[0] for line in md5sum_output] 932 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 942 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) 949 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) 954 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) 957 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'): 961 return 962 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)) 970 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) 979 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) 983 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!') 988 return '' 989 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!') 993 return '' 994 995 tombstones = tombstones.rstrip().split('\n') 996 last_tombstone = None 997 for tombstone in tombstones: 998 # Format of fields: 999 # 0 1 2 3 4 5 6 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) 1005 continue 1006 1007 if not last_tombstone or fields[4] + fields[5] >= last_tombstone[4] + last_tombstone[5]: 1008 last_tombstone = fields 1009 else: 1010 break 1011 1012 if not last_tombstone: 1013 self._log_error('The driver crashed, but we could not find any valid tombstone!') 1014 return '' 1015 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]])) 1021 1022 def _get_logcat(self): 1023 return self._android_commands.run(['logcat', '-d', '-v', 'threadtime']) 1024 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]) 1033 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 = {} 1038 1039 def _get_crash_log(self, stdout, stderr, newer_than): 1040 if not stdout: 1041 stdout = '' 1042 stdout += '********* [%s] Logcat:\n%s' % (self._android_commands.get_serial(), self._get_logcat()) 1043 if not stderr: 1044 stderr = '' 1045 stderr += '********* [%s] Tombstone file:\n%s' % (self._android_commands.get_serial(), self._get_last_stacktrace()) 1046 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)) 1051 1052 return super(ChromiumAndroidDriver, self)._get_crash_log(stdout, stderr, newer_than) 1053 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'] 1058 1059 def _android_driver_cmd_line(self, pixel_tests, per_test_args): 1060 return driver.Driver.cmd_line(self, pixel_tests, per_test_args) 1061 1062 @staticmethod 1063 def _loop_with_timeout(condition, timeout_secs): 1064 deadline = time.time() + timeout_secs 1065 while time.time() < deadline: 1066 if condition(): 1067 return True 1068 return False 1069 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)) 1074 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]) 1078 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)) 1082 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) 1087 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: 1093 self.stop() 1094 self._current_android_cmd_line = new_cmd_line 1095 1096 super(ChromiumAndroidDriver, self).start(pixel_tests, per_test_args) 1097 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()) 1101 1102 for retries in range(3): 1103 try: 1104 if self._start_once(pixel_tests, per_test_args): 1105 return 1106 except ScriptError as e: 1107 self._abort('ScriptError("%s") in _start()' % str(e)) 1108 1109 self._log_error('Failed to start the content_shell application. Retries=%d. Log:%s' % (retries, self._get_logcat())) 1110 self.stop() 1111 time.sleep(2) 1112 self._abort('Failed to start the content_shell application multiple times. Giving up.') 1113 1114 def _start_once(self, pixel_tests, per_test_args): 1115 super(ChromiumAndroidDriver, self)._start(pixel_tests, per_test_args, wait_for_ready=False) 1116 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() 1121 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'): 1124 return False 1125 1126 self._android_commands.run(['logcat', '-c']) 1127 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]) 1135 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 1138 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') 1141 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) 1145 return False 1146 1147 if not ChromiumAndroidDriver._loop_with_timeout(self._all_pipes_created, DRIVER_START_STOP_TIMEOUT_SECS): 1148 return False 1149 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') 1155 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() 1161 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() 1167 1168 self._log_debug('Redirecting stdin to ' + self._in_fifo_path) 1169 self._server_process.write('cat >%s\n' % self._in_fifo_path) 1170 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) 1173 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.') 1181 for i in processes: 1182 i.kill() 1183 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() 1188 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'): 1192 return False 1193 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") 1197 return True 1198 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)) 1206 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()) 1211 1212 def stop(self): 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()]) 1217 1218 if self._read_stdout_process: 1219 self._read_stdout_process.kill() 1220 self._read_stdout_process = None 1221 1222 if self._read_stderr_process: 1223 self._read_stderr_process.kill() 1224 self._read_stderr_process = None 1225 1226 super(ChromiumAndroidDriver, self).stop() 1227 1228 if self._forwarder_process: 1229 self._forwarder_process.kill() 1230 self._forwarder_process = None 1231 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.') 1235 1236 self._clean_up_cmd_line() 1237 1238 def _pull_crash_dumps_from_device(self): 1239 result = [] 1240 if not self._android_commands.file_exists(self._driver_details.device_crash_dumps_directory()): 1241 return result 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) 1246 1247 # FIXME: crbug.com/321489. Figure out why these commands would fail ... 1248 err = self._android_commands.run(['shell', 'chmod', '777', device_dump]) 1249 if not err: 1250 self._android_commands.pull(device_dump, local_dump) 1251 if not err: 1252 self._android_commands.run(['shell', 'rm', '-f', device_dump]) 1253 1254 if self._port._filesystem.exists(local_dump): 1255 result.append(local_dump) 1256 return result 1257 1258 def _clean_up_cmd_line(self): 1259 if not self._created_cmd_line: 1260 return 1261 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 1269 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 1277 return command 1278 1279 def _read_prompt(self, deadline): 1280 last_char = '' 1281 while True: 1282 current_char = self._server_process.read_stdout(deadline, 1) 1283 if current_char == ' ': 1284 if last_char in ('#', '$'): 1285 return 1286 last_char = current_char 1287