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 itertools
30import logging
31import os
32import posixpath
33import re
34import sys
35import threading
36import time
37
38from blinkpy.common import exit_codes
39from blinkpy.common.path_finder import RELATIVE_WEB_TESTS
40from blinkpy.common.path_finder import WEB_TESTS_LAST_COMPONENT
41from blinkpy.common.path_finder import get_chromium_src_dir
42from blinkpy.common.system.executive import ScriptError
43from blinkpy.common.system.profiler import SingleFileOutputProfiler
44from blinkpy.web_tests.breakpad.dump_reader_multipart import DumpReaderAndroid
45from blinkpy.web_tests.models import test_run_results
46from blinkpy.web_tests.port import base
47from blinkpy.web_tests.port import linux
48from blinkpy.web_tests.port import driver
49from blinkpy.web_tests.port import factory
50from blinkpy.web_tests.port import server_process
51
52
53# These are stub globals used for android-specific modules. We
54# don't import them unless we actually need a real Android port object,
55# in order to not have the dependency on all of the android and catapult
56# modules in non-Android ports.
57# pylint: disable=invalid-name
58battery_utils = None
59device_errors = None
60device_utils = None
61devil_chromium = None
62devil_env = None
63intent = None
64perf_control = None
65# pylint: enable=invalid-name
66
67_friendly_browser_names = {
68    'weblayershell': 'weblayer', 'systemwebviewshell': 'webview', 'chromepublic': 'chromium'}
69
70def _import_android_packages_if_necessary():
71    # pylint: disable=invalid-name
72    global battery_utils
73    global device_errors
74    global device_utils
75    global devil_chromium
76    global devil_env
77    global intent
78    global perf_control
79    # pylint: enable=invalid-name
80
81    if not battery_utils:
82        chromium_src_root = get_chromium_src_dir()
83        devil_root = os.path.join(chromium_src_root, 'third_party', 'catapult',
84                                  'devil')
85        build_android_root = os.path.join(chromium_src_root, 'build', 'android')
86        sys.path.insert(0, devil_root)
87        sys.path.insert(0, build_android_root)
88        from importlib import import_module
89
90        battery_utils = import_module('devil.android.battery_utils')
91        devil_env = import_module('devil.devil_env')
92        device_errors = import_module('devil.android.device_errors')
93        device_utils = import_module('devil.android.device_utils')
94        devil_chromium = import_module('devil_chromium')
95        intent = import_module('devil.android.sdk.intent')
96        perf_control = import_module('devil.android.perf.perf_control')
97
98
99_log = logging.getLogger(__name__)
100
101# The root directory for test resources, which has the same structure as the
102# source root directory of Chromium.
103# This path is defined in Chromium's base/test/test_support_android.cc.
104DEVICE_SOURCE_ROOT_DIR = '/data/local/tmp/'
105
106# The web tests directory on device, which has two usages:
107# 1. as a virtual path in file urls that will be bridged to HTTP.
108# 2. pointing to some files that are pushed to the device for tests that
109# don't work on file-over-http (e.g. blob protocol tests).
110DEVICE_WEB_TESTS_DIR = DEVICE_SOURCE_ROOT_DIR + RELATIVE_WEB_TESTS
111
112KPTR_RESTRICT_PATH = '/proc/sys/kernel/kptr_restrict'
113
114# All the test cases are still served to the test runner through file protocol,
115# but we use a file-to-http feature to bridge the file request to host's http
116# server to get the real test files and corresponding resources.
117# See webkit/support/platform_support_android.cc for the other side of this bridge.
118# WEB_TEST_PATH_PREFIX should be matched to the local directory name of
119# web_tests because some tests and test_runner find test root directory
120# with it.
121PERF_TEST_PATH_PREFIX = '/PerformanceTests'
122WEB_TESTS_PATH_PREFIX = '/' + WEB_TESTS_LAST_COMPONENT
123
124# We start netcat processes for each of the three stdio streams. In doing so,
125# we attempt to use ports starting from 10201. This starting value is
126# completely arbitrary.
127FIRST_NETCAT_PORT = 10201
128
129# Timeout in seconds to wait for starting/stopping the driver.
130DRIVER_START_STOP_TIMEOUT_SECS = 10
131
132# Test resources that need to be accessed as files directly.
133# Each item can be the relative path of a directory or a file.
134TEST_RESOURCES_TO_PUSH = [
135    # Blob tests need to access files directly.
136    'editing/pasteboard/resources',
137    'fast/files/resources',
138    'http/tests/local/resources',
139    'http/tests/local/formdata/resources',
140    # User style URLs are accessed as local files in webkit_support.
141    'http/tests/security/resources/cssStyle.css',
142    # Media tests need to access audio/video as files.
143    'media/content',
144    'compositing/resources/video.mp4',
145]
146
147
148class DriverDetails(object):
149
150    def __init__(self, apk):
151        self._apk = apk
152
153    def apk_name(self):
154        return self._apk
155
156
157# Information required when running web tests using content_shell as the test runner.
158class ContentShellDriverDetails(DriverDetails):
159
160    def __init__(self):
161        super(ContentShellDriverDetails, self).__init__('apks/ContentShell.apk')
162
163    def device_cache_directory(self):
164        return self.device_directory() + 'cache/'
165
166    def device_fonts_directory(self):
167        return self.device_directory() + 'fonts/'
168
169    def device_fifo_directory(self):
170        return '/data/data/' + self.package_name() + '/files/'
171
172    def package_name(self):
173        return 'org.chromium.content_shell_apk'
174
175    def activity_name(self):
176        return self.package_name() + '/.ContentShellActivity'
177
178    def library_name(self):
179        return 'libcontent_shell_content_view.so'
180
181    def command_line_file(self):
182        return '/data/local/tmp/content-shell-command-line'
183
184    def device_crash_dumps_directory(self):
185        return '/data/local/tmp/content-shell-crash-dumps'
186
187    def additional_command_line_flags(self, use_breakpad):
188        flags = ['--encode-binary']
189        if use_breakpad:
190            flags.extend(['--enable-crash-reporter', '--crash-dumps-dir=%s' % self.device_crash_dumps_directory()])
191        return flags
192
193    def device_directory(self):
194        return DEVICE_SOURCE_ROOT_DIR + 'content_shell/'
195
196
197# A class to encapsulate device status and information, such as the DeviceUtils
198# instances and whether the device has been set up.
199class AndroidDevices(object):
200    # Percentage of battery a device needs to have in order for it to be considered
201    # to participate in running the web tests.
202    MINIMUM_BATTERY_PERCENTAGE = 30
203
204    def __init__(self, default_devices=None, debug_logging=False):
205        self._usable_devices = []
206        self._default_devices = default_devices
207        self._prepared_devices = []
208        self._debug_logging = debug_logging
209
210    def prepared_devices(self):
211        return self._prepared_devices
212
213    def usable_devices(self, executive):
214        if self._usable_devices:
215            return self._usable_devices
216
217        if self._default_devices:
218            self._usable_devices = [
219                device_utils.DeviceUtils(d)
220                for d in self._default_devices]
221            return self._usable_devices
222
223        devices = device_utils.DeviceUtils.HealthyDevices()
224        self._usable_devices = [
225            d for d in devices
226            if (battery_utils.BatteryUtils(d).GetBatteryInfo().get('level', 0)
227                >= AndroidDevices.MINIMUM_BATTERY_PERCENTAGE
228                and d.IsScreenOn())]
229
230        return self._usable_devices
231
232    def get_device(self, executive, device_index):
233        devices = self.usable_devices(executive)
234        if device_index >= len(devices):
235            raise AssertionError('Device index exceeds number of usable devices.')
236
237        return devices[device_index]
238
239    def is_device_prepared(self, device_serial):
240        return device_serial in self._prepared_devices
241
242    def set_device_prepared(self, device_serial):
243        self._prepared_devices.append(device_serial)
244
245
246class AndroidPort(base.Port):
247    port_name = 'android'
248
249    # Avoid initializing the adb path [worker count]+1 times by storing it as a static member.
250    _adb_path = None
251
252    SUPPORTED_VERSIONS = ('android')
253
254    FALLBACK_PATHS = {'kitkat': ['android'] + linux.LinuxPort.latest_platform_fallback_path()}
255
256    BUILD_REQUIREMENTS_URL = 'https://www.chromium.org/developers/how-tos/android-build-instructions'
257
258    def __init__(self, host, port_name='', apk='', options=None, **kwargs):
259        super(AndroidPort, self).__init__(host, port_name, options=options, **kwargs)
260        self._operating_system = 'android'
261        self._version = 'kitkat'
262        fs = host.filesystem
263        self._local_port = factory.PortFactory(host).get(**kwargs)
264
265        if apk:
266            self._driver_details = DriverDetails(apk)
267            browser_type = fs.splitext(fs.basename(apk))[0].lower()
268            self._browser_type = _friendly_browser_names.get(browser_type, browser_type)
269        else:
270            # The legacy test runner will be used to run web tests on Android.
271            # So we need to initialize several port member variables.
272            _import_android_packages_if_necessary()
273            self._driver_details = ContentShellDriverDetails()
274            self._browser_type = 'content_shell'
275            self._debug_logging = self.get_option('android_logging')
276            self.server_process_constructor = self._android_server_process_constructor
277
278            if not self.get_option('disable_breakpad'):
279                self._dump_reader = DumpReaderAndroid(host, self._build_path())
280
281            # Initialize the AndroidDevices class which tracks available devices.
282            default_devices = None
283            if hasattr(self._options, 'adb_devices') and len(self._options.adb_devices):
284                default_devices = self._options.adb_devices
285
286            self._devices = AndroidDevices(default_devices, self._debug_logging)
287
288            devil_chromium.Initialize(
289                output_directory=self._build_path(),
290                adb_path=self._path_from_chromium_base(
291                    'third_party', 'android_sdk', 'public', 'platform-tools', 'adb'))
292
293            devil_env.config.InitializeLogging(
294                logging.DEBUG
295                if self._debug_logging and self.get_option('debug_rwt_logging')
296                else logging.WARNING)
297
298            prepared_devices = self.get_option('prepared_devices', [])
299            for serial in prepared_devices:
300                self._devices.set_device_prepared(serial)
301
302    def get_platform_tags(self):
303        _sanitize_tag = lambda t: t.replace('_', '-').replace(' ', '-')
304        return frozenset(['android', 'android-' + _sanitize_tag(self._browser_type)])
305
306    def default_smoke_test_only(self):
307        return True
308
309    def additional_driver_flags(self):
310        return super(AndroidPort, self).additional_driver_flags() + \
311            self._driver_details.additional_command_line_flags(use_breakpad=not self.get_option('disable_breakpad'))
312
313    def default_timeout_ms(self):
314        # Android platform has less computing power than desktop platforms.
315        # Using 10 seconds allows us to pass most slow tests which are not
316        # marked as slow tests on desktop platforms.
317        return 10 * 1000
318
319    def driver_stop_timeout(self):
320        # The driver doesn't respond to closing stdin, so we might as well stop the driver immediately.
321        return 0.0
322
323    def default_child_processes(self):
324        usable_devices = self._devices.usable_devices(self._executive)
325        if not usable_devices:
326            raise test_run_results.TestRunException(exit_codes.NO_DEVICES_EXIT_STATUS,
327                                                    'Unable to find any attached Android devices.')
328        return len(usable_devices)
329
330    def check_build(self, needs_http, printer):
331        exit_status = super(AndroidPort, self).check_build(needs_http, printer)
332        if exit_status:
333            return exit_status
334
335        return self._check_devices(printer)
336
337    def _check_devices(self, printer):
338
339        # Push the executables and other files to the devices; doing this now
340        # means we can do this in parallel in the manager process and not mix
341        # this in with starting and stopping workers.
342
343        lock = threading.Lock()
344
345        def log_safely(msg, throttled=True):
346            if throttled:
347                callback = printer.write_throttled_update
348            else:
349                callback = printer.write_update
350            with lock:
351                callback('%s' % msg)
352
353        def _setup_device_impl(device):
354            log_safely('preparing device', throttled=False)
355
356            if self._devices.is_device_prepared(device.serial):
357                return
358
359            device.EnableRoot()
360            perf_control.PerfControl(device).SetPerfProfilingMode()
361
362            # Required by webkit_support::GetWebKitRootDirFilePath().
363            # Other directories will be created automatically by adb push.
364            device.RunShellCommand(
365                ['mkdir', '-p', DEVICE_SOURCE_ROOT_DIR + 'chrome'],
366                check_return=True)
367
368            # Allow the test driver to get full read and write access to the directory on the device,
369            # as well as for the FIFOs. We'll need a world writable directory.
370            device.RunShellCommand(
371                ['mkdir', '-p', self._driver_details.device_directory()],
372                check_return=True)
373
374            # Make sure that the disk cache on the device resets to a clean state.
375            device.RunShellCommand(
376                ['rm', '-rf', self._driver_details.device_cache_directory()],
377                check_return=True)
378
379            device.EnableRoot()
380            perf_control.PerfControl(device).SetPerfProfilingMode()
381
382            # Required by webkit_support::GetWebKitRootDirFilePath().
383            # Other directories will be created automatically by adb push.
384            device.RunShellCommand(
385                ['mkdir', '-p', DEVICE_SOURCE_ROOT_DIR + 'chrome'],
386                check_return=True)
387
388            # Allow the test driver to get full read and write access to the directory on the device,
389            # as well as for the FIFOs. We'll need a world writable directory.
390            device.RunShellCommand(
391                ['mkdir', '-p', self._driver_details.device_directory()],
392                check_return=True)
393
394            # Make sure that the disk cache on the device resets to a clean state.
395            device.RunShellCommand(
396                ['rm', '-rf', self._driver_details.device_cache_directory()],
397                check_return=True)
398
399            device_path = lambda *p: posixpath.join(
400                self._driver_details.device_directory(), *p)
401
402            device.Install(self._path_to_driver())
403
404            # Build up a list of what we want to push, including:
405            host_device_tuples = []
406
407            # - the custom font files
408            # TODO(sergeyu): Rename these files, they can be used on platforms
409            # other than Android.
410            host_device_tuples.append(
411                (self._build_path('test_fonts/android_main_fonts.xml'),
412                 device_path('android_main_fonts.xml')))
413            host_device_tuples.append(
414                (self._build_path('test_fonts/android_fallback_fonts.xml'),
415                 device_path('android_fallback_fonts.xml')))
416            for font_file in self._get_font_files():
417                host_device_tuples.append(
418                    (font_file, device_path('fonts', os.path.basename(font_file))))
419
420            # - the test resources
421            host_device_tuples.extend(
422                (self.host.filesystem.join(self.web_tests_dir(), resource),
423                 posixpath.join(DEVICE_WEB_TESTS_DIR, resource))
424                for resource in TEST_RESOURCES_TO_PUSH)
425
426            # ... and then push them to the device.
427            device.PushChangedFiles(host_device_tuples)
428
429            device.RunShellCommand(
430                ['mkdir', '-p', self._driver_details.device_fifo_directory()],
431                check_return=True)
432
433            device.RunShellCommand(
434                ['chmod', '-R', '777', self._driver_details.device_directory()],
435                check_return=True)
436            device.RunShellCommand(
437                ['chmod', '-R', '777', self._driver_details.device_fifo_directory()],
438                check_return=True)
439
440            # Mark this device as having been set up.
441            self._devices.set_device_prepared(device.serial)
442
443            log_safely('device prepared', throttled=False)
444
445        def setup_device(device):
446            try:
447                _setup_device_impl(device)
448            except (ScriptError,
449                    driver.DeviceFailure,
450                    device_errors.CommandFailedError,
451                    device_errors.CommandTimeoutError,
452                    device_errors.DeviceUnreachableError) as error:
453                with lock:
454                    _log.warning('[%s] failed to prepare_device: %s', device.serial, error)
455
456        devices = self._devices.usable_devices(self.host.executive)
457        device_utils.DeviceUtils.parallel(devices).pMap(setup_device)
458
459        if not self._devices.prepared_devices():
460            _log.error('Could not prepare any devices for testing.')
461            return exit_codes.NO_DEVICES_EXIT_STATUS
462        return exit_codes.OK_EXIT_STATUS
463
464    def setup_test_run(self):
465        super(AndroidPort, self).setup_test_run()
466
467        # By setting this on the options object, we can propagate the list
468        # of prepared devices to the workers (it is read in __init__()).
469        if self._devices._prepared_devices:
470            self._options.prepared_devices = self._devices.prepared_devices()
471        else:
472            # We were called with --no-build, so assume the devices are up to date.
473            self._options.prepared_devices = [d.get_serial() for d in self._devices.usable_devices(self.host.executive)]
474
475    def num_workers(self, requested_num_workers):
476        return min(len(self._options.prepared_devices), requested_num_workers)
477
478    def check_sys_deps(self):
479        # _get_font_files() will throw if any of the required fonts is missing.
480        self._get_font_files()
481        return exit_codes.OK_EXIT_STATUS
482
483    def requires_http_server(self):
484        """Chromium Android runs tests on devices, and uses the HTTP server to
485        serve the actual web tests to the test driver.
486        """
487        return True
488
489    def start_http_server(self, additional_dirs, number_of_drivers):
490        additional_dirs[PERF_TEST_PATH_PREFIX] = self._perf_tests_dir()
491        additional_dirs[WEB_TESTS_PATH_PREFIX] = self.web_tests_dir()
492        super(AndroidPort, self).start_http_server(additional_dirs, number_of_drivers)
493
494    def create_driver(self, worker_number, no_timeout=False):
495        return ChromiumAndroidDriver(self, worker_number,
496                                     driver_details=self._driver_details,
497                                     android_devices=self._devices,
498                                     # Force no timeout to avoid test driver timeouts before NRWT.
499                                     no_timeout=True)
500
501    def driver_cmd_line(self):
502        # Override to return the actual test driver's command line.
503        return self.create_driver(0)._android_driver_cmd_line([])
504
505    def clobber_old_port_specific_results(self):
506        if not self.get_option('disable_breakpad'):
507            self._dump_reader.clobber_old_results()
508
509    # Overridden protected methods.
510
511    def _build_path(self, *comps):
512        return self._local_port._build_path(*comps)
513
514    def _build_path_with_target(self, target, *comps):
515        return self._local_port._build_path_with_target(target, *comps)
516
517    def path_to_apache(self):
518        return self._local_port.path_to_apache()
519
520    def path_to_apache_config_file(self):
521        return self._local_port.path_to_apache_config_file()
522
523    def _path_to_driver(self, target=None):
524        return self._build_path_with_target(target, self._driver_details.apk_name())
525
526    def _path_to_image_diff(self):
527        return self._local_port._path_to_image_diff()
528
529    def _shut_down_http_server(self, pid):
530        return self._local_port._shut_down_http_server(pid)
531
532    def _driver_class(self):
533        return ChromiumAndroidDriver
534
535    # Local private methods.
536
537    @staticmethod
538    def _android_server_process_constructor(port, server_name, cmd_line, env=None, more_logging=False):
539        return server_process.ServerProcess(port, server_name, cmd_line, env,
540                                            treat_no_data_as_crash=True, more_logging=more_logging)
541
542
543class AndroidPerf(SingleFileOutputProfiler):
544    _cached_perf_host_path = None
545    _have_searched_for_perf_host = False
546
547    def __init__(self, host, executable_path, output_dir, device, symfs_path, kallsyms_path, identifier=None):
548        super(AndroidPerf, self).__init__(host, executable_path, output_dir, 'data', identifier)
549        self._device = device
550        self._perf_process = None
551        self._symfs_path = symfs_path
552        self._kallsyms_path = kallsyms_path
553
554    def check_configuration(self):
555        # Check that perf is installed
556        if not self._device.PathExists('/system/bin/perf'):
557            _log.error('Cannot find /system/bin/perf on device %s', self._device.serial)
558            return False
559
560        # Check that the device is a userdebug build (or at least has the necessary libraries).
561        if self._device.build_type != 'userdebug':
562            _log.error('Device %s is not flashed with a userdebug build of Android', self._device.serial)
563            return False
564
565        # FIXME: Check that the binary actually is perf-able (has stackframe pointers)?
566        # objdump -s a function and make sure it modifies the fp?
567        # Instruct users to rebuild after export GYP_DEFINES="profiling=1 $GYP_DEFINES"
568        return True
569
570    def print_setup_instructions(self):
571        _log.error("""
572perf on android requires a 'userdebug' build of Android, see:
573http://source.android.com/source/building-devices.html"
574
575The perf command can be built from:
576https://android.googlesource.com/platform/external/linux-tools-perf/
577and requires libefl, libebl, libdw, and libdwfl available in:
578https://android.googlesource.com/platform/external/elfutils/
579
580The test driver must be built with profiling=1, make sure you've done:
581export GYP_DEFINES="profiling=1 $GYP_DEFINES"
582update-webkit --chromium-android
583build-webkit --chromium-android
584
585Googlers should read:
586http://goto.google.com/cr-android-perf-howto
587""")
588
589    def attach_to_pid(self, pid):
590        assert pid
591        assert self._perf_process is None
592        # FIXME: This can't be a fixed timeout!
593        cmd = [self._device.adb.GetAdbPath(), '-s', self._device.serial,
594               'shell', 'perf', 'record', '-g', '-p', pid, 'sleep', 30]
595        self._perf_process = self._host.executive.popen(cmd)
596
597    def _perf_version_string(self, perf_path):
598        try:
599            return self._host.executive.run_command([perf_path, '--version'])
600        except:
601            return None
602
603    def _find_perfhost_binary(self):
604        perfhost_version = self._perf_version_string('perfhost_linux')
605        if perfhost_version:
606            return 'perfhost_linux'
607        perf_version = self._perf_version_string('perf')
608        if perf_version:
609            return 'perf'
610        return None
611
612    def _perfhost_path(self):
613        if self._have_searched_for_perf_host:
614            return self._cached_perf_host_path
615        self._have_searched_for_perf_host = True
616        self._cached_perf_host_path = self._find_perfhost_binary()
617        return self._cached_perf_host_path
618
619    def _first_ten_lines_of_profile(self, perf_output):
620        match = re.search(r"^#[^\n]*\n((?: [^\n]*\n){1,10})", perf_output, re.MULTILINE)
621        return match.group(1) if match else None
622
623    def profile_after_exit(self):
624        perf_exitcode = self._perf_process.wait()
625        if perf_exitcode != 0:
626            _log.debug("Perf failed (exit code: %i), can't process results.", perf_exitcode)
627            return
628
629        self._device.PullFile('/data/perf.data', self._output_path)
630
631        perfhost_path = self._perfhost_path()
632        perfhost_report_command = [
633            'report',
634            '--input', self._output_path,
635            '--symfs', self._symfs_path,
636            '--kallsyms', self._kallsyms_path,
637        ]
638        if perfhost_path:
639            perfhost_args = [perfhost_path] + perfhost_report_command + ['--call-graph', 'none']
640            perf_output = self._host.executive.run_command(perfhost_args)
641            # We could save off the full -g report to a file if users found that useful.
642            _log.debug(self._first_ten_lines_of_profile(perf_output))
643        else:
644            _log.debug("""
645Failed to find perfhost_linux binary, can't process samples from the device.
646
647perfhost_linux can be built from:
648https://android.googlesource.com/platform/external/linux-tools-perf/
649also, modern versions of perf (available from apt-get install goobuntu-kernel-tools-common)
650may also be able to process the perf.data files from the device.
651
652Googlers should read:
653http://goto.google.com/cr-android-perf-howto
654for instructions on installing pre-built copies of perfhost_linux
655http://crbug.com/165250 discusses making these pre-built binaries externally available.
656""")
657
658        perfhost_display_patch = perfhost_path if perfhost_path else 'perfhost_linux'
659        _log.debug('To view the full profile, run:')
660        _log.debug(' '.join([perfhost_display_patch] + perfhost_report_command))
661
662
663class ChromiumAndroidDriver(driver.Driver):
664
665    def __init__(self, port, worker_number, driver_details, android_devices, no_timeout=False):
666        super(ChromiumAndroidDriver, self).__init__(port, worker_number, no_timeout)
667        self._write_stdin_process = None
668        self._read_stdout_process = None
669        self._read_stderr_process = None
670        self._original_kptr_restrict = None
671
672        self._android_devices = android_devices
673        self._device = android_devices.get_device(port._executive, worker_number)  # pylint: disable=protected-access
674        self._driver_details = driver_details
675        self._debug_logging = self._port._debug_logging
676        self._created_cmd_line = False
677        self._device_failed = False
678
679        # FIXME: If we taught ProfileFactory about "target" devices we could
680        # just use the logic in Driver instead of duplicating it here.
681        if self._port.get_option('profile'):
682            # FIXME: This should be done once, instead of per-driver!
683            symfs_path = self._find_or_create_symfs()
684            kallsyms_path = self._update_kallsyms_cache(symfs_path)
685            # FIXME: We should pass this some sort of "Bridge" object abstraction around ADB instead of a path/device pair.
686            self._profiler = AndroidPerf(self._port.host, self._port._path_to_driver(), self._port.artifacts_directory(),
687                                         self._device, symfs_path, kallsyms_path)
688            # FIXME: This is a layering violation and should be moved to Port.check_sys_deps
689            # once we have an abstraction around an adb_path/device_serial pair to make it
690            # easy to make these class methods on AndroidPerf.
691            if not self._profiler.check_configuration():
692                self._profiler.print_setup_instructions()
693                sys.exit(1)
694        else:
695            self._profiler = None
696
697    def __del__(self):
698        self._teardown_performance()
699        self._clean_up_cmd_line()
700        super(ChromiumAndroidDriver, self).__del__()
701
702    def _update_kallsyms_cache(self, output_dir):
703        kallsyms_name = '%s-kallsyms' % self._device.serial
704        kallsyms_cache_path = self._port.host.filesystem.join(output_dir, kallsyms_name)
705
706        self._device.EnableRoot()
707
708        saved_kptr_restrict = self._device.ReadFile(KPTR_RESTRICT_PATH).strip()
709        self._device.WriteFile(KPTR_RESTRICT_PATH, '0')
710
711        _log.debug('Updating kallsyms file (%s) from device', kallsyms_cache_path)
712        self._device.PullFile('/proc/kallsysm', kallsyms_cache_path)
713        self._device.WriteFile(KPTR_RESTRICT_PATH, saved_kptr_restrict)
714
715        return kallsyms_cache_path
716
717    def _find_or_create_symfs(self):
718        env = self._port.host.environ.copy()
719        fs = self._port.host.filesystem
720
721        if 'ANDROID_SYMFS' in env:
722            symfs_path = env['ANDROID_SYMFS']
723        else:
724            symfs_path = fs.join(self._port.artifacts_directory(), 'symfs')
725            _log.debug('ANDROID_SYMFS not set, using %s', symfs_path)
726
727        # find the installed path, and the path of the symboled built library
728        # FIXME: We should get the install path from the device!
729        symfs_library_path = fs.join(symfs_path, 'data/app-lib/%s-1/%s' %
730                                     (self._driver_details.package_name(), self._driver_details.library_name()))
731        built_library_path = self._port._build_path('lib', self._driver_details.library_name())
732        assert fs.exists(built_library_path)
733
734        # FIXME: Ideally we'd check the sha1's first and make a soft-link instead
735        # of copying (since we probably never care about windows).
736        _log.debug('Updating symfs library (%s) from built copy (%s)', symfs_library_path, built_library_path)
737        fs.maybe_make_directory(fs.dirname(symfs_library_path))
738        fs.copyfile(built_library_path, symfs_library_path)
739
740        return symfs_path
741
742    def _log_error(self, message):
743        _log.error('[%s] %s', self._device.serial, message)
744
745    def _log_warning(self, message):
746        _log.warning('[%s] %s', self._device.serial, message)
747
748    def _log_debug(self, message):
749        if self._debug_logging:
750            _log.debug('[%s] %s', self._device.serial, message)
751
752    def _abort(self, message):
753        self._device_failed = True
754        raise driver.DeviceFailure('[%s] %s' % (self._device.serial, message))
755
756    def _get_last_stacktrace(self):
757        try:
758            tombstones = self._device.RunShellCommand(
759                'ls -n /data/tombstones/tombstone_*',
760                check_return=True, shell=True)
761        except device_errors.CommandFailedError as exc:
762            # FIXME: crbug.com/321489 ... figure out why we sometimes get
763            #   permission denied.
764            self._log_error('The driver crashed, but we were unable to read a tombstone: %s' % str(exc))
765            return ''
766
767        last_tombstone = None
768        for tombstone in tombstones:
769            # Format of fields:
770            # 0          1      2      3     4          5     6
771            # permission uid    gid    size  date       time  filename
772            # -rw------- 1000   1000   45859 2011-04-13 06:00 tombstone_00
773            fields = tombstone.split()
774            if len(fields) != 7:
775                self._log_warning("unexpected line in tombstone output, skipping: '%s'" % tombstone)
776                continue
777
778            if not last_tombstone or fields[4] + fields[5] >= last_tombstone[4] + last_tombstone[5]:
779                last_tombstone = fields
780            else:
781                break
782
783        if not last_tombstone:
784            self._log_error('The driver crashed, but we could not find any valid tombstone!')
785            return ''
786
787        # Use Android tool vendor/google/tools/stack to convert the raw
788        # stack trace into a human readable format, if needed.
789        # It takes a long time, so don't do it here.
790        tombstone_contents = self._device.ReadFile(
791            '/data/tombstones/%s' % last_tombstone[6])
792        return '%s\n%s' % (' '.join(last_tombstone), tombstone_contents)
793
794    def _get_logcat(self):
795        return '\n'.join(self._device.adb.Logcat(dump=True, logcat_format='threadtime'))
796
797    def _teardown_performance(self):
798        perf_control.PerfControl(self._device).SetDefaultPerfMode()
799
800    def _get_crash_log(self, stdout, stderr, newer_than):
801        if not stdout:
802            stdout = ''
803        stdout += '********* [%s] Logcat:\n%s' % (self._device.serial, self._get_logcat())
804        if not stderr:
805            stderr = ''
806        stderr += '********* [%s] Tombstone file:\n%s' % (self._device.serial, self._get_last_stacktrace())
807
808        if not self._port.get_option('disable_breakpad'):
809            crashes = self._pull_crash_dumps_from_device()
810            for crash in crashes:
811                stack = self._port._dump_reader._get_stack_from_dump(crash)  # pylint: disable=protected-access
812                try:
813                  stack_str = stack.encode('ascii', 'replace')
814                except Exception as e:
815                  stack_str = '<No Stack> (%s)' % e
816                stderr += '********* [%s] breakpad minidump %s:\n%s' % (
817                    self._port.host.filesystem.basename(crash),
818                    self._device.serial,
819                    stack_str)
820
821        return super(ChromiumAndroidDriver, self)._get_crash_log(
822            stdout, stderr, newer_than)
823
824    def cmd_line(self, per_test_args):
825        # The returned command line is used to start _server_process. In our case, it's an interactive 'adb shell'.
826        # The command line passed to the driver process is returned by _driver_cmd_line() instead.
827        return [self._device.adb.GetAdbPath(), '-s', self._device.serial, 'shell']
828
829    def _android_driver_cmd_line(self, per_test_args):
830        return driver.Driver.cmd_line(self, per_test_args)
831
832    @staticmethod
833    def _loop_with_timeout(condition, timeout_secs):
834        deadline = time.time() + timeout_secs
835        while time.time() < deadline:
836            if condition():
837                return True
838        return False
839
840    def start(self, per_test_args, deadline):
841        # We override the default start() so that we can call _android_driver_cmd_line()
842        # instead of cmd_line().
843        new_cmd_line = self._android_driver_cmd_line(per_test_args)
844
845        # Since _android_driver_cmd_line() is different than cmd_line() we need to provide
846        # our own mechanism for detecting when the process should be stopped.
847        if self._current_cmd_line is None:
848            self._current_android_cmd_line = None
849        if new_cmd_line != self._current_android_cmd_line:
850            self.stop()
851        self._current_android_cmd_line = new_cmd_line
852
853        super(ChromiumAndroidDriver, self).start(per_test_args, deadline)
854
855    def _start(self, per_test_args):
856        if not self._android_devices.is_device_prepared(self._device.serial):
857            raise driver.DeviceFailure('%s is not prepared in _start()' % self._device.serial)
858
859        for retries in range(3):
860            try:
861                if self._start_once(per_test_args):
862                    return
863            except ScriptError as error:
864                self._abort('ScriptError("%s") in _start()' % error)
865
866            self._log_error('Failed to start the content_shell application. Retries=%d. Log:\n%s' % (retries, self._get_logcat()))
867            self.stop()
868            time.sleep(2)
869        self._abort('Failed to start the content_shell application multiple times. Giving up.')
870
871    def _start_once(self, per_test_args):
872        super(ChromiumAndroidDriver, self)._start(per_test_args, wait_for_ready=False)
873
874        self._device.adb.Logcat(clear=True)
875
876        self._create_device_crash_dumps_directory()
877
878        # Read back the shell prompt to ensure adb shell is ready.
879        deadline = time.time() + DRIVER_START_STOP_TIMEOUT_SECS
880        self._server_process.start()
881        self._read_prompt(deadline)
882        self._log_debug('Interactive shell started')
883
884        # Start a netcat process to which the test driver will connect to write stdout.
885        self._read_stdout_process, stdout_port = self._start_netcat(
886            'ReadStdout', read_from_stdin=False)
887        self._log_debug('Redirecting stdout to port %d' % stdout_port)
888
889        # Start a netcat process to which the test driver will connect to write stderr.
890        self._read_stderr_process, stderr_port = self._start_netcat(
891            'ReadStderr', first_port=stdout_port + 1, read_from_stdin=False)
892        self._log_debug('Redirecting stderr to port %d' % stderr_port)
893
894        # Start a netcat process to which the test driver will connect to read stdin.
895        self._write_stdin_process, stdin_port = self._start_netcat(
896            'WriteStdin', first_port=stderr_port + 1)
897        self._log_debug('Redirecting stdin to port %d' % stdin_port)
898
899        # Combine the stdin, stdout, and stderr pipes into self._server_process.
900        self._replace_server_process_streams()
901
902        # We delay importing forwarder as long as possible because it uses fcntl,
903        # which isn't available on windows.
904        from devil.android import forwarder
905
906        self._log_debug('Starting forwarder')
907        forwarder.Forwarder.Map(
908            [(p, p) for p in base.Port.SERVER_PORTS],
909            self._device)
910        forwarder.Forwarder.Map(
911            [(forwarder.DYNAMIC_DEVICE_PORT, p)
912             for p in (stdout_port, stderr_port, stdin_port)],
913            self._device)
914
915        cmd_line_file_path = self._driver_details.command_line_file()
916        original_cmd_line_file_path = cmd_line_file_path + '.orig'
917        if (self._device.PathExists(cmd_line_file_path)
918                and not self._device.PathExists(original_cmd_line_file_path)):
919            # We check for both the normal path and the backup because we do not want to step
920            # on the backup. Otherwise, we'd clobber the backup whenever we changed the
921            # command line during the run.
922            self._device.RunShellCommand(
923                ['mv', cmd_line_file_path, original_cmd_line_file_path],
924                check_return=True)
925
926        stream_port_args = [
927            '--android-stderr-port=%s' % forwarder.Forwarder.DevicePortForHostPort(stderr_port),
928            '--android-stdin-port=%s' % forwarder.Forwarder.DevicePortForHostPort(stdin_port),
929            '--android-stdout-port=%s' % forwarder.Forwarder.DevicePortForHostPort(stdout_port),
930        ]
931        cmd_line_contents = self._android_driver_cmd_line(per_test_args + stream_port_args)
932        self._device.WriteFile(
933            self._driver_details.command_line_file(),
934            ' '.join(cmd_line_contents))
935        self._log_debug('Command-line file contents: %s' % ' '.join(cmd_line_contents))
936        self._created_cmd_line = True
937
938        try:
939            self._device.StartActivity(
940                intent.Intent(
941                    component=self._driver_details.activity_name(),
942                    extras={'RunInSubThread': None}))
943        except device_errors.CommandFailedError as exc:
944            self._log_error('Failed to start the content_shell application. Exception:\n' + str(exc))
945            return False
946
947        # The test driver might crash during startup.
948        if not self._wait_for_server_process_output(self._server_process, deadline, '#READY'):
949            return False
950
951        self._log_debug('content_shell is ready')
952        return True
953
954    def _create_device_crash_dumps_directory(self):
955        self._device.RunShellCommand(
956            ['rm', '-rf', self._driver_details.device_crash_dumps_directory()],
957            check_return=True)
958        self._device.RunShellCommand(
959            ['mkdir', self._driver_details.device_crash_dumps_directory()],
960            check_return=True)
961        self._device.RunShellCommand(
962            ['chmod', '-R', '777', self._driver_details.device_crash_dumps_directory()],
963            check_return=True)
964
965    def _start_netcat(self, server_name, first_port=FIRST_NETCAT_PORT, read_from_stdin=True):
966        for i in itertools.count(first_port, 65536):
967            nc_cmd = ['nc', '-l', str(i)]
968            if not read_from_stdin:
969                nc_cmd.append('-d')
970            proc = self._port.server_process_constructor(self._port, server_name, nc_cmd)
971            proc.start()
972            self._port.host.executive.wait_limited(proc.pid(), limit_in_seconds=1)
973            if self._port.host.executive.check_running_pid(proc.pid()):
974                return (proc, i)
975
976        raise Exception(
977            'Unable to find a port for netcat process %s' % server_name)
978
979    def _replace_server_process_streams(self):
980        # pylint: disable=protected-access
981        self._server_process.replace_input(
982            self._write_stdin_process._proc.stdin)
983        self._server_process.replace_outputs(
984            self._read_stdout_process._proc.stdout,
985            self._read_stderr_process._proc.stdout)
986
987    def _pid_on_target(self):
988        pids = self._device.GetPids(self._driver_details.package_name())
989        return pids.get(self._driver_details.package_name())
990
991    def stop(self):
992        if not self._device_failed:
993            # Do not try to stop the application if there's something wrong with the device; adb may hang.
994            # FIXME: crbug.com/305040. Figure out if it's really hanging (and why).
995            self._device.ForceStop(self._driver_details.package_name())
996
997        if self._write_stdin_process:
998            self._write_stdin_process.kill()
999            self._write_stdin_process = None
1000
1001        if self._read_stdout_process:
1002            self._read_stdout_process.kill()
1003            self._read_stdout_process = None
1004
1005        if self._read_stderr_process:
1006            self._read_stderr_process.kill()
1007            self._read_stderr_process = None
1008
1009        # We delay importing forwarder as long as possible because it uses fcntl,
1010        # which isn't available on windows.
1011        from devil.android import forwarder
1012
1013        forwarder.Forwarder.KillDevice(self._device)
1014        forwarder.Forwarder.KillHost()
1015
1016        super(ChromiumAndroidDriver, self).stop()
1017
1018        self._clean_up_cmd_line()
1019
1020    def _pull_crash_dumps_from_device(self):
1021        result = []
1022        if not self._device.PathExists(self._driver_details.device_crash_dumps_directory()):
1023            return result
1024        dumps = self._device.ListDirectory(
1025            self._driver_details.device_crash_dumps_directory())
1026        for dump in dumps:
1027            device_dump = '%s/%s' % (self._driver_details.device_crash_dumps_directory(), dump)
1028            local_dump = self._port.host.filesystem.join(
1029                self._port._dump_reader.crash_dumps_directory(), dump)  # pylint: disable=protected-access
1030
1031            # FIXME: crbug.com/321489. Figure out why these commands would fail ...
1032            try:
1033                self._device.RunShellCommand(
1034                    ['chmod', '777', device_dump], check_return=True)
1035                self._device.PullFile(device_dump, local_dump)
1036                self._device.RunShellCommand(
1037                    ['rm', '-f', device_dump], check_return=True)
1038            except device_errors.CommandFailedError:
1039                pass
1040
1041            if self._port.host.filesystem.exists(local_dump):
1042                result.append(local_dump)
1043        return result
1044
1045    def _clean_up_cmd_line(self):
1046        if not self._created_cmd_line:
1047            return
1048
1049        cmd_line_file_path = self._driver_details.command_line_file()
1050        original_cmd_line_file_path = cmd_line_file_path + '.orig'
1051        if self._device.PathExists(original_cmd_line_file_path):
1052            self._device.RunShellCommand(
1053                ['mv', original_cmd_line_file_path, cmd_line_file_path],
1054                check_return=True)
1055        elif self._device.PathExists(cmd_line_file_path):
1056            self._device.RunShellCommand(
1057                ['rm', cmd_line_file_path],
1058                check_return=True)
1059        self._created_cmd_line = False
1060
1061    def _command_from_driver_input(self, driver_input):
1062        command = super(ChromiumAndroidDriver, self)._command_from_driver_input(driver_input)
1063        if command.startswith('/'):
1064            command = 'http://127.0.0.1:8000' + WEB_TESTS_PATH_PREFIX + \
1065                '/' + self._port.relative_test_filename(command)
1066        return command
1067
1068    def _read_prompt(self, deadline):
1069        last_char = ''
1070        while True:
1071            current_char = self._server_process.read_stdout(deadline, 1)
1072            if current_char == ' ':
1073                if last_char in ('#', '$'):
1074                    return
1075            last_char = current_char
1076