| OLD | NEW |
| (Empty) |
| 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 sys | |
| 34 import subprocess | |
| 35 import threading | |
| 36 import time | |
| 37 | |
| 38 from webkitpy.layout_tests.port import chromium | |
| 39 from webkitpy.layout_tests.port import linux | |
| 40 from webkitpy.layout_tests.port import driver | |
| 41 from webkitpy.layout_tests.port import factory | |
| 42 from webkitpy.layout_tests.port import server_process | |
| 43 from webkitpy.common.system.profiler import SingleFileOutputProfiler | |
| 44 | |
| 45 _log = logging.getLogger(__name__) | |
| 46 | |
| 47 # The root directory for test resources, which has the same structure as the | |
| 48 # source root directory of Chromium. | |
| 49 # This path is defined in Chromium's base/test/test_support_android.cc. | |
| 50 DEVICE_SOURCE_ROOT_DIR = '/data/local/tmp/' | |
| 51 | |
| 52 # The layout tests directory on device, which has two usages: | |
| 53 # 1. as a virtual path in file urls that will be bridged to HTTP. | |
| 54 # 2. pointing to some files that are pushed to the device for tests that | |
| 55 # don't work on file-over-http (e.g. blob protocol tests). | |
| 56 DEVICE_WEBKIT_BASE_DIR = DEVICE_SOURCE_ROOT_DIR + 'third_party/WebKit/' | |
| 57 DEVICE_LAYOUT_TESTS_DIR = DEVICE_WEBKIT_BASE_DIR + 'LayoutTests/' | |
| 58 | |
| 59 SCALING_GOVERNORS_PATTERN = "/sys/devices/system/cpu/cpu*/cpufreq/scaling_govern
or" | |
| 60 KPTR_RESTRICT_PATH = "/proc/sys/kernel/kptr_restrict" | |
| 61 | |
| 62 # All the test cases are still served to the test runner through file protocol, | |
| 63 # but we use a file-to-http feature to bridge the file request to host's http | |
| 64 # server to get the real test files and corresponding resources. | |
| 65 # See webkit/support/platform_support_android.cc for the other side of this brid
ge. | |
| 66 PERF_TEST_PATH_PREFIX = '/all-perf-tests' | |
| 67 LAYOUT_TEST_PATH_PREFIX = '/all-tests' | |
| 68 | |
| 69 # All ports the Android forwarder to forward. | |
| 70 # 8000, 8080 and 8443 are for http/https tests. | |
| 71 # 8880 and 9323 are for websocket tests | |
| 72 # (see http_server.py, apache_http_server.py and websocket_server.py). | |
| 73 FORWARD_PORTS = '8000 8080 8443 8880 9323' | |
| 74 | |
| 75 MS_TRUETYPE_FONTS_DIR = '/usr/share/fonts/truetype/msttcorefonts/' | |
| 76 MS_TRUETYPE_FONTS_PACKAGE = 'ttf-mscorefonts-installer' | |
| 77 | |
| 78 # Timeout in seconds to wait for starting/stopping the driver. | |
| 79 DRIVER_START_STOP_TIMEOUT_SECS = 10 | |
| 80 | |
| 81 HOST_FONT_FILES = [ | |
| 82 [[MS_TRUETYPE_FONTS_DIR], 'Arial.ttf', MS_TRUETYPE_FONTS_PACKAGE], | |
| 83 [[MS_TRUETYPE_FONTS_DIR], 'Arial_Bold.ttf', MS_TRUETYPE_FONTS_PACKAGE], | |
| 84 [[MS_TRUETYPE_FONTS_DIR], 'Arial_Bold_Italic.ttf', MS_TRUETYPE_FONTS_PACKAGE
], | |
| 85 [[MS_TRUETYPE_FONTS_DIR], 'Arial_Italic.ttf', MS_TRUETYPE_FONTS_PACKAGE], | |
| 86 [[MS_TRUETYPE_FONTS_DIR], 'Comic_Sans_MS.ttf', MS_TRUETYPE_FONTS_PACKAGE], | |
| 87 [[MS_TRUETYPE_FONTS_DIR], 'Comic_Sans_MS_Bold.ttf', MS_TRUETYPE_FONTS_PACKAG
E], | |
| 88 [[MS_TRUETYPE_FONTS_DIR], 'Courier_New.ttf', MS_TRUETYPE_FONTS_PACKAGE], | |
| 89 [[MS_TRUETYPE_FONTS_DIR], 'Courier_New_Bold.ttf', MS_TRUETYPE_FONTS_PACKAGE]
, | |
| 90 [[MS_TRUETYPE_FONTS_DIR], 'Courier_New_Bold_Italic.ttf', MS_TRUETYPE_FONTS_P
ACKAGE], | |
| 91 [[MS_TRUETYPE_FONTS_DIR], 'Courier_New_Italic.ttf', MS_TRUETYPE_FONTS_PACKAG
E], | |
| 92 [[MS_TRUETYPE_FONTS_DIR], 'Georgia.ttf', MS_TRUETYPE_FONTS_PACKAGE], | |
| 93 [[MS_TRUETYPE_FONTS_DIR], 'Georgia_Bold.ttf', MS_TRUETYPE_FONTS_PACKAGE], | |
| 94 [[MS_TRUETYPE_FONTS_DIR], 'Georgia_Bold_Italic.ttf', MS_TRUETYPE_FONTS_PACKA
GE], | |
| 95 [[MS_TRUETYPE_FONTS_DIR], 'Georgia_Italic.ttf', MS_TRUETYPE_FONTS_PACKAGE], | |
| 96 [[MS_TRUETYPE_FONTS_DIR], 'Impact.ttf', MS_TRUETYPE_FONTS_PACKAGE], | |
| 97 [[MS_TRUETYPE_FONTS_DIR], 'Trebuchet_MS.ttf', MS_TRUETYPE_FONTS_PACKAGE], | |
| 98 [[MS_TRUETYPE_FONTS_DIR], 'Trebuchet_MS_Bold.ttf', MS_TRUETYPE_FONTS_PACKAGE
], | |
| 99 [[MS_TRUETYPE_FONTS_DIR], 'Trebuchet_MS_Bold_Italic.ttf', MS_TRUETYPE_FONTS_
PACKAGE], | |
| 100 [[MS_TRUETYPE_FONTS_DIR], 'Trebuchet_MS_Italic.ttf', MS_TRUETYPE_FONTS_PACKA
GE], | |
| 101 [[MS_TRUETYPE_FONTS_DIR], 'Times_New_Roman.ttf', MS_TRUETYPE_FONTS_PACKAGE], | |
| 102 [[MS_TRUETYPE_FONTS_DIR], 'Times_New_Roman_Bold.ttf', MS_TRUETYPE_FONTS_PACK
AGE], | |
| 103 [[MS_TRUETYPE_FONTS_DIR], 'Times_New_Roman_Bold_Italic.ttf', MS_TRUETYPE_FON
TS_PACKAGE], | |
| 104 [[MS_TRUETYPE_FONTS_DIR], 'Times_New_Roman_Italic.ttf', MS_TRUETYPE_FONTS_PA
CKAGE], | |
| 105 [[MS_TRUETYPE_FONTS_DIR], 'Verdana.ttf', MS_TRUETYPE_FONTS_PACKAGE], | |
| 106 [[MS_TRUETYPE_FONTS_DIR], 'Verdana_Bold.ttf', MS_TRUETYPE_FONTS_PACKAGE], | |
| 107 [[MS_TRUETYPE_FONTS_DIR], 'Verdana_Bold_Italic.ttf', MS_TRUETYPE_FONTS_PACKA
GE], | |
| 108 [[MS_TRUETYPE_FONTS_DIR], 'Verdana_Italic.ttf', MS_TRUETYPE_FONTS_PACKAGE], | |
| 109 # The Microsoft font EULA | |
| 110 [['/usr/share/doc/ttf-mscorefonts-installer/'], 'READ_ME!.gz', MS_TRUETYPE_F
ONTS_PACKAGE], | |
| 111 # Other fonts: Arabic, CJK, Indic, Thai, etc. | |
| 112 [['/usr/share/fonts/truetype/ttf-dejavu/'], 'DejaVuSans.ttf', 'ttf-dejavu'], | |
| 113 [['/usr/share/fonts/truetype/kochi/'], 'kochi-mincho.ttf', 'ttf-kochi-mincho
'], | |
| 114 [['/usr/share/fonts/truetype/ttf-indic-fonts-core/'], 'lohit_hi.ttf', 'ttf-i
ndic-fonts-core'], | |
| 115 [['/usr/share/fonts/truetype/ttf-indic-fonts-core/'], 'lohit_ta.ttf', 'ttf-i
ndic-fonts-core'], | |
| 116 [['/usr/share/fonts/truetype/ttf-indic-fonts-core/'], 'MuktiNarrow.ttf', 'tt
f-indic-fonts-core'], | |
| 117 [['/usr/share/fonts/truetype/thai/', '/usr/share/fonts/truetype/tlwg/'], 'Ga
ruda.ttf', 'fonts-tlwg-garuda'], | |
| 118 [['/usr/share/fonts/truetype/ttf-indic-fonts-core/', '/usr/share/fonts/truet
ype/ttf-punjabi-fonts/'], 'lohit_pa.ttf', 'ttf-indic-fonts-core'], | |
| 119 ] | |
| 120 | |
| 121 # Test resources that need to be accessed as files directly. | |
| 122 # Each item can be the relative path of a directory or a file. | |
| 123 TEST_RESOURCES_TO_PUSH = [ | |
| 124 # Blob tests need to access files directly. | |
| 125 'editing/pasteboard/resources', | |
| 126 'fast/files/resources', | |
| 127 'http/tests/local/resources', | |
| 128 'http/tests/local/formdata/resources', | |
| 129 # User style URLs are accessed as local files in webkit_support. | |
| 130 'http/tests/security/resources/cssStyle.css', | |
| 131 # Media tests need to access audio/video as files. | |
| 132 'media/content', | |
| 133 'compositing/resources/video.mp4', | |
| 134 ] | |
| 135 | |
| 136 # List of test resources from webkit platform. These resources will be copied to
the external storage. | |
| 137 WEBKIT_PLATFORM_RESOURCES_TO_PUSH = [ | |
| 138 'third_party/hyphen/hyph_en_US.dic', | |
| 139 ] | |
| 140 | |
| 141 MD5SUM_DEVICE_FILE_NAME = 'md5sum_bin' | |
| 142 MD5SUM_HOST_FILE_NAME = 'md5sum_bin_host' | |
| 143 MD5SUM_DEVICE_PATH = '/data/local/tmp/' + MD5SUM_DEVICE_FILE_NAME | |
| 144 | |
| 145 | |
| 146 # Information required when running layout tests using content_shell as the test
runner. | |
| 147 class ContentShellDriverDetails(): | |
| 148 def device_cache_directory(self): | |
| 149 return self.device_directory() + 'cache/' | |
| 150 | |
| 151 def device_fonts_directory(self): | |
| 152 return self.device_directory() + 'fonts/' | |
| 153 | |
| 154 def device_forwarder_path(self): | |
| 155 return self.device_directory() + 'forwarder' | |
| 156 | |
| 157 def device_fifo_directory(self): | |
| 158 return '/data/data/' + self.package_name() + '/files/' | |
| 159 | |
| 160 def apk_name(self): | |
| 161 return 'apks/ContentShell.apk' | |
| 162 | |
| 163 def package_name(self): | |
| 164 return 'org.chromium.content_shell_apk' | |
| 165 | |
| 166 def activity_name(self): | |
| 167 return self.package_name() + '/.ContentShellActivity' | |
| 168 | |
| 169 def library_name(self): | |
| 170 return 'libcontent_shell_content_view.so' | |
| 171 | |
| 172 def additional_resources(self): | |
| 173 return ['content_resources.pak', 'shell_resources.pak'] | |
| 174 | |
| 175 def command_line_file(self): | |
| 176 return '/data/local/tmp/content-shell-command-line' | |
| 177 | |
| 178 def additional_command_line_flags(self): | |
| 179 return ['--dump-render-tree', '--encode-binary'] | |
| 180 | |
| 181 def device_directory(self): | |
| 182 return DEVICE_SOURCE_ROOT_DIR + 'content_shell/' | |
| 183 | |
| 184 | |
| 185 # The AndroidCommands class encapsulates commands to communicate with an attache
d device. | |
| 186 class AndroidCommands(object): | |
| 187 _adb_command_path = None | |
| 188 _adb_command_path_options = [] | |
| 189 | |
| 190 def __init__(self, executive, device_serial): | |
| 191 self._executive = executive | |
| 192 self._device_serial = device_serial | |
| 193 | |
| 194 # Local public methods. | |
| 195 | |
| 196 def file_exists(self, full_path): | |
| 197 assert full_path.startswith('/') | |
| 198 return self.run(['shell', 'ls', full_path]).strip() == full_path | |
| 199 | |
| 200 def push(self, host_path, device_path, ignore_error=False): | |
| 201 return self.run(['push', host_path, device_path], ignore_error=ignore_er
ror) | |
| 202 | |
| 203 def pull(self, device_path, host_path, ignore_error=False): | |
| 204 return self.run(['pull', device_path, host_path], ignore_error=ignore_er
ror) | |
| 205 | |
| 206 def mkdir(self, device_path, chmod=None): | |
| 207 self.run(['shell', 'mkdir', '-p', device_path]) | |
| 208 if chmod: | |
| 209 self.run(['shell', 'chmod', chmod, device_path]) | |
| 210 | |
| 211 def restart_as_root(self): | |
| 212 output = self.run(['root']) | |
| 213 if 'adbd is already running as root' in output: | |
| 214 return | |
| 215 | |
| 216 elif not 'restarting adbd as root' in output: | |
| 217 self._log_error('Unrecognized output from adb root: %s' % output) | |
| 218 | |
| 219 self.run(['wait-for-device']) | |
| 220 | |
| 221 def run(self, command, ignore_error=False): | |
| 222 self._log_debug('Run adb command: ' + str(command)) | |
| 223 if ignore_error: | |
| 224 error_handler = self._executive.ignore_error | |
| 225 else: | |
| 226 error_handler = None | |
| 227 | |
| 228 result = self._executive.run_command(self.adb_command() + command, | |
| 229 error_handler=error_handler) | |
| 230 | |
| 231 # We limit the length to avoid outputting too verbose commands, such as
"adb logcat". | |
| 232 self._log_debug('Run adb result: ' + result[:80]) | |
| 233 return result | |
| 234 | |
| 235 def get_serial(self): | |
| 236 return self._device_serial | |
| 237 | |
| 238 def adb_command(self): | |
| 239 return [AndroidCommands.adb_command_path(self._executive), '-s', self._d
evice_serial] | |
| 240 | |
| 241 @staticmethod | |
| 242 def set_adb_command_path_options(paths): | |
| 243 AndroidCommands._adb_command_path_options = paths | |
| 244 | |
| 245 @staticmethod | |
| 246 def adb_command_path(executive): | |
| 247 if AndroidCommands._adb_command_path: | |
| 248 return AndroidCommands._adb_command_path | |
| 249 | |
| 250 assert AndroidCommands._adb_command_path_options, 'No commands paths hav
e been set to look for the "adb" command.' | |
| 251 | |
| 252 command_path = None | |
| 253 command_version = None | |
| 254 for path_option in AndroidCommands._adb_command_path_options: | |
| 255 path_version = AndroidCommands._determine_adb_version(path_option, e
xecutive) | |
| 256 if not path_version: | |
| 257 continue | |
| 258 if command_version != None and path_version < command_version: | |
| 259 continue | |
| 260 | |
| 261 command_path = path_option | |
| 262 command_version = path_version | |
| 263 | |
| 264 assert command_path, 'Unable to locate the "adb" command. Are you using
an Android checkout of Chromium?' | |
| 265 | |
| 266 AndroidCommands._adb_command_path = command_path | |
| 267 return command_path | |
| 268 | |
| 269 # Local private methods. | |
| 270 | |
| 271 def _log_error(self, message): | |
| 272 _log.error('[%s] %s' % (self._device_serial, message)) | |
| 273 | |
| 274 def _log_debug(self, message): | |
| 275 _log.debug('[%s] %s' % (self._device_serial, message)) | |
| 276 | |
| 277 @staticmethod | |
| 278 def _determine_adb_version(adb_command_path, executive): | |
| 279 re_version = re.compile('^.*version ([\d\.]+)$') | |
| 280 try: | |
| 281 output = executive.run_command([adb_command_path, 'version'], error_
handler=executive.ignore_error) | |
| 282 except OSError: | |
| 283 return None | |
| 284 | |
| 285 result = re_version.match(output) | |
| 286 if not output or not result: | |
| 287 return None | |
| 288 | |
| 289 return [int(n) for n in result.group(1).split('.')] | |
| 290 | |
| 291 | |
| 292 # A class to encapsulate device status and information, such as the AndroidComma
nds | |
| 293 # instances and whether the device has been set up. | |
| 294 class AndroidDevices(object): | |
| 295 # Percentage of battery a device needs to have in order for it to be conside
red | |
| 296 # to participate in running the layout tests. | |
| 297 MINIMUM_BATTERY_PERCENTAGE = 30 | |
| 298 | |
| 299 def __init__(self, executive, default_device=None): | |
| 300 self._usable_devices = [] | |
| 301 self._default_device = default_device | |
| 302 self._prepared_devices = [] | |
| 303 | |
| 304 def usable_devices(self, executive): | |
| 305 if self._usable_devices: | |
| 306 return self._usable_devices | |
| 307 | |
| 308 if self._default_device: | |
| 309 self._usable_devices = [AndroidCommands(executive, self._default_dev
ice)] | |
| 310 return self._usable_devices | |
| 311 | |
| 312 # Example "adb devices" command output: | |
| 313 # List of devices attached | |
| 314 # 0123456789ABCDEF device | |
| 315 re_device = re.compile('^([a-zA-Z0-9_:.-]+)\tdevice$', re.MULTILINE) | |
| 316 | |
| 317 result = executive.run_command([AndroidCommands.adb_command_path(executi
ve), 'devices'], | |
| 318 error_handler=executive.ignore_error) | |
| 319 devices = re_device.findall(result) | |
| 320 if not devices: | |
| 321 raise AssertionError('Unable to find attached Android devices. ADB o
utput: %s' % result) | |
| 322 | |
| 323 for device_serial in devices: | |
| 324 commands = AndroidCommands(executive, device_serial) | |
| 325 if self._battery_level_for_device(commands) < AndroidDevices.MINIMUM
_BATTERY_PERCENTAGE: | |
| 326 continue | |
| 327 | |
| 328 self._usable_devices.append(commands) | |
| 329 | |
| 330 if not self._usable_devices: | |
| 331 raise AssertionError('No devices attached with more than %d percent
battery.' % | |
| 332 AndroidDevices.MINIMUM_BATTERY_PERCENTAGE) | |
| 333 | |
| 334 return self._usable_devices | |
| 335 | |
| 336 def get_device(self, executive, device_index): | |
| 337 devices = self.usable_devices(executive) | |
| 338 if device_index >= len(devices): | |
| 339 raise AssertionError('Device index exceeds number of usable devices.
') | |
| 340 | |
| 341 return devices[device_index] | |
| 342 | |
| 343 def is_device_prepared(self, device_serial): | |
| 344 return device_serial in self._prepared_devices | |
| 345 | |
| 346 def set_device_prepared(self, device_serial): | |
| 347 self._prepared_devices.append(device_serial) | |
| 348 | |
| 349 # Private methods | |
| 350 def _battery_level_for_device(self, commands): | |
| 351 battery_status = commands.run(['shell', 'dumpsys', 'battery']) | |
| 352 if 'Error' in battery_status: | |
| 353 _log.warning('Unable to read the battery level from device with seri
al "%s".' % commands.get_serial()) | |
| 354 return 100 | |
| 355 | |
| 356 return int(re.findall('level: (\d+)', battery_status)[0]) | |
| 357 | |
| 358 | |
| 359 class ChromiumAndroidPort(chromium.ChromiumPort): | |
| 360 port_name = 'chromium-android' | |
| 361 | |
| 362 # Avoid initializing the adb path [worker count]+1 times by storing it as a
static member. | |
| 363 _adb_path = None | |
| 364 | |
| 365 SUPPORTED_VERSIONS = ('android') | |
| 366 | |
| 367 FALLBACK_PATHS = { 'android': [ 'chromium-android' ] + linux.LinuxPort.lates
t_platform_fallback_path() } | |
| 368 | |
| 369 def __init__(self, host, port_name, **kwargs): | |
| 370 super(ChromiumAndroidPort, self).__init__(host, port_name, **kwargs) | |
| 371 | |
| 372 self._operating_system = 'android' | |
| 373 self._version = 'icecreamsandwich' | |
| 374 | |
| 375 self._host_port = factory.PortFactory(host).get('chromium', **kwargs) | |
| 376 self._server_process_constructor = self._android_server_process_construc
tor | |
| 377 | |
| 378 if self.driver_name() != self.CONTENT_SHELL_NAME: | |
| 379 raise AssertionError('Layout tests on Android only support content_s
hell as the driver.') | |
| 380 | |
| 381 self._driver_details = ContentShellDriverDetails() | |
| 382 | |
| 383 # Initialize the AndroidDevices class which tracks available devices. | |
| 384 default_device = None | |
| 385 if hasattr(self._options, 'adb_device') and len(self._options.adb_device
): | |
| 386 default_device = self._options.adb_device | |
| 387 | |
| 388 self._devices = AndroidDevices(self._executive, default_device) | |
| 389 | |
| 390 # Tell AndroidCommands where to search for the "adb" command. | |
| 391 AndroidCommands.set_adb_command_path_options(['adb', | |
| 392 self.path_from_chromium_base('third_party', 'android_tools', 'sdk',
'platform-tools', 'adb')]) | |
| 393 | |
| 394 # Local public methods. | |
| 395 def path_to_forwarder(self): | |
| 396 return self._build_path('forwarder') | |
| 397 | |
| 398 def path_to_md5sum(self): | |
| 399 return self._build_path(MD5SUM_DEVICE_FILE_NAME) | |
| 400 | |
| 401 def path_to_md5sum_host(self): | |
| 402 return self._build_path(MD5SUM_HOST_FILE_NAME) | |
| 403 | |
| 404 # Overridden public methods. | |
| 405 def buildbot_archives_baselines(self): | |
| 406 return False | |
| 407 | |
| 408 def additional_drt_flag(self): | |
| 409 return self._driver_details.additional_command_line_flags() | |
| 410 | |
| 411 def default_timeout_ms(self): | |
| 412 # Android platform has less computing power than desktop platforms. | |
| 413 # Using 10 seconds allows us to pass most slow tests which are not | |
| 414 # marked as slow tests on desktop platforms. | |
| 415 return 10 * 1000 | |
| 416 | |
| 417 def driver_stop_timeout(self): | |
| 418 # The driver doesn't respond to closing stdin, so we might as well stop
the driver immediately. | |
| 419 return 0.0 | |
| 420 | |
| 421 def default_child_processes(self): | |
| 422 usable_device_count = len(self._devices.usable_devices(self._executive)) | |
| 423 if not usable_device_count: | |
| 424 raise AssertionError('There are no devices available to run the layo
ut tests on.') | |
| 425 | |
| 426 return usable_device_count | |
| 427 | |
| 428 def default_baseline_search_path(self): | |
| 429 return map(self._webkit_baseline_path, self.FALLBACK_PATHS['android']) | |
| 430 | |
| 431 def check_wdiff(self, logging=True): | |
| 432 return self._host_port.check_wdiff(logging) | |
| 433 | |
| 434 def check_build(self, needs_http): | |
| 435 result = super(ChromiumAndroidPort, self).check_build(needs_http) | |
| 436 result = self._check_file_exists(self.path_to_md5sum(), 'md5sum utility'
) and result | |
| 437 result = self._check_file_exists(self.path_to_md5sum_host(), 'md5sum hos
t utility') and result | |
| 438 result = self._check_file_exists(self.path_to_forwarder(), 'forwarder ut
ility') and result | |
| 439 if not result: | |
| 440 _log.error('For complete Android build requirements, please see:') | |
| 441 _log.error('') | |
| 442 _log.error(' http://code.google.com/p/chromium/wiki/AndroidBuildI
nstructions') | |
| 443 | |
| 444 return result | |
| 445 | |
| 446 def check_sys_deps(self, needs_http): | |
| 447 for (font_dirs, font_file, package) in HOST_FONT_FILES: | |
| 448 exists = False | |
| 449 for font_dir in font_dirs: | |
| 450 font_path = font_dir + font_file | |
| 451 if self._check_file_exists(font_path, '', logging=False): | |
| 452 exists = True | |
| 453 break | |
| 454 if not exists: | |
| 455 _log.error('You are missing %s under %s. Try installing %s. See
build instructions.' % (font_file, font_dirs, package)) | |
| 456 return False | |
| 457 return True | |
| 458 | |
| 459 def requires_http_server(self): | |
| 460 """Chromium Android runs tests on devices, and uses the HTTP server to | |
| 461 serve the actual layout tests to the test driver.""" | |
| 462 return True | |
| 463 | |
| 464 def start_http_server(self, additional_dirs=None, number_of_servers=0): | |
| 465 if not additional_dirs: | |
| 466 additional_dirs = {} | |
| 467 additional_dirs[PERF_TEST_PATH_PREFIX] = self.perf_tests_dir() | |
| 468 additional_dirs[LAYOUT_TEST_PATH_PREFIX] = self.layout_tests_dir() | |
| 469 super(ChromiumAndroidPort, self).start_http_server(additional_dirs, numb
er_of_servers) | |
| 470 | |
| 471 def create_driver(self, worker_number, no_timeout=False): | |
| 472 return ChromiumAndroidDriver(self, worker_number, pixel_tests=self.get_o
ption('pixel_tests'), | |
| 473 driver_details=self._driver_details, | |
| 474 android_devices=self._devices, | |
| 475 # Force no timeout to avoid test driver tim
eouts before NRWT. | |
| 476 no_timeout=True) | |
| 477 | |
| 478 def driver_cmd_line(self): | |
| 479 # Override to return the actual test driver's command line. | |
| 480 return self.create_driver(0)._android_driver_cmd_line(self.get_option('p
ixel_tests'), []) | |
| 481 | |
| 482 # Overridden protected methods. | |
| 483 | |
| 484 def _build_path(self, *comps): | |
| 485 return self._host_port._build_path(*comps) | |
| 486 | |
| 487 def _build_path_with_configuration(self, configuration, *comps): | |
| 488 return self._host_port._build_path_with_configuration(configuration, *co
mps) | |
| 489 | |
| 490 def _path_to_apache(self): | |
| 491 return self._host_port._path_to_apache() | |
| 492 | |
| 493 def _path_to_apache_config_file(self): | |
| 494 return self._host_port._path_to_apache_config_file() | |
| 495 | |
| 496 def _path_to_driver(self, configuration=None): | |
| 497 return self._build_path_with_configuration(configuration, self._driver_d
etails.apk_name()) | |
| 498 | |
| 499 def _path_to_helper(self): | |
| 500 return None | |
| 501 | |
| 502 def _path_to_image_diff(self): | |
| 503 return self._host_port._path_to_image_diff() | |
| 504 | |
| 505 def _path_to_lighttpd(self): | |
| 506 return self._host_port._path_to_lighttpd() | |
| 507 | |
| 508 def _path_to_lighttpd_modules(self): | |
| 509 return self._host_port._path_to_lighttpd_modules() | |
| 510 | |
| 511 def _path_to_lighttpd_php(self): | |
| 512 return self._host_port._path_to_lighttpd_php() | |
| 513 | |
| 514 def _path_to_wdiff(self): | |
| 515 return self._host_port._path_to_wdiff() | |
| 516 | |
| 517 def _shut_down_http_server(self, pid): | |
| 518 return self._host_port._shut_down_http_server(pid) | |
| 519 | |
| 520 def _driver_class(self): | |
| 521 return ChromiumAndroidDriver | |
| 522 | |
| 523 # Local private methods. | |
| 524 | |
| 525 @staticmethod | |
| 526 def _android_server_process_constructor(port, server_name, cmd_line, env=Non
e): | |
| 527 return server_process.ServerProcess(port, server_name, cmd_line, env, | |
| 528 universal_newlines=True, treat_no_da
ta_as_crash=True) | |
| 529 | |
| 530 | |
| 531 class AndroidPerf(SingleFileOutputProfiler): | |
| 532 _cached_perf_host_path = None | |
| 533 _have_searched_for_perf_host = False | |
| 534 | |
| 535 def __init__(self, host, executable_path, output_dir, android_commands, symf
s_path, kallsyms_path, identifier=None): | |
| 536 super(AndroidPerf, self).__init__(host, executable_path, output_dir, "da
ta", identifier) | |
| 537 self._android_commands = android_commands | |
| 538 self._perf_process = None | |
| 539 self._symfs_path = symfs_path | |
| 540 self._kallsyms_path = kallsyms_path | |
| 541 | |
| 542 def check_configuration(self): | |
| 543 # Check that perf is installed | |
| 544 if not self._android_commands.file_exists('/system/bin/perf'): | |
| 545 print "Cannot find /system/bin/perf on device %s" % self._android_co
mmands.get_serial() | |
| 546 return False | |
| 547 | |
| 548 # Check that the device is a userdebug build (or at least has the necess
ary libraries). | |
| 549 if self._android_commands.run(['shell', 'getprop', 'ro.build.type']).str
ip() != 'userdebug': | |
| 550 print "Device %s is not flashed with a userdebug build of Android" %
self._android_commands.get_serial() | |
| 551 return False | |
| 552 | |
| 553 # FIXME: Check that the binary actually is perf-able (has stackframe poi
nters)? | |
| 554 # objdump -s a function and make sure it modifies the fp? | |
| 555 # Instruct users to rebuild after export GYP_DEFINES="profiling=1 $GYP_D
EFINES" | |
| 556 return True | |
| 557 | |
| 558 def print_setup_instructions(self): | |
| 559 print """ | |
| 560 perf on android requires a 'userdebug' build of Android, see: | |
| 561 http://source.android.com/source/building-devices.html" | |
| 562 | |
| 563 The perf command can be built from: | |
| 564 https://android.googlesource.com/platform/external/linux-tools-perf/ | |
| 565 and requires libefl, libebl, libdw, and libdwfl available in: | |
| 566 https://android.googlesource.com/platform/external/elfutils/ | |
| 567 | |
| 568 The test driver must be built with profiling=1, make sure you've done: | |
| 569 export GYP_DEFINES="profiling=1 $GYP_DEFINES" | |
| 570 update-webkit --chromium-android | |
| 571 build-webkit --chromium-android | |
| 572 | |
| 573 Googlers should read: | |
| 574 http://goto.google.com/cr-android-perf-howto | |
| 575 """ | |
| 576 | |
| 577 def attach_to_pid(self, pid): | |
| 578 assert(pid) | |
| 579 assert(self._perf_process == None) | |
| 580 # FIXME: This can't be a fixed timeout! | |
| 581 cmd = self._android_commands.adb_command() + ['shell', 'perf', 'record',
'-g', '-p', pid, 'sleep', 30] | |
| 582 self._perf_process = self._host.executive.popen(cmd) | |
| 583 | |
| 584 def _perf_version_string(self, perf_path): | |
| 585 try: | |
| 586 return self._host.executive.run_command([perf_path, '--version']) | |
| 587 except: | |
| 588 return None | |
| 589 | |
| 590 def _find_perfhost_binary(self): | |
| 591 perfhost_version = self._perf_version_string('perfhost_linux') | |
| 592 if perfhost_version: | |
| 593 return 'perfhost_linux' | |
| 594 perf_version = self._perf_version_string('perf') | |
| 595 if perf_version: | |
| 596 return 'perf' | |
| 597 return None | |
| 598 | |
| 599 def _perfhost_path(self): | |
| 600 if self._have_searched_for_perf_host: | |
| 601 return self._cached_perf_host_path | |
| 602 self._have_searched_for_perf_host = True | |
| 603 self._cached_perf_host_path = self._find_perfhost_binary() | |
| 604 return self._cached_perf_host_path | |
| 605 | |
| 606 def _first_ten_lines_of_profile(self, perf_output): | |
| 607 match = re.search("^#[^\n]*\n((?: [^\n]*\n){1,10})", perf_output, re.MUL
TILINE) | |
| 608 return match.group(1) if match else None | |
| 609 | |
| 610 def profile_after_exit(self): | |
| 611 perf_exitcode = self._perf_process.wait() | |
| 612 if perf_exitcode != 0: | |
| 613 print "Perf failed (exit code: %i), can't process results." % perf_e
xitcode | |
| 614 return | |
| 615 | |
| 616 self._android_commands.pull('/data/perf.data', self._output_path) | |
| 617 | |
| 618 perfhost_path = self._perfhost_path() | |
| 619 perfhost_report_command = [ | |
| 620 'report', | |
| 621 '--input', self._output_path, | |
| 622 '--symfs', self._symfs_path, | |
| 623 '--kallsyms', self._kallsyms_path, | |
| 624 ] | |
| 625 if perfhost_path: | |
| 626 perfhost_args = [perfhost_path] + perfhost_report_command + ['--call
-graph', 'none'] | |
| 627 perf_output = self._host.executive.run_command(perfhost_args) | |
| 628 # We could save off the full -g report to a file if users found that
useful. | |
| 629 print self._first_ten_lines_of_profile(perf_output) | |
| 630 else: | |
| 631 print """ | |
| 632 Failed to find perfhost_linux binary, can't process samples from the device. | |
| 633 | |
| 634 perfhost_linux can be built from: | |
| 635 https://android.googlesource.com/platform/external/linux-tools-perf/ | |
| 636 also, modern versions of perf (available from apt-get install goobuntu-kernel-to
ols-common) | |
| 637 may also be able to process the perf.data files from the device. | |
| 638 | |
| 639 Googlers should read: | |
| 640 http://goto.google.com/cr-android-perf-howto | |
| 641 for instructions on installing pre-built copies of perfhost_linux | |
| 642 http://crbug.com/165250 discusses making these pre-built binaries externally ava
ilable. | |
| 643 """ | |
| 644 | |
| 645 perfhost_display_patch = perfhost_path if perfhost_path else 'perfhost_l
inux' | |
| 646 print "To view the full profile, run:" | |
| 647 print ' '.join([perfhost_display_patch] + perfhost_report_command) | |
| 648 | |
| 649 | |
| 650 class ChromiumAndroidDriver(driver.Driver): | |
| 651 def __init__(self, port, worker_number, pixel_tests, driver_details, android
_devices, no_timeout=False): | |
| 652 super(ChromiumAndroidDriver, self).__init__(port, worker_number, pixel_t
ests, no_timeout) | |
| 653 self._in_fifo_path = driver_details.device_fifo_directory() + 'stdin.fif
o' | |
| 654 self._out_fifo_path = driver_details.device_fifo_directory() + 'test.fif
o' | |
| 655 self._err_fifo_path = driver_details.device_fifo_directory() + 'stderr.f
ifo' | |
| 656 self._read_stdout_process = None | |
| 657 self._read_stderr_process = None | |
| 658 self._forwarder_process = None | |
| 659 self._original_governors = {} | |
| 660 self._original_kptr_restrict = None | |
| 661 | |
| 662 self._android_devices = android_devices | |
| 663 self._android_commands = android_devices.get_device(port._executive, wor
ker_number) | |
| 664 self._driver_details = driver_details | |
| 665 | |
| 666 # FIXME: If we taught ProfileFactory about "target" devices we could | |
| 667 # just use the logic in Driver instead of duplicating it here. | |
| 668 if self._port.get_option("profile"): | |
| 669 # FIXME: This should be done once, instead of per-driver! | |
| 670 symfs_path = self._find_or_create_symfs() | |
| 671 kallsyms_path = self._update_kallsyms_cache(symfs_path) | |
| 672 # FIXME: We should pass this some sort of "Bridge" object abstractio
n around ADB instead of a path/device pair. | |
| 673 self._profiler = AndroidPerf(self._port.host, self._port._path_to_dr
iver(), self._port.results_directory(), | |
| 674 self._android_commands, symfs_path, kallsyms_path) | |
| 675 # FIXME: This is a layering violation and should be moved to Port.ch
eck_sys_deps | |
| 676 # once we have an abstraction around an adb_path/device_serial pair
to make it | |
| 677 # easy to make these class methods on AndroidPerf. | |
| 678 if not self._profiler.check_configuration(): | |
| 679 self._profiler.print_setup_instructions() | |
| 680 sys.exit(1) | |
| 681 else: | |
| 682 self._profiler = None | |
| 683 | |
| 684 def __del__(self): | |
| 685 self._teardown_performance() | |
| 686 super(ChromiumAndroidDriver, self).__del__() | |
| 687 | |
| 688 def _update_kallsyms_cache(self, output_dir): | |
| 689 kallsyms_name = "%s-kallsyms" % self._android_commands.get_serial() | |
| 690 kallsyms_cache_path = self._port.host.filesystem.join(output_dir, kallsy
ms_name) | |
| 691 | |
| 692 self._android_commands.restart_as_root() | |
| 693 | |
| 694 saved_kptr_restrict = self._android_commands.run(['shell', 'cat', KPTR_R
ESTRICT_PATH]).strip() | |
| 695 self._android_commands.run(['shell', 'echo', '0', '>', KPTR_RESTRICT_PAT
H]) | |
| 696 | |
| 697 print "Updating kallsyms file (%s) from device" % kallsyms_cache_path | |
| 698 self._android_commands.pull("/proc/kallsyms", kallsyms_cache_path) | |
| 699 | |
| 700 self._android_commands.run(['shell', 'echo', saved_kptr_restrict, '>', K
PTR_RESTRICT_PATH]) | |
| 701 | |
| 702 return kallsyms_cache_path | |
| 703 | |
| 704 def _find_or_create_symfs(self): | |
| 705 environment = self._port.host.copy_current_environment() | |
| 706 env = environment.to_dictionary() | |
| 707 fs = self._port.host.filesystem | |
| 708 | |
| 709 if 'ANDROID_SYMFS' in env: | |
| 710 symfs_path = env['ANDROID_SYMFS'] | |
| 711 else: | |
| 712 symfs_path = fs.join(self._port.results_directory(), 'symfs') | |
| 713 print "ANDROID_SYMFS not set, using %s" % symfs_path | |
| 714 | |
| 715 # find the installed path, and the path of the symboled built library | |
| 716 # FIXME: We should get the install path from the device! | |
| 717 symfs_library_path = fs.join(symfs_path, "data/app-lib/%s-1/%s" % (self.
_driver_details.package_name(), self._driver_details.library_name())) | |
| 718 built_library_path = self._port._build_path('lib', self._driver_details.
library_name()) | |
| 719 assert(fs.exists(built_library_path)) | |
| 720 | |
| 721 # FIXME: Ideally we'd check the sha1's first and make a soft-link instea
d of copying (since we probably never care about windows). | |
| 722 print "Updating symfs libary (%s) from built copy (%s)" % (symfs_library
_path, built_library_path) | |
| 723 fs.maybe_make_directory(fs.dirname(symfs_library_path)) | |
| 724 fs.copyfile(built_library_path, symfs_library_path) | |
| 725 | |
| 726 return symfs_path | |
| 727 | |
| 728 def _setup_md5sum_and_push_data_if_needed(self): | |
| 729 self._md5sum_path = self._port.path_to_md5sum() | |
| 730 if not self._android_commands.file_exists(MD5SUM_DEVICE_PATH): | |
| 731 if not self._android_commands.push(self._md5sum_path, MD5SUM_DEVICE_
PATH): | |
| 732 raise AssertionError('Could not push md5sum to device') | |
| 733 | |
| 734 self._push_executable() | |
| 735 self._push_fonts() | |
| 736 self._push_test_resources() | |
| 737 self._push_platform_resources() | |
| 738 | |
| 739 def _setup_test(self): | |
| 740 if self._android_devices.is_device_prepared(self._android_commands.get_s
erial()): | |
| 741 return | |
| 742 | |
| 743 self._android_commands.restart_as_root() | |
| 744 self._setup_md5sum_and_push_data_if_needed() | |
| 745 self._setup_performance() | |
| 746 | |
| 747 # Required by webkit_support::GetWebKitRootDirFilePath(). | |
| 748 # Other directories will be created automatically by adb push. | |
| 749 self._android_commands.mkdir(DEVICE_SOURCE_ROOT_DIR + 'chrome') | |
| 750 | |
| 751 # Allow the test driver to get full read and write access to the directo
ry on the device, | |
| 752 # as well as for the FIFOs. We'll need a world writable directory. | |
| 753 self._android_commands.mkdir(self._driver_details.device_directory(), ch
mod='777') | |
| 754 self._android_commands.mkdir(self._driver_details.device_fifo_directory(
), chmod='777') | |
| 755 | |
| 756 # Make sure that the disk cache on the device resets to a clean state. | |
| 757 self._android_commands.run(['shell', 'rm', '-r', self._driver_details.de
vice_cache_directory()]) | |
| 758 | |
| 759 # Mark this device as having been set up. | |
| 760 self._android_devices.set_device_prepared(self._android_commands.get_ser
ial()) | |
| 761 | |
| 762 def _log_error(self, message): | |
| 763 _log.error('[%s] %s' % (self._android_commands.get_serial(), message)) | |
| 764 | |
| 765 def _log_debug(self, message): | |
| 766 _log.debug('[%s] %s' % (self._android_commands.get_serial(), message)) | |
| 767 | |
| 768 def _abort(self, message): | |
| 769 raise AssertionError('[%s] %s' % (self._android_commands.get_serial(), m
essage)) | |
| 770 | |
| 771 @staticmethod | |
| 772 def _extract_hashes_from_md5sum_output(md5sum_output): | |
| 773 assert md5sum_output | |
| 774 return [line.split(' ')[0] for line in md5sum_output] | |
| 775 | |
| 776 def _push_file_if_needed(self, host_file, device_file): | |
| 777 assert os.path.exists(host_file) | |
| 778 device_hashes = self._extract_hashes_from_md5sum_output( | |
| 779 self._port.host.executive.popen(self._android_commands.adb_comma
nd() + ['shell', MD5SUM_DEVICE_PATH, device_file], | |
| 780 stdout=subprocess.PIPE).stdout) | |
| 781 host_hashes = self._extract_hashes_from_md5sum_output( | |
| 782 self._port.host.executive.popen(args=['%s_host' % self._md5sum_p
ath, host_file], | |
| 783 stdout=subprocess.PIPE).stdout) | |
| 784 if host_hashes and device_hashes == host_hashes: | |
| 785 return | |
| 786 | |
| 787 self._android_commands.push(host_file, device_file) | |
| 788 | |
| 789 def _push_executable(self): | |
| 790 self._push_file_if_needed(self._port.path_to_forwarder(), self._driver_d
etails.device_forwarder_path()) | |
| 791 for resource in self._driver_details.additional_resources(): | |
| 792 self._push_file_if_needed(self._port._build_path(resource), self._dr
iver_details.device_directory() + resource) | |
| 793 | |
| 794 self._push_file_if_needed(self._port._build_path('android_main_fonts.xml
'), self._driver_details.device_directory() + 'android_main_fonts.xml') | |
| 795 self._push_file_if_needed(self._port._build_path('android_fallback_fonts
.xml'), self._driver_details.device_directory() + 'android_fallback_fonts.xml') | |
| 796 | |
| 797 self._android_commands.run(['uninstall', self._driver_details.package_na
me()]) | |
| 798 driver_host_path = self._port._path_to_driver() | |
| 799 install_result = self._android_commands.run(['install', driver_host_path
]) | |
| 800 if install_result.find('Success') == -1: | |
| 801 self._abort('Failed to install %s onto device: %s' % (driver_host_pa
th, install_result)) | |
| 802 | |
| 803 def _push_fonts(self): | |
| 804 self._log_debug('Pushing fonts') | |
| 805 path_to_ahem_font = self._port._build_path('AHEM____.TTF') | |
| 806 self._push_file_if_needed(path_to_ahem_font, self._driver_details.device
_fonts_directory() + 'AHEM____.TTF') | |
| 807 for (host_dirs, font_file, package) in HOST_FONT_FILES: | |
| 808 for host_dir in host_dirs: | |
| 809 host_font_path = host_dir + font_file | |
| 810 if self._port._check_file_exists(host_font_path, '', logging=Fal
se): | |
| 811 self._push_file_if_needed(host_font_path, self._driver_detai
ls.device_fonts_directory() + font_file) | |
| 812 | |
| 813 def _push_test_resources(self): | |
| 814 self._log_debug('Pushing test resources') | |
| 815 for resource in TEST_RESOURCES_TO_PUSH: | |
| 816 self._push_file_if_needed(self._port.layout_tests_dir() + '/' + reso
urce, DEVICE_LAYOUT_TESTS_DIR + resource) | |
| 817 | |
| 818 def _push_platform_resources(self): | |
| 819 self._log_debug('Pushing platform resources') | |
| 820 external_storage = self._port._filesystem.join(self._android_commands.ru
n(['shell', 'echo $EXTERNAL_STORAGE']).strip(), 'Source', 'WebKit', 'chromium') | |
| 821 for resource in WEBKIT_PLATFORM_RESOURCES_TO_PUSH: | |
| 822 self._push_file_if_needed(self._port._chromium_base_dir(self._port._
filesystem) + '/' + resource, external_storage + '/' + resource) | |
| 823 | |
| 824 def _get_last_stacktrace(self): | |
| 825 tombstones = self._android_commands.run(['shell', 'ls', '-n', '/data/tom
bstones']) | |
| 826 if not tombstones or tombstones.startswith('/data/tombstones: No such fi
le or directory'): | |
| 827 self._log_error('The driver crashed, but no tombstone found!') | |
| 828 return '' | |
| 829 tombstones = tombstones.rstrip().split('\n') | |
| 830 last_tombstone = tombstones[0].split() | |
| 831 for tombstone in tombstones[1:]: | |
| 832 # Format of fields: | |
| 833 # 0 1 2 3 4 5 6 | |
| 834 # permission uid gid size date time filename | |
| 835 # -rw------- 1000 1000 45859 2011-04-13 06:00 tombstone_00 | |
| 836 fields = tombstone.split() | |
| 837 if (fields[4] + fields[5] >= last_tombstone[4] + last_tombstone[5]): | |
| 838 last_tombstone = fields | |
| 839 else: | |
| 840 break | |
| 841 | |
| 842 # Use Android tool vendor/google/tools/stack to convert the raw | |
| 843 # stack trace into a human readable format, if needed. | |
| 844 # It takes a long time, so don't do it here. | |
| 845 return '%s\n%s' % (' '.join(last_tombstone), | |
| 846 self._android_commands.run(['shell', 'cat', '/data/to
mbstones/' + last_tombstone[6]])) | |
| 847 | |
| 848 def _get_logcat(self): | |
| 849 return self._android_commands.run(['logcat', '-d', '-v', 'threadtime']) | |
| 850 | |
| 851 def _setup_performance(self): | |
| 852 # Disable CPU scaling and drop ram cache to reduce noise in tests | |
| 853 if not self._original_governors: | |
| 854 governor_files = self._android_commands.run(['shell', 'ls', SCALING_
GOVERNORS_PATTERN]) | |
| 855 if governor_files.find('No such file or directory') == -1: | |
| 856 for file in governor_files.split(): | |
| 857 self._original_governors[file] = self._android_commands.run(
['shell', 'cat', file]).strip() | |
| 858 self._android_commands.run(['shell', 'echo', 'performance',
'>', file]) | |
| 859 | |
| 860 def _teardown_performance(self): | |
| 861 for file, original_content in self._original_governors.items(): | |
| 862 self._android_commands.run(['shell', 'echo', original_content, '>',
file]) | |
| 863 self._original_governors = {} | |
| 864 | |
| 865 def _get_crash_log(self, stdout, stderr, newer_than): | |
| 866 if not stdout: | |
| 867 stdout = '' | |
| 868 stdout += '********* [%s] Logcat:\n%s' % (self._android_commands.get_ser
ial(), self._get_logcat()) | |
| 869 if not stderr: | |
| 870 stderr = '' | |
| 871 stderr += '********* [%s] Tombstone file:\n%s' % (self._android_commands
.get_serial(), self._get_last_stacktrace()) | |
| 872 return super(ChromiumAndroidDriver, self)._get_crash_log(stdout, stderr,
newer_than) | |
| 873 | |
| 874 def cmd_line(self, pixel_tests, per_test_args): | |
| 875 # The returned command line is used to start _server_process. In our cas
e, it's an interactive 'adb shell'. | |
| 876 # The command line passed to the driver process is returned by _driver_c
md_line() instead. | |
| 877 return self._android_commands.adb_command() + ['shell'] | |
| 878 | |
| 879 def _android_driver_cmd_line(self, pixel_tests, per_test_args): | |
| 880 return driver.Driver.cmd_line(self, pixel_tests, per_test_args) | |
| 881 | |
| 882 @staticmethod | |
| 883 def _loop_with_timeout(condition, timeout_secs): | |
| 884 deadline = time.time() + timeout_secs | |
| 885 while time.time() < deadline: | |
| 886 if condition(): | |
| 887 return True | |
| 888 return False | |
| 889 | |
| 890 def _all_pipes_created(self): | |
| 891 return (self._android_commands.file_exists(self._in_fifo_path) and | |
| 892 self._android_commands.file_exists(self._out_fifo_path) and | |
| 893 self._android_commands.file_exists(self._err_fifo_path)) | |
| 894 | |
| 895 def _remove_all_pipes(self): | |
| 896 for file in [self._in_fifo_path, self._out_fifo_path, self._err_fifo_pat
h]: | |
| 897 self._android_commands.run(['shell', 'rm', file]) | |
| 898 | |
| 899 return (not self._android_commands.file_exists(self._in_fifo_path) and | |
| 900 not self._android_commands.file_exists(self._out_fifo_path) and | |
| 901 not self._android_commands.file_exists(self._err_fifo_path)) | |
| 902 | |
| 903 def run_test(self, driver_input, stop_when_done): | |
| 904 base = self._port.lookup_virtual_test_base(driver_input.test_name) | |
| 905 if base: | |
| 906 driver_input = copy.copy(driver_input) | |
| 907 driver_input.args = self._port.lookup_virtual_test_args(driver_input
.test_name) | |
| 908 driver_input.test_name = base | |
| 909 return super(ChromiumAndroidDriver, self).run_test(driver_input, stop_wh
en_done) | |
| 910 | |
| 911 def start(self, pixel_tests, per_test_args): | |
| 912 # We override the default start() so that we can call _android_driver_cm
d_line() | |
| 913 # instead of cmd_line(). | |
| 914 new_cmd_line = self._android_driver_cmd_line(pixel_tests, per_test_args) | |
| 915 | |
| 916 # Since _android_driver_cmd_line() is different than cmd_line() we need
to provide | |
| 917 # our own mechanism for detecting when the process should be stopped. | |
| 918 if self._current_cmd_line is None: | |
| 919 self._current_android_cmd_line = None | |
| 920 if new_cmd_line != self._current_android_cmd_line: | |
| 921 self.stop() | |
| 922 self._current_android_cmd_line = new_cmd_line | |
| 923 | |
| 924 super(ChromiumAndroidDriver, self).start(pixel_tests, per_test_args) | |
| 925 | |
| 926 def _start(self, pixel_tests, per_test_args): | |
| 927 self._setup_test() | |
| 928 | |
| 929 for retries in range(3): | |
| 930 if self._start_once(pixel_tests, per_test_args): | |
| 931 return | |
| 932 self._log_error('Failed to start the content_shell application. Retr
ies=%d. Log:%s' % (retries, self._get_logcat())) | |
| 933 self.stop() | |
| 934 time.sleep(2) | |
| 935 self._abort('Failed to start the content_shell application multiple time
s. Giving up.') | |
| 936 | |
| 937 def _start_once(self, pixel_tests, per_test_args): | |
| 938 super(ChromiumAndroidDriver, self)._start(pixel_tests, per_test_args) | |
| 939 | |
| 940 self._log_debug('Starting forwarder') | |
| 941 self._forwarder_process = self._port._server_process_constructor( | |
| 942 self._port, 'Forwarder', self._android_commands.adb_command() + ['sh
ell', '%s -D %s' % (self._driver_details.device_forwarder_path(), FORWARD_PORTS)
]) | |
| 943 self._forwarder_process.start() | |
| 944 | |
| 945 deadline = time.time() + DRIVER_START_STOP_TIMEOUT_SECS | |
| 946 if not self._wait_for_server_process_output(self._forwarder_process, dea
dline, 'Forwarding device port'): | |
| 947 return False | |
| 948 | |
| 949 self._android_commands.run(['logcat', '-c']) | |
| 950 self._android_commands.run(['shell', 'echo'] + self._android_driver_cmd_
line(pixel_tests, per_test_args) + ['>', self._driver_details.command_line_file(
)]) | |
| 951 start_result = self._android_commands.run(['shell', 'am', 'start', '-e',
'RunInSubThread', '-n', self._driver_details.activity_name()]) | |
| 952 if start_result.find('Exception') != -1: | |
| 953 self._log_error('Failed to start the content_shell application. Exce
ption:\n' + start_result) | |
| 954 return False | |
| 955 | |
| 956 if not ChromiumAndroidDriver._loop_with_timeout(self._all_pipes_created,
DRIVER_START_STOP_TIMEOUT_SECS): | |
| 957 return False | |
| 958 | |
| 959 # Read back the shell prompt to ensure adb shell ready. | |
| 960 deadline = time.time() + DRIVER_START_STOP_TIMEOUT_SECS | |
| 961 self._server_process.start() | |
| 962 self._read_prompt(deadline) | |
| 963 self._log_debug('Interactive shell started') | |
| 964 | |
| 965 # Start a process to read from the stdout fifo of the test driver and pr
int to stdout. | |
| 966 self._log_debug('Redirecting stdout to ' + self._out_fifo_path) | |
| 967 self._read_stdout_process = self._port._server_process_constructor( | |
| 968 self._port, 'ReadStdout', self._android_commands.adb_command() + ['s
hell', 'cat', self._out_fifo_path]) | |
| 969 self._read_stdout_process.start() | |
| 970 | |
| 971 # Start a process to read from the stderr fifo of the test driver and pr
int to stdout. | |
| 972 self._log_debug('Redirecting stderr to ' + self._err_fifo_path) | |
| 973 self._read_stderr_process = self._port._server_process_constructor( | |
| 974 self._port, 'ReadStderr', self._android_commands.adb_command() + ['s
hell', 'cat', self._err_fifo_path]) | |
| 975 self._read_stderr_process.start() | |
| 976 | |
| 977 self._log_debug('Redirecting stdin to ' + self._in_fifo_path) | |
| 978 self._server_process.write('cat >%s\n' % self._in_fifo_path) | |
| 979 | |
| 980 # Combine the stdout and stderr pipes into self._server_process. | |
| 981 self._server_process.replace_outputs(self._read_stdout_process._proc.std
out, self._read_stderr_process._proc.stdout) | |
| 982 | |
| 983 def deadlock_detector(processes, normal_startup_event): | |
| 984 if not ChromiumAndroidDriver._loop_with_timeout(lambda: normal_start
up_event.is_set(), DRIVER_START_STOP_TIMEOUT_SECS): | |
| 985 # If normal_startup_event is not set in time, the main thread mu
st be blocked at | |
| 986 # reading/writing the fifo. Kill the fifo reading/writing proces
ses to let the | |
| 987 # main thread escape from the deadlocked state. After that, the
main thread will | |
| 988 # treat this as a crash. | |
| 989 self._log_error('Deadlock detected. Processes killed.') | |
| 990 for i in processes: | |
| 991 i.kill() | |
| 992 | |
| 993 # Start a thread to kill the pipe reading/writing processes on deadlock
of the fifos during startup. | |
| 994 normal_startup_event = threading.Event() | |
| 995 threading.Thread(name='DeadlockDetector', target=deadlock_detector, | |
| 996 args=([self._server_process, self._read_stdout_process,
self._read_stderr_process], normal_startup_event)).start() | |
| 997 | |
| 998 # The test driver might crash during startup or when the deadlock detect
or hits | |
| 999 # a deadlock and kills the fifo reading/writing processes. | |
| 1000 if not self._wait_for_server_process_output(self._server_process, deadli
ne, '#READY'): | |
| 1001 return False | |
| 1002 | |
| 1003 # Inform the deadlock detector that the startup is successful without de
adlock. | |
| 1004 normal_startup_event.set() | |
| 1005 return True | |
| 1006 | |
| 1007 def _pid_from_android_ps_output(self, ps_output, package_name): | |
| 1008 # ps output seems to be fixed width, we only care about the name and the
pid | |
| 1009 # u0_a72 21630 125 947920 59364 ffffffff 400beee4 S org.chromium.na
tive_test | |
| 1010 for line in ps_output.split('\n'): | |
| 1011 if line.find(self._driver_details.package_name()) != -1: | |
| 1012 match = re.match(r'\S+\s+(\d+)', line) | |
| 1013 return int(match.group(1)) | |
| 1014 | |
| 1015 def _pid_on_target(self): | |
| 1016 # FIXME: There must be a better way to do this than grepping ps output! | |
| 1017 ps_output = self._android_commands.run(['shell', 'ps']) | |
| 1018 return self._pid_from_android_ps_output(ps_output, self._driver_details.
package_name()) | |
| 1019 | |
| 1020 def stop(self): | |
| 1021 self._android_commands.run(['shell', 'am', 'force-stop', self._driver_de
tails.package_name()]) | |
| 1022 | |
| 1023 if self._read_stdout_process: | |
| 1024 self._read_stdout_process.kill() | |
| 1025 self._read_stdout_process = None | |
| 1026 | |
| 1027 if self._read_stderr_process: | |
| 1028 self._read_stderr_process.kill() | |
| 1029 self._read_stderr_process = None | |
| 1030 | |
| 1031 super(ChromiumAndroidDriver, self).stop() | |
| 1032 | |
| 1033 if self._forwarder_process: | |
| 1034 self._forwarder_process.kill() | |
| 1035 self._forwarder_process = None | |
| 1036 | |
| 1037 if self._android_devices.is_device_prepared(self._android_commands.get_s
erial()): | |
| 1038 if not ChromiumAndroidDriver._loop_with_timeout(self._remove_all_pip
es, DRIVER_START_STOP_TIMEOUT_SECS): | |
| 1039 raise AssertionError('Failed to remove fifo files. May be locked
.') | |
| 1040 | |
| 1041 def _command_from_driver_input(self, driver_input): | |
| 1042 command = super(ChromiumAndroidDriver, self)._command_from_driver_input(
driver_input) | |
| 1043 if command.startswith('/'): | |
| 1044 fs = self._port._filesystem | |
| 1045 # FIXME: what happens if command lies outside of the layout_tests_di
r on the host? | |
| 1046 relative_test_filename = fs.relpath(command, fs.dirname(self._port.l
ayout_tests_dir())) | |
| 1047 command = DEVICE_WEBKIT_BASE_DIR + relative_test_filename | |
| 1048 return command | |
| 1049 | |
| 1050 def _read_prompt(self, deadline): | |
| 1051 last_char = '' | |
| 1052 while True: | |
| 1053 current_char = self._server_process.read_stdout(deadline, 1) | |
| 1054 if current_char == ' ': | |
| 1055 if last_char in ('#', '$'): | |
| 1056 return | |
| 1057 last_char = current_char | |
| 1058 | |
| 1059 def _wait_for_server_process_output(self, server_process, deadline, text): | |
| 1060 output = '' | |
| 1061 line = server_process.read_stdout_line(deadline) | |
| 1062 while not server_process.timed_out and not server_process.has_crashed()
and not text in line.rstrip(): | |
| 1063 output += line | |
| 1064 line = server_process.read_stdout_line(deadline) | |
| 1065 | |
| 1066 if server_process.timed_out or server_process.has_crashed(): | |
| 1067 _log.error('Failed to start the %s process: \n%s' % (server_process.
name(), output)) | |
| 1068 return False | |
| 1069 | |
| 1070 return True | |
| OLD | NEW |