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
29import copy
30import logging
31import os
32import re
33import signal
34import sys
35import subprocess
36import threading
37import time
38
39from multiprocessing.pool import ThreadPool
40
41from webkitpy.common.system.executive import ScriptError
42from webkitpy.layout_tests.breakpad.dump_reader_multipart import DumpReaderAndroid
43from webkitpy.layout_tests.models import test_run_results
44from webkitpy.layout_tests.port import base
45from webkitpy.layout_tests.port import linux
46from webkitpy.layout_tests.port import driver
47from webkitpy.layout_tests.port import factory
48from webkitpy.layout_tests.port import server_process
49from 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.
56DEVICE_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).
62DEVICE_WEBKIT_BASE_DIR = DEVICE_SOURCE_ROOT_DIR + 'third_party/WebKit/'
63DEVICE_LAYOUT_TESTS_DIR = DEVICE_WEBKIT_BASE_DIR + 'LayoutTests/'
64
65SCALING_GOVERNORS_PATTERN = "/sys/devices/system/cpu/cpu*/cpufreq/scaling_governor"
66KPTR_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.
72PERF_TEST_PATH_PREFIX = '/all-perf-tests'
73LAYOUT_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).
79FORWARD_PORTS = '8000 8080 8443 8880 9323'
80
81MS_TRUETYPE_FONTS_DIR = '/usr/share/fonts/truetype/msttcorefonts/'
82MS_TRUETYPE_FONTS_PACKAGE = 'ttf-mscorefonts-installer'
83
84# Timeout in seconds to wait for starting/stopping the driver.
85DRIVER_START_STOP_TIMEOUT_SECS = 10
86
87HOST_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.
129TEST_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
142MD5SUM_DEVICE_FILE_NAME = 'md5sum_bin'
143MD5SUM_HOST_FILE_NAME = 'md5sum_bin_host'
144MD5SUM_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.
148class 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', 'content_shell.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.
193class 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        # Also make sure that the output is ascii-encoded to avoid confusing other parts of
261        # the system.
262        self._log_debug('Run adb result: ' + result[:80].encode('ascii', errors='replace'))
263        return result
264
265    def get_serial(self):
266        return self._device_serial
267
268    def adb_command(self):
269        return [AndroidCommands.adb_command_path(self._executive, self._debug_logging), '-s', self._device_serial]
270
271    @staticmethod
272    def set_adb_command_path_options(paths):
273        AndroidCommands._adb_command_path_options = paths
274
275    @staticmethod
276    def adb_command_path(executive, debug_logging):
277        if AndroidCommands._adb_command_path:
278            return AndroidCommands._adb_command_path
279
280        assert AndroidCommands._adb_command_path_options, 'No commands paths have been set to look for the "adb" command.'
281
282        command_path = None
283        command_version = None
284        for path_option in AndroidCommands._adb_command_path_options:
285            path_version = AndroidCommands._determine_adb_version(path_option, executive, debug_logging)
286            if not path_version:
287                continue
288            if command_version != None and path_version < command_version:
289                continue
290
291            command_path = path_option
292            command_version = path_version
293
294        assert command_path, 'Unable to locate the "adb" command. Are you using an Android checkout of Chromium?'
295
296        AndroidCommands._adb_command_path = command_path
297        return command_path
298
299    # Local private methods.
300
301    def _log_error(self, message):
302        _log.error('[%s] %s' % (self._device_serial, message))
303
304    def _log_info(self, message):
305        _log.info('[%s] %s' % (self._device_serial, message))
306
307    def _log_debug(self, message):
308        if self._debug_logging:
309            _log.debug('[%s] %s' % (self._device_serial, message))
310
311    @staticmethod
312    def _determine_adb_version(adb_command_path, executive, debug_logging):
313        re_version = re.compile('^.*version ([\d\.]+)$')
314        try:
315            output = executive.run_command([adb_command_path, 'version'], error_handler=executive.ignore_error,
316                                           debug_logging=debug_logging)
317        except OSError:
318            return None
319
320        result = re_version.match(output)
321        if not output or not result:
322            return None
323
324        return [int(n) for n in result.group(1).split('.')]
325
326
327# A class to encapsulate device status and information, such as the AndroidCommands
328# instances and whether the device has been set up.
329class AndroidDevices(object):
330    # Percentage of battery a device needs to have in order for it to be considered
331    # to participate in running the layout tests.
332    MINIMUM_BATTERY_PERCENTAGE = 30
333
334    def __init__(self, executive, default_device=None, debug_logging=False):
335        self._usable_devices = []
336        self._default_device = default_device
337        self._prepared_devices = []
338        self._debug_logging = debug_logging
339
340    def prepared_devices(self):
341        return self._prepared_devices
342
343    def usable_devices(self, executive):
344        if self._usable_devices:
345            return self._usable_devices
346
347        if self._default_device:
348            self._usable_devices = [AndroidCommands(executive, self._default_device, self._debug_logging)]
349            return self._usable_devices
350
351        # Example "adb devices" command output:
352        #   List of devices attached
353        #   0123456789ABCDEF        device
354        re_device = re.compile('^([a-zA-Z0-9_:.-]+)\tdevice$', re.MULTILINE)
355
356        result = executive.run_command([AndroidCommands.adb_command_path(executive, debug_logging=self._debug_logging), 'devices'],
357                                       error_handler=executive.ignore_error, debug_logging=self._debug_logging)
358        devices = re_device.findall(result)
359        if not devices:
360            return []
361
362        for device_serial in sorted(devices):
363            commands = AndroidCommands(executive, device_serial, self._debug_logging)
364            if self._battery_level_for_device(commands) < AndroidDevices.MINIMUM_BATTERY_PERCENTAGE:
365                _log.warning('Device with serial "%s" skipped because it has less than %d percent battery.'
366                    % (commands.get_serial(), AndroidDevices.MINIMUM_BATTERY_PERCENTAGE))
367                continue
368
369            if not self._is_device_screen_on(commands):
370                _log.warning('Device with serial "%s" skipped because the screen must be on.' % commands.get_serial())
371                continue
372
373            self._usable_devices.append(commands)
374
375        return self._usable_devices
376
377    def get_device(self, executive, device_index):
378        devices = self.usable_devices(executive)
379        if device_index >= len(devices):
380            raise AssertionError('Device index exceeds number of usable devices.')
381
382        return devices[device_index]
383
384    def is_device_prepared(self, device_serial):
385        return device_serial in self._prepared_devices
386
387    def set_device_prepared(self, device_serial):
388        self._prepared_devices.append(device_serial)
389
390    # Private methods
391    def _battery_level_for_device(self, commands):
392        battery_status = commands.run(['shell', 'dumpsys', 'battery'])
393        if 'Error' in battery_status or "Can't find service: battery" in battery_status:
394            _log.warning('Unable to read the battery level from device with serial "%s".' % commands.get_serial())
395            return 0
396
397        return int(re.findall('level: (\d+)', battery_status)[0])
398
399    def _is_device_screen_on(self, commands):
400        power_status = commands.run(['shell', 'dumpsys', 'power'])
401        return 'mScreenOn=true' in power_status or 'mScreenOn=SCREEN_ON_BIT' in power_status or 'Display Power: state=ON' in power_status
402
403
404class AndroidPort(base.Port):
405    port_name = 'android'
406
407    # Avoid initializing the adb path [worker count]+1 times by storing it as a static member.
408    _adb_path = None
409
410    SUPPORTED_VERSIONS = ('android')
411
412    FALLBACK_PATHS = {'icecreamsandwich': ['android'] + linux.LinuxPort.latest_platform_fallback_path()}
413
414    # Android has aac and mp3 codecs built in.
415    PORT_HAS_AUDIO_CODECS_BUILT_IN = True
416
417    BUILD_REQUIREMENTS_URL = 'https://code.google.com/p/chromium/wiki/AndroidBuildInstructions'
418
419    def __init__(self, host, port_name, **kwargs):
420        super(AndroidPort, self).__init__(host, port_name, **kwargs)
421
422        self._operating_system = 'android'
423        self._version = 'icecreamsandwich'
424
425        self._host_port = factory.PortFactory(host).get('chromium', **kwargs)
426        self._server_process_constructor = self._android_server_process_constructor
427
428        if not self.get_option('disable_breakpad'):
429            self._dump_reader = DumpReaderAndroid(host, self._build_path())
430
431        if self.driver_name() != self.CONTENT_SHELL_NAME:
432            raise AssertionError('Layout tests on Android only support content_shell as the driver.')
433
434        self._driver_details = ContentShellDriverDetails()
435
436        # Initialize the AndroidDevices class which tracks available devices.
437        default_device = None
438        if hasattr(self._options, 'adb_device') and len(self._options.adb_device):
439            default_device = self._options.adb_device
440
441        self._debug_logging = self.get_option('android_logging')
442        self._devices = AndroidDevices(self._executive, default_device, self._debug_logging)
443
444        # Tell AndroidCommands where to search for the "adb" command.
445        AndroidCommands.set_adb_command_path_options(['adb',
446            self.path_from_chromium_base('third_party', 'android_tools', 'sdk', 'platform-tools', 'adb')])
447
448        prepared_devices = self.get_option('prepared_devices', [])
449        for serial in prepared_devices:
450            self._devices.set_device_prepared(serial)
451
452    def default_smoke_test_only(self):
453        return True
454
455    # Local public methods.
456    def path_to_forwarder(self):
457        return self._build_path('forwarder')
458
459    def path_to_md5sum(self):
460        return self._build_path(MD5SUM_DEVICE_FILE_NAME)
461
462    def path_to_md5sum_host(self):
463        return self._build_path(MD5SUM_HOST_FILE_NAME)
464
465    def additional_drt_flag(self):
466        return self._driver_details.additional_command_line_flags(use_breakpad=not self.get_option('disable_breakpad'))
467
468    def default_timeout_ms(self):
469        # Android platform has less computing power than desktop platforms.
470        # Using 10 seconds allows us to pass most slow tests which are not
471        # marked as slow tests on desktop platforms.
472        return 10 * 1000
473
474    def driver_stop_timeout(self):
475        # The driver doesn't respond to closing stdin, so we might as well stop the driver immediately.
476        return 0.0
477
478    def default_child_processes(self):
479        usable_devices = self._devices.usable_devices(self._executive)
480        if not usable_devices:
481            raise test_run_results.TestRunException(test_run_results.NO_DEVICES_EXIT_STATUS, "Unable to find any attached Android devices.")
482        return len(usable_devices)
483
484    def check_wdiff(self, logging=True):
485        return self._host_port.check_wdiff(logging)
486
487    def check_build(self, needs_http, printer):
488        exit_status = super(AndroidPort, self).check_build(needs_http, printer)
489        if exit_status:
490            return exit_status
491
492        result = self._check_file_exists(self.path_to_md5sum(), 'md5sum utility')
493        result = self._check_file_exists(self.path_to_md5sum_host(), 'md5sum host utility') and result
494        result = self._check_file_exists(self.path_to_forwarder(), 'forwarder utility') and result
495
496        if not result:
497            # There is a race condition in adb at least <= 4.3 on Linux that causes it to go offline periodically
498            # We set the processor affinity for any running adb process to attempt to work around this.
499            # See crbug.com/268450
500            if self.host.platform.is_linux():
501                pids = self._executive.running_pids(lambda name: 'adb' in name)
502                if not pids:
503                    # Apparently adb is not running, which is unusual. Running any adb command should start it.
504                    self._executive.run_command(['adb', 'devices'])
505                    pids = self._executive.running_pids(lambda name: 'adb' in name)
506                if not pids:
507                    _log.error("The adb daemon does not appear to be running.")
508                    return False
509
510                for pid in pids:
511                    self._executive.run_command(['taskset', '-p', '-c', '0', str(pid)])
512
513        if not result:
514            _log.error('For complete Android build requirements, please see:')
515            _log.error('')
516            _log.error('    http://code.google.com/p/chromium/wiki/AndroidBuildInstructions')
517            return test_run_results.UNEXPECTED_ERROR_EXIT_STATUS
518
519        return self._check_devices(printer)
520
521    def _check_devices(self, printer):
522        # Printer objects aren't threadsafe, so we need to protect calls to them.
523        lock = threading.Lock()
524        pool = None
525
526        # Push the executables and other files to the devices; doing this now
527        # means we can do this in parallel in the manager process and not mix
528        # this in with starting and stopping workers.
529        def setup_device(worker_number):
530            d = self.create_driver(worker_number)
531            serial = d._android_commands.get_serial()
532
533            def log_safely(msg, throttled=True):
534                if throttled:
535                    callback = printer.write_throttled_update
536                else:
537                    callback = printer.write_update
538                lock.acquire()
539                try:
540                    callback("[%s] %s" % (serial, msg))
541                finally:
542                    lock.release()
543
544            log_safely("preparing device", throttled=False)
545            try:
546                d._setup_test(log_safely)
547                log_safely("device prepared", throttled=False)
548            except (ScriptError, driver.DeviceFailure) as e:
549                lock.acquire()
550                _log.warning("[%s] failed to prepare_device: %s" % (serial, str(e)))
551                lock.release()
552            except KeyboardInterrupt:
553                if pool:
554                    pool.terminate()
555
556        # FIXME: It would be nice if we knew how many workers we needed.
557        num_workers = self.default_child_processes()
558        num_child_processes = int(self.get_option('child_processes'))
559        if num_child_processes:
560            num_workers = min(num_workers, num_child_processes)
561        if num_workers > 1:
562            pool = ThreadPool(num_workers)
563            try:
564                pool.map(setup_device, range(num_workers))
565            except KeyboardInterrupt:
566                pool.terminate()
567                raise
568        else:
569            setup_device(0)
570
571        if not self._devices.prepared_devices():
572            _log.error('Could not prepare any devices for testing.')
573            return test_run_results.NO_DEVICES_EXIT_STATUS
574        return test_run_results.OK_EXIT_STATUS
575
576    def setup_test_run(self):
577        super(AndroidPort, self).setup_test_run()
578
579        # By setting this on the options object, we can propagate the list
580        # of prepared devices to the workers (it is read in __init__()).
581        if self._devices._prepared_devices:
582            self._options.prepared_devices = self._devices.prepared_devices()
583        else:
584            # We were called with --no-build, so assume the devices are up to date.
585            self._options.prepared_devices = [d.get_serial() for d in self._devices.usable_devices(self.host.executive)]
586
587    def num_workers(self, requested_num_workers):
588        return min(len(self._options.prepared_devices), requested_num_workers)
589
590    def check_sys_deps(self, needs_http):
591        for (font_dirs, font_file, package) in HOST_FONT_FILES:
592            exists = False
593            for font_dir in font_dirs:
594                font_path = font_dir + font_file
595                if self._check_file_exists(font_path, '', logging=False):
596                    exists = True
597                    break
598            if not exists:
599                _log.error('You are missing %s under %s. Try installing %s. See build instructions.' % (font_file, font_dirs, package))
600                return test_run_results.SYS_DEPS_EXIT_STATUS
601        return test_run_results.OK_EXIT_STATUS
602
603    def requires_http_server(self):
604        """Chromium Android runs tests on devices, and uses the HTTP server to
605        serve the actual layout tests to the test driver."""
606        return True
607
608    def start_http_server(self, additional_dirs, number_of_drivers):
609        additional_dirs[PERF_TEST_PATH_PREFIX] = self.perf_tests_dir()
610        additional_dirs[LAYOUT_TEST_PATH_PREFIX] = self.layout_tests_dir()
611        super(AndroidPort, self).start_http_server(additional_dirs, number_of_drivers)
612
613    def create_driver(self, worker_number, no_timeout=False):
614        return ChromiumAndroidDriver(self, worker_number, pixel_tests=self.get_option('pixel_tests'),
615                                     driver_details=self._driver_details,
616                                     android_devices=self._devices,
617                                     # Force no timeout to avoid test driver timeouts before NRWT.
618                                     no_timeout=True)
619
620    def driver_cmd_line(self):
621        # Override to return the actual test driver's command line.
622        return self.create_driver(0)._android_driver_cmd_line(self.get_option('pixel_tests'), [])
623
624    def clobber_old_port_specific_results(self):
625        if not self.get_option('disable_breakpad'):
626            self._dump_reader.clobber_old_results()
627
628    # Overridden protected methods.
629
630    def _build_path(self, *comps):
631        return self._host_port._build_path(*comps)
632
633    def _build_path_with_configuration(self, configuration, *comps):
634        return self._host_port._build_path_with_configuration(configuration, *comps)
635
636    def path_to_apache(self):
637        return self._host_port.path_to_apache()
638
639    def path_to_apache_config_file(self):
640        return self._host_port.path_to_apache_config_file()
641
642    def _path_to_driver(self, configuration=None):
643        return self._build_path_with_configuration(configuration, self._driver_details.apk_name())
644
645    def _path_to_helper(self):
646        return None
647
648    def _path_to_image_diff(self):
649        return self._host_port._path_to_image_diff()
650
651    def _path_to_wdiff(self):
652        return self._host_port._path_to_wdiff()
653
654    def _shut_down_http_server(self, pid):
655        return self._host_port._shut_down_http_server(pid)
656
657    def _driver_class(self):
658        return ChromiumAndroidDriver
659
660    # Local private methods.
661
662    @staticmethod
663    def _android_server_process_constructor(port, server_name, cmd_line, env=None, logging=False):
664        return server_process.ServerProcess(port, server_name, cmd_line, env,
665                                            universal_newlines=True, treat_no_data_as_crash=True, logging=logging)
666
667
668class AndroidPerf(SingleFileOutputProfiler):
669    _cached_perf_host_path = None
670    _have_searched_for_perf_host = False
671
672    def __init__(self, host, executable_path, output_dir, android_commands, symfs_path, kallsyms_path, identifier=None):
673        super(AndroidPerf, self).__init__(host, executable_path, output_dir, "data", identifier)
674        self._android_commands = android_commands
675        self._perf_process = None
676        self._symfs_path = symfs_path
677        self._kallsyms_path = kallsyms_path
678
679    def check_configuration(self):
680        # Check that perf is installed
681        if not self._android_commands.file_exists('/system/bin/perf'):
682            print "Cannot find /system/bin/perf on device %s" % self._android_commands.get_serial()
683            return False
684
685        # Check that the device is a userdebug build (or at least has the necessary libraries).
686        if self._android_commands.run(['shell', 'getprop', 'ro.build.type']).strip() != 'userdebug':
687            print "Device %s is not flashed with a userdebug build of Android" % self._android_commands.get_serial()
688            return False
689
690        # FIXME: Check that the binary actually is perf-able (has stackframe pointers)?
691        # objdump -s a function and make sure it modifies the fp?
692        # Instruct users to rebuild after export GYP_DEFINES="profiling=1 $GYP_DEFINES"
693        return True
694
695    def print_setup_instructions(self):
696        print """
697perf on android requires a 'userdebug' build of Android, see:
698http://source.android.com/source/building-devices.html"
699
700The perf command can be built from:
701https://android.googlesource.com/platform/external/linux-tools-perf/
702and requires libefl, libebl, libdw, and libdwfl available in:
703https://android.googlesource.com/platform/external/elfutils/
704
705The test driver must be built with profiling=1, make sure you've done:
706export GYP_DEFINES="profiling=1 $GYP_DEFINES"
707update-webkit --chromium-android
708build-webkit --chromium-android
709
710Googlers should read:
711http://goto.google.com/cr-android-perf-howto
712"""
713
714    def attach_to_pid(self, pid):
715        assert(pid)
716        assert(self._perf_process == None)
717        # FIXME: This can't be a fixed timeout!
718        cmd = self._android_commands.adb_command() + ['shell', 'perf', 'record', '-g', '-p', pid, 'sleep', 30]
719        self._perf_process = self._host.executive.popen(cmd)
720
721    def _perf_version_string(self, perf_path):
722        try:
723            return self._host.executive.run_command([perf_path, '--version'])
724        except:
725            return None
726
727    def _find_perfhost_binary(self):
728        perfhost_version = self._perf_version_string('perfhost_linux')
729        if perfhost_version:
730            return 'perfhost_linux'
731        perf_version = self._perf_version_string('perf')
732        if perf_version:
733            return 'perf'
734        return None
735
736    def _perfhost_path(self):
737        if self._have_searched_for_perf_host:
738            return self._cached_perf_host_path
739        self._have_searched_for_perf_host = True
740        self._cached_perf_host_path = self._find_perfhost_binary()
741        return self._cached_perf_host_path
742
743    def _first_ten_lines_of_profile(self, perf_output):
744        match = re.search("^#[^\n]*\n((?: [^\n]*\n){1,10})", perf_output, re.MULTILINE)
745        return match.group(1) if match else None
746
747    def profile_after_exit(self):
748        perf_exitcode = self._perf_process.wait()
749        if perf_exitcode != 0:
750            print "Perf failed (exit code: %i), can't process results." % perf_exitcode
751            return
752
753        self._android_commands.pull('/data/perf.data', self._output_path)
754
755        perfhost_path = self._perfhost_path()
756        perfhost_report_command = [
757            'report',
758            '--input', self._output_path,
759            '--symfs', self._symfs_path,
760            '--kallsyms', self._kallsyms_path,
761        ]
762        if perfhost_path:
763            perfhost_args = [perfhost_path] + perfhost_report_command + ['--call-graph', 'none']
764            perf_output = self._host.executive.run_command(perfhost_args)
765            # We could save off the full -g report to a file if users found that useful.
766            print self._first_ten_lines_of_profile(perf_output)
767        else:
768            print """
769Failed to find perfhost_linux binary, can't process samples from the device.
770
771perfhost_linux can be built from:
772https://android.googlesource.com/platform/external/linux-tools-perf/
773also, modern versions of perf (available from apt-get install goobuntu-kernel-tools-common)
774may also be able to process the perf.data files from the device.
775
776Googlers should read:
777http://goto.google.com/cr-android-perf-howto
778for instructions on installing pre-built copies of perfhost_linux
779http://crbug.com/165250 discusses making these pre-built binaries externally available.
780"""
781
782        perfhost_display_patch = perfhost_path if perfhost_path else 'perfhost_linux'
783        print "To view the full profile, run:"
784        print ' '.join([perfhost_display_patch] + perfhost_report_command)
785
786
787class ChromiumAndroidDriver(driver.Driver):
788    def __init__(self, port, worker_number, pixel_tests, driver_details, android_devices, no_timeout=False):
789        super(ChromiumAndroidDriver, self).__init__(port, worker_number, pixel_tests, no_timeout)
790        self._in_fifo_path = driver_details.device_fifo_directory() + 'stdin.fifo'
791        self._out_fifo_path = driver_details.device_fifo_directory() + 'test.fifo'
792        self._err_fifo_path = driver_details.device_fifo_directory() + 'stderr.fifo'
793        self._read_stdout_process = None
794        self._read_stderr_process = None
795        self._forwarder_process = None
796        self._original_governors = {}
797        self._original_kptr_restrict = None
798
799        self._android_devices = android_devices
800        self._android_commands = android_devices.get_device(port._executive, worker_number)
801        self._driver_details = driver_details
802        self._debug_logging = self._port._debug_logging
803        self._created_cmd_line = False
804        self._device_failed = False
805
806        # FIXME: If we taught ProfileFactory about "target" devices we could
807        # just use the logic in Driver instead of duplicating it here.
808        if self._port.get_option("profile"):
809            # FIXME: This should be done once, instead of per-driver!
810            symfs_path = self._find_or_create_symfs()
811            kallsyms_path = self._update_kallsyms_cache(symfs_path)
812            # FIXME: We should pass this some sort of "Bridge" object abstraction around ADB instead of a path/device pair.
813            self._profiler = AndroidPerf(self._port.host, self._port._path_to_driver(), self._port.results_directory(),
814                self._android_commands, symfs_path, kallsyms_path)
815            # FIXME: This is a layering violation and should be moved to Port.check_sys_deps
816            # once we have an abstraction around an adb_path/device_serial pair to make it
817            # easy to make these class methods on AndroidPerf.
818            if not self._profiler.check_configuration():
819                self._profiler.print_setup_instructions()
820                sys.exit(1)
821        else:
822            self._profiler = None
823
824    def __del__(self):
825        self._teardown_performance()
826        self._clean_up_cmd_line()
827        super(ChromiumAndroidDriver, self).__del__()
828
829    def _update_kallsyms_cache(self, output_dir):
830        kallsyms_name = "%s-kallsyms" % self._android_commands.get_serial()
831        kallsyms_cache_path = self._port.host.filesystem.join(output_dir, kallsyms_name)
832
833        self._android_commands.restart_as_root()
834
835        saved_kptr_restrict = self._android_commands.run(['shell', 'cat', KPTR_RESTRICT_PATH]).strip()
836        self._android_commands.run(['shell', 'echo', '0', '>', KPTR_RESTRICT_PATH])
837
838        print "Updating kallsyms file (%s) from device" % kallsyms_cache_path
839        self._android_commands.pull("/proc/kallsyms", kallsyms_cache_path)
840
841        self._android_commands.run(['shell', 'echo', saved_kptr_restrict, '>', KPTR_RESTRICT_PATH])
842
843        return kallsyms_cache_path
844
845    def _find_or_create_symfs(self):
846        environment = self._port.host.copy_current_environment()
847        env = environment.to_dictionary()
848        fs = self._port.host.filesystem
849
850        if 'ANDROID_SYMFS' in env:
851            symfs_path = env['ANDROID_SYMFS']
852        else:
853            symfs_path = fs.join(self._port.results_directory(), 'symfs')
854            print "ANDROID_SYMFS not set, using %s" % symfs_path
855
856        # find the installed path, and the path of the symboled built library
857        # FIXME: We should get the install path from the device!
858        symfs_library_path = fs.join(symfs_path, "data/app-lib/%s-1/%s" % (self._driver_details.package_name(), self._driver_details.library_name()))
859        built_library_path = self._port._build_path('lib', self._driver_details.library_name())
860        assert(fs.exists(built_library_path))
861
862        # FIXME: Ideally we'd check the sha1's first and make a soft-link instead of copying (since we probably never care about windows).
863        print "Updating symfs libary (%s) from built copy (%s)" % (symfs_library_path, built_library_path)
864        fs.maybe_make_directory(fs.dirname(symfs_library_path))
865        fs.copyfile(built_library_path, symfs_library_path)
866
867        return symfs_path
868
869    def _setup_md5sum_and_push_data_if_needed(self, log_callback):
870        self._md5sum_path = self._port.path_to_md5sum()
871        if not self._android_commands.file_exists(MD5SUM_DEVICE_PATH):
872            if not self._android_commands.push(self._md5sum_path, MD5SUM_DEVICE_PATH):
873                self._abort('Could not push md5sum to device')
874
875        self._push_executable(log_callback)
876        self._push_fonts(log_callback)
877        self._push_test_resources(log_callback)
878
879    def _setup_test(self, log_callback):
880        # FIXME: Move this routine and its subroutines off of the AndroidDriver
881        # class and onto AndroidCommands or some other helper class, so that we
882        # can initialize the device without needing to create a driver.
883
884        if self._android_devices.is_device_prepared(self._android_commands.get_serial()):
885            return
886
887        self._android_commands.restart_adb()
888        self._android_commands.restart_as_root()
889        self._setup_md5sum_and_push_data_if_needed(log_callback)
890        self._setup_performance()
891
892        # Required by webkit_support::GetWebKitRootDirFilePath().
893        # Other directories will be created automatically by adb push.
894        self._android_commands.mkdir(DEVICE_SOURCE_ROOT_DIR + 'chrome')
895
896        # Allow the test driver to get full read and write access to the directory on the device,
897        # as well as for the FIFOs. We'll need a world writable directory.
898        self._android_commands.mkdir(self._driver_details.device_directory(), chmod='777')
899        self._android_commands.mkdir(self._driver_details.device_fifo_directory(), chmod='777')
900
901        # Make sure that the disk cache on the device resets to a clean state.
902        self._android_commands.run(['shell', 'rm', '-r', self._driver_details.device_cache_directory()])
903
904        # Mark this device as having been set up.
905        self._android_devices.set_device_prepared(self._android_commands.get_serial())
906
907    def _log_error(self, message):
908        _log.error('[%s] %s' % (self._android_commands.get_serial(), message))
909
910    def _log_warning(self, message):
911        _log.warning('[%s] %s' % (self._android_commands.get_serial(), message))
912
913    def _log_debug(self, message):
914        if self._debug_logging:
915            _log.debug('[%s] %s' % (self._android_commands.get_serial(), message))
916
917    def _abort(self, message):
918        self._device_failed = True
919        raise driver.DeviceFailure('[%s] %s' % (self._android_commands.get_serial(), message))
920
921    @staticmethod
922    def _extract_hashes_from_md5sum_output(md5sum_output):
923        assert md5sum_output
924        return [line.split('  ')[0] for line in md5sum_output]
925
926    def _files_match(self, host_file, device_file):
927        assert self._port.host.filesystem.exists(host_file)
928        device_hashes = self._extract_hashes_from_md5sum_output(
929                self._port.host.executive.popen(self._android_commands.adb_command() + ['shell', MD5SUM_DEVICE_PATH, device_file],
930                                                stdout=subprocess.PIPE).stdout)
931        host_hashes = self._extract_hashes_from_md5sum_output(
932                self._port.host.executive.popen(args=['%s_host' % self._md5sum_path, host_file],
933                                                stdout=subprocess.PIPE).stdout)
934        return host_hashes and device_hashes == host_hashes
935
936    def _push_file_if_needed(self, host_file, device_file, log_callback):
937        basename = self._port.host.filesystem.basename(host_file)
938        log_callback("checking %s" % basename)
939        if not self._files_match(host_file, device_file):
940            log_callback("pushing %s" % basename)
941            self._android_commands.push(host_file, device_file)
942
943    def _push_executable(self, log_callback):
944        self._push_file_if_needed(self._port.path_to_forwarder(), self._driver_details.device_forwarder_path(), log_callback)
945        for resource in self._driver_details.additional_resources():
946            self._push_file_if_needed(self._port._build_path(resource), self._driver_details.device_directory() + resource, log_callback)
947
948        self._push_file_if_needed(self._port._build_path('android_main_fonts.xml'), self._driver_details.device_directory() + 'android_main_fonts.xml', log_callback)
949        self._push_file_if_needed(self._port._build_path('android_fallback_fonts.xml'), self._driver_details.device_directory() + 'android_fallback_fonts.xml', log_callback)
950
951        log_callback("checking apk")
952        if self._files_match(self._port._build_path('apks', 'ContentShell.apk'),
953                             '/data/app/org.chromium.content_shell_apk-1.apk'):
954            return
955
956        log_callback("uninstalling apk")
957        self._android_commands.run(['uninstall', self._driver_details.package_name()])
958        driver_host_path = self._port._path_to_driver()
959        log_callback("installing apk")
960        install_result = self._android_commands.run(['install', driver_host_path])
961        if install_result.find('Success') == -1:
962            self._abort('Failed to install %s onto device: %s' % (driver_host_path, install_result))
963
964    def _push_fonts(self, log_callback):
965        path_to_ahem_font = self._port._build_path('AHEM____.TTF')
966        self._push_file_if_needed(path_to_ahem_font, self._driver_details.device_fonts_directory() + 'AHEM____.TTF', log_callback)
967        for (host_dirs, font_file, package) in HOST_FONT_FILES:
968            for host_dir in host_dirs:
969                host_font_path = host_dir + font_file
970                if self._port._check_file_exists(host_font_path, '', logging=False):
971                    self._push_file_if_needed(host_font_path, self._driver_details.device_fonts_directory() + font_file, log_callback)
972
973    def _push_test_resources(self, log_callback):
974        for resource in TEST_RESOURCES_TO_PUSH:
975            self._push_file_if_needed(self._port.layout_tests_dir() + '/' + resource, DEVICE_LAYOUT_TESTS_DIR + resource, log_callback)
976
977    def _get_last_stacktrace(self):
978        tombstones = self._android_commands.run(['shell', 'ls', '-n', '/data/tombstones/tombstone_*'])
979        if not tombstones or tombstones.startswith('/data/tombstones/tombstone_*: No such file or directory'):
980            self._log_error('The driver crashed, but no tombstone found!')
981            return ''
982
983        if tombstones.startswith('/data/tombstones/tombstone_*: Permission denied'):
984            # FIXME: crbug.com/321489 ... figure out why this happens.
985            self._log_error('The driver crashed, but we could not read the tombstones!')
986            return ''
987
988        tombstones = tombstones.rstrip().split('\n')
989        last_tombstone = None
990        for tombstone in tombstones:
991            # Format of fields:
992            # 0          1      2      3     4          5     6
993            # permission uid    gid    size  date       time  filename
994            # -rw------- 1000   1000   45859 2011-04-13 06:00 tombstone_00
995            fields = tombstone.split()
996            if len(fields) != 7:
997                self._log_warning("unexpected line in tombstone output, skipping: '%s'" % tombstone)
998                continue
999
1000            if not last_tombstone or fields[4] + fields[5] >= last_tombstone[4] + last_tombstone[5]:
1001                last_tombstone = fields
1002            else:
1003                break
1004
1005        if not last_tombstone:
1006            self._log_error('The driver crashed, but we could not find any valid tombstone!')
1007            return ''
1008
1009        # Use Android tool vendor/google/tools/stack to convert the raw
1010        # stack trace into a human readable format, if needed.
1011        # It takes a long time, so don't do it here.
1012        return '%s\n%s' % (' '.join(last_tombstone),
1013                           self._android_commands.run(['shell', 'cat', '/data/tombstones/' + last_tombstone[6]]))
1014
1015    def _get_logcat(self):
1016        return self._android_commands.run(['logcat', '-d', '-v', 'threadtime'])
1017
1018    def _setup_performance(self):
1019        # Disable CPU scaling and drop ram cache to reduce noise in tests
1020        if not self._original_governors:
1021            governor_files = self._android_commands.run(['shell', 'ls', SCALING_GOVERNORS_PATTERN])
1022            if governor_files.find('No such file or directory') == -1:
1023                for file in governor_files.split():
1024                    self._original_governors[file] = self._android_commands.run(['shell', 'cat', file]).strip()
1025                    self._android_commands.run(['shell', 'echo', 'performance', '>', file])
1026
1027    def _teardown_performance(self):
1028        for file, original_content in self._original_governors.items():
1029            self._android_commands.run(['shell', 'echo', original_content, '>', file])
1030        self._original_governors = {}
1031
1032    def _get_crash_log(self, stdout, stderr, newer_than):
1033        if not stdout:
1034            stdout = ''
1035        stdout += '********* [%s] Logcat:\n%s' % (self._android_commands.get_serial(), self._get_logcat())
1036        if not stderr:
1037            stderr = ''
1038        stderr += '********* [%s] Tombstone file:\n%s' % (self._android_commands.get_serial(), self._get_last_stacktrace())
1039
1040        if not self._port.get_option('disable_breakpad'):
1041            crashes = self._pull_crash_dumps_from_device()
1042            for crash in crashes:
1043                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))
1044
1045        return super(ChromiumAndroidDriver, self)._get_crash_log(stdout, stderr, newer_than)
1046
1047    def cmd_line(self, pixel_tests, per_test_args):
1048        # The returned command line is used to start _server_process. In our case, it's an interactive 'adb shell'.
1049        # The command line passed to the driver process is returned by _driver_cmd_line() instead.
1050        return self._android_commands.adb_command() + ['shell']
1051
1052    def _android_driver_cmd_line(self, pixel_tests, per_test_args):
1053        return driver.Driver.cmd_line(self, pixel_tests, per_test_args)
1054
1055    @staticmethod
1056    def _loop_with_timeout(condition, timeout_secs):
1057        deadline = time.time() + timeout_secs
1058        while time.time() < deadline:
1059            if condition():
1060                return True
1061        return False
1062
1063    def _all_pipes_created(self):
1064        return (self._android_commands.file_exists(self._in_fifo_path) and
1065                self._android_commands.file_exists(self._out_fifo_path) and
1066                self._android_commands.file_exists(self._err_fifo_path))
1067
1068    def _remove_all_pipes(self):
1069        for file in [self._in_fifo_path, self._out_fifo_path, self._err_fifo_path]:
1070            self._android_commands.run(['shell', 'rm', file])
1071
1072        return (not self._android_commands.file_exists(self._in_fifo_path) and
1073                not self._android_commands.file_exists(self._out_fifo_path) and
1074                not self._android_commands.file_exists(self._err_fifo_path))
1075
1076    def start(self, pixel_tests, per_test_args, deadline):
1077        # We override the default start() so that we can call _android_driver_cmd_line()
1078        # instead of cmd_line().
1079        new_cmd_line = self._android_driver_cmd_line(pixel_tests, per_test_args)
1080
1081        # Since _android_driver_cmd_line() is different than cmd_line() we need to provide
1082        # our own mechanism for detecting when the process should be stopped.
1083        if self._current_cmd_line is None:
1084            self._current_android_cmd_line = None
1085        if new_cmd_line != self._current_android_cmd_line:
1086            self.stop()
1087        self._current_android_cmd_line = new_cmd_line
1088
1089        super(ChromiumAndroidDriver, self).start(pixel_tests, per_test_args, deadline)
1090
1091    def _start(self, pixel_tests, per_test_args):
1092        if not self._android_devices.is_device_prepared(self._android_commands.get_serial()):
1093            raise driver.DeviceFailure("%s is not prepared in _start()" % self._android_commands.get_serial())
1094
1095        for retries in range(3):
1096            try:
1097                if self._start_once(pixel_tests, per_test_args):
1098                    return
1099            except ScriptError as e:
1100                self._abort('ScriptError("%s") in _start()' % str(e))
1101
1102            self._log_error('Failed to start the content_shell application. Retries=%d. Log:%s' % (retries, self._get_logcat()))
1103            self.stop()
1104            time.sleep(2)
1105        self._abort('Failed to start the content_shell application multiple times. Giving up.')
1106
1107    def _start_once(self, pixel_tests, per_test_args):
1108        super(ChromiumAndroidDriver, self)._start(pixel_tests, per_test_args, wait_for_ready=False)
1109
1110        self._log_debug('Starting forwarder')
1111        self._forwarder_process = self._port._server_process_constructor(
1112            self._port, 'Forwarder', self._android_commands.adb_command() + ['shell', '%s -D %s' % (self._driver_details.device_forwarder_path(), FORWARD_PORTS)])
1113        self._forwarder_process.start()
1114
1115        deadline = time.time() + DRIVER_START_STOP_TIMEOUT_SECS
1116        if not self._wait_for_server_process_output(self._forwarder_process, deadline, 'Forwarding device port'):
1117            return False
1118
1119        self._android_commands.run(['logcat', '-c'])
1120
1121        cmd_line_file_path = self._driver_details.command_line_file()
1122        original_cmd_line_file_path = cmd_line_file_path + '.orig'
1123        if self._android_commands.file_exists(cmd_line_file_path) and not self._android_commands.file_exists(original_cmd_line_file_path):
1124            # We check for both the normal path and the backup because we do not want to step
1125            # on the backup. Otherwise, we'd clobber the backup whenever we changed the
1126            # command line during the run.
1127            self._android_commands.run(['shell', 'mv', cmd_line_file_path, original_cmd_line_file_path])
1128
1129        self._android_commands.run(['shell', 'echo'] + self._android_driver_cmd_line(pixel_tests, per_test_args) + ['>', self._driver_details.command_line_file()])
1130        self._created_cmd_line = True
1131
1132        self._android_commands.run(['shell', 'rm', '-rf', self._driver_details.device_crash_dumps_directory()])
1133        self._android_commands.mkdir(self._driver_details.device_crash_dumps_directory(), chmod='777')
1134
1135        start_result = self._android_commands.run(['shell', 'am', 'start', '-e', 'RunInSubThread', '-n', self._driver_details.activity_name()])
1136        if start_result.find('Exception') != -1:
1137            self._log_error('Failed to start the content_shell application. Exception:\n' + start_result)
1138            return False
1139
1140        if not ChromiumAndroidDriver._loop_with_timeout(self._all_pipes_created, DRIVER_START_STOP_TIMEOUT_SECS):
1141            return False
1142
1143        # Read back the shell prompt to ensure adb shell ready.
1144        deadline = time.time() + DRIVER_START_STOP_TIMEOUT_SECS
1145        self._server_process.start()
1146        self._read_prompt(deadline)
1147        self._log_debug('Interactive shell started')
1148
1149        # Start a process to read from the stdout fifo of the test driver and print to stdout.
1150        self._log_debug('Redirecting stdout to ' + self._out_fifo_path)
1151        self._read_stdout_process = self._port._server_process_constructor(
1152            self._port, 'ReadStdout', self._android_commands.adb_command() + ['shell', 'cat', self._out_fifo_path])
1153        self._read_stdout_process.start()
1154
1155        # Start a process to read from the stderr fifo of the test driver and print to stdout.
1156        self._log_debug('Redirecting stderr to ' + self._err_fifo_path)
1157        self._read_stderr_process = self._port._server_process_constructor(
1158            self._port, 'ReadStderr', self._android_commands.adb_command() + ['shell', 'cat', self._err_fifo_path])
1159        self._read_stderr_process.start()
1160
1161        self._log_debug('Redirecting stdin to ' + self._in_fifo_path)
1162        self._server_process.write('cat >%s\n' % self._in_fifo_path)
1163
1164        # Combine the stdout and stderr pipes into self._server_process.
1165        self._server_process.replace_outputs(self._read_stdout_process._proc.stdout, self._read_stderr_process._proc.stdout)
1166
1167        def deadlock_detector(processes, normal_startup_event):
1168            if not ChromiumAndroidDriver._loop_with_timeout(lambda: normal_startup_event.is_set(), DRIVER_START_STOP_TIMEOUT_SECS):
1169                # If normal_startup_event is not set in time, the main thread must be blocked at
1170                # reading/writing the fifo. Kill the fifo reading/writing processes to let the
1171                # main thread escape from the deadlocked state. After that, the main thread will
1172                # treat this as a crash.
1173                self._log_error('Deadlock detected. Processes killed.')
1174                for i in processes:
1175                    i.kill()
1176
1177        # Start a thread to kill the pipe reading/writing processes on deadlock of the fifos during startup.
1178        normal_startup_event = threading.Event()
1179        threading.Thread(name='DeadlockDetector', target=deadlock_detector,
1180                         args=([self._server_process, self._read_stdout_process, self._read_stderr_process], normal_startup_event)).start()
1181
1182        # The test driver might crash during startup or when the deadlock detector hits
1183        # a deadlock and kills the fifo reading/writing processes.
1184        if not self._wait_for_server_process_output(self._server_process, deadline, '#READY'):
1185            return False
1186
1187        # Inform the deadlock detector that the startup is successful without deadlock.
1188        normal_startup_event.set()
1189        self._log_debug("content_shell is ready")
1190        return True
1191
1192    def _pid_from_android_ps_output(self, ps_output, package_name):
1193        # ps output seems to be fixed width, we only care about the name and the pid
1194        # u0_a72    21630 125   947920 59364 ffffffff 400beee4 S org.chromium.native_test
1195        for line in ps_output.split('\n'):
1196            if line.find(self._driver_details.package_name()) != -1:
1197                match = re.match(r'\S+\s+(\d+)', line)
1198                return int(match.group(1))
1199
1200    def _pid_on_target(self):
1201        # FIXME: There must be a better way to do this than grepping ps output!
1202        ps_output = self._android_commands.run(['shell', 'ps'])
1203        return self._pid_from_android_ps_output(ps_output, self._driver_details.package_name())
1204
1205    def stop(self):
1206        if not self._device_failed:
1207            # Do not try to stop the application if there's something wrong with the device; adb may hang.
1208            # FIXME: crbug.com/305040. Figure out if it's really hanging (and why).
1209            self._android_commands.run(['shell', 'am', 'force-stop', self._driver_details.package_name()])
1210
1211        if self._read_stdout_process:
1212            self._read_stdout_process.kill()
1213            self._read_stdout_process = None
1214
1215        if self._read_stderr_process:
1216            self._read_stderr_process.kill()
1217            self._read_stderr_process = None
1218
1219        super(ChromiumAndroidDriver, self).stop()
1220
1221        if self._forwarder_process:
1222            self._forwarder_process.kill()
1223            self._forwarder_process = None
1224
1225        if self._android_devices.is_device_prepared(self._android_commands.get_serial()):
1226            if not ChromiumAndroidDriver._loop_with_timeout(self._remove_all_pipes, DRIVER_START_STOP_TIMEOUT_SECS):
1227                self._abort('Failed to remove fifo files. May be locked.')
1228
1229        self._clean_up_cmd_line()
1230
1231    def _pull_crash_dumps_from_device(self):
1232        result = []
1233        if not self._android_commands.file_exists(self._driver_details.device_crash_dumps_directory()):
1234            return result
1235        dumps = self._android_commands.run(['shell', 'ls', self._driver_details.device_crash_dumps_directory()])
1236        for dump in dumps.splitlines():
1237            device_dump = '%s/%s' % (self._driver_details.device_crash_dumps_directory(), dump)
1238            local_dump = self._port._filesystem.join(self._port._dump_reader.crash_dumps_directory(), dump)
1239
1240            # FIXME: crbug.com/321489. Figure out why these commands would fail ...
1241            err = self._android_commands.run(['shell', 'chmod', '777', device_dump])
1242            if not err:
1243                self._android_commands.pull(device_dump, local_dump)
1244            if not err:
1245                self._android_commands.run(['shell', 'rm', '-f', device_dump])
1246
1247            if self._port._filesystem.exists(local_dump):
1248                result.append(local_dump)
1249        return result
1250
1251    def _clean_up_cmd_line(self):
1252        if not self._created_cmd_line:
1253            return
1254
1255        cmd_line_file_path = self._driver_details.command_line_file()
1256        original_cmd_line_file_path = cmd_line_file_path + '.orig'
1257        if self._android_commands.file_exists(original_cmd_line_file_path):
1258            self._android_commands.run(['shell', 'mv', original_cmd_line_file_path, cmd_line_file_path])
1259        elif self._android_commands.file_exists(cmd_line_file_path):
1260            self._android_commands.run(['shell', 'rm', cmd_line_file_path])
1261        self._created_cmd_line = False
1262
1263    def _command_from_driver_input(self, driver_input):
1264        command = super(ChromiumAndroidDriver, self)._command_from_driver_input(driver_input)
1265        if command.startswith('/'):
1266            fs = self._port._filesystem
1267            # FIXME: what happens if command lies outside of the layout_tests_dir on the host?
1268            relative_test_filename = fs.relpath(command, fs.dirname(self._port.layout_tests_dir()))
1269            command = DEVICE_WEBKIT_BASE_DIR + relative_test_filename
1270        return command
1271
1272    def _read_prompt(self, deadline):
1273        last_char = ''
1274        while True:
1275            current_char = self._server_process.read_stdout(deadline, 1)
1276            if current_char == ' ':
1277                if last_char in ('#', '$'):
1278                    return
1279            last_char = current_char
1280