| #!/usr/bin/env vpython |
| # Copyright 2019 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Runs Web Platform Tests (WPT) on Android browsers. |
| |
| This script supports running tests on the Chromium Waterfall by mapping isolated |
| script flags to WPT flags. |
| |
| It is also useful for local reproduction by performing APK installation and |
| configuring the browser to resolve test hosts. Be sure to invoke this |
| executable directly rather than using python run_android_wpt.py so that |
| WPT dependencies in Chromium vpython are found. |
| |
| If you need more advanced test control, please use the runner located at |
| //third_party/blink/web_tests/external/wpt/wpt. |
| |
| Here's the mapping [isolate script flag] : [wpt flag] |
| --isolated-script-test-output : --log-chromium |
| --total-shards : --total-chunks |
| --shard-index : -- this-chunk |
| """ |
| |
| # TODO(aluo): Combine or factor out commons parts with run_wpt_tests.py script. |
| |
| import argparse |
| import contextlib |
| import json |
| import logging |
| import os |
| import sys |
| |
| import common |
| |
| logger = logging.getLogger(__name__) |
| |
| SRC_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) |
| |
| BUILD_ANDROID = os.path.join(SRC_DIR, 'build', 'android') |
| |
| if BUILD_ANDROID not in sys.path: |
| sys.path.append(BUILD_ANDROID) |
| |
| import devil_chromium |
| |
| from devil import devil_env |
| from devil.android import apk_helper |
| from devil.android import device_utils |
| from devil.android import flag_changer |
| from devil.android.tools import system_app |
| from devil.android.tools import webview_app |
| |
| |
| DEFAULT_WEBDRIVER = os.path.join(SRC_DIR, 'chrome', 'test', 'chromedriver', |
| 'cipd', 'linux', 'chromedriver') |
| DEFAULT_WPT = os.path.join(SRC_DIR, 'third_party', 'blink', 'web_tests', |
| 'external', 'wpt', 'wpt') |
| |
| SYSTEM_WEBVIEW_SHELL_PKG = 'org.chromium.webview_shell' |
| |
| # This avoids having to update the hosts file on device. |
| HOST_RESOLVER_ARGS = ['--host-resolver-rules=MAP nonexistent.*.test ~NOTFOUND,' |
| ' MAP *.test 127.0.0.1'] |
| |
| # Browsers on debug and eng devices read command-line-flags from special files |
| # during startup. |
| FLAGS_FILE_MAP = {'android_webview': 'webview-command-line', |
| 'chrome_android': 'chrome-command-line'} |
| |
| |
| class PassThroughArgs(argparse.Action): |
| pass_through_args = [] |
| def __call__(self, parser, namespace, values, option_string=None): |
| if option_string: |
| if self.nargs == 0: |
| self.add_unique_pass_through_arg(option_string) |
| elif self.nargs is None: |
| self.add_unique_pass_through_arg('{}={}'.format(option_string, values)) |
| else: |
| raise ValueError("nargs {} not supported: {} {}".format( |
| self.nargs, option_string, values)) |
| |
| @classmethod |
| def add_unique_pass_through_arg(cls, arg): |
| if arg not in cls.pass_through_args: |
| cls.pass_through_args.append(arg) |
| |
| |
| class WPTAndroidAdapter(common.BaseIsolatedScriptArgsAdapter): |
| def __init__(self): |
| self.pass_through_wpt_args = [] |
| self.pass_through_binary_args = [] |
| super(WPTAndroidAdapter, self).__init__() |
| |
| def generate_test_output_args(self, output): |
| return ['--log-chromium', output] |
| |
| def generate_sharding_args(self, total_shards, shard_index): |
| return ['--total-chunks=%d' % total_shards, |
| # shard_index is 0-based but WPT's this-chunk to be 1-based |
| '--this-chunk=%d' % (shard_index + 1)] |
| |
| @property |
| def rest_args(self): |
| rest_args = super(WPTAndroidAdapter, self).rest_args |
| |
| # Here we add all of the arguments required to run WPT tests on Android. |
| rest_args.extend([self.options.wpt_path]) |
| |
| # vpython has packages needed by wpt, so force it to skip the setup |
| rest_args.extend(["--venv=../../", "--skip-venv-setup"]) |
| |
| rest_args.extend(["run", |
| "--test-type=" + self.options.test_type, |
| self.options.product, |
| "--webdriver-binary", |
| self.options.webdriver_binary, |
| "--headless", |
| "--no-pause-after-test", |
| "--no-capture-stdio", |
| "--no-manifest-download", |
| "--no-fail-on-unexpected", |
| #TODO(aluo): Tune this as tests are stabilized |
| "--timeout-multiplier", |
| "0.25", |
| ]) |
| |
| # Default to the apk's package name for chrome_android |
| if not self.options.package_name: |
| if self.options.product == 'chrome_android': |
| if self.options.apk: |
| pkg = apk_helper.GetPackageName(self.options.apk) |
| logger.info("Defaulting --package-name to that of the apk: %s", pkg) |
| rest_args.extend(['--package-name', pkg]) |
| else: |
| raise Exception('chrome_android requires --package-name or --apk.') |
| else: |
| rest_args.extend(['--package-name', self.options.package_name]) |
| |
| if self.options.verbose >= 3: |
| rest_args.extend(["--log-mach=-", "--log-mach-level=debug", |
| "--log-mach-verbose"]) |
| |
| if self.options.verbose >= 4: |
| rest_args.extend(['--webdriver-arg=--verbose', |
| '--webdriver-arg="--log-path=-"']) |
| |
| rest_args.extend(self.pass_through_wpt_args) |
| |
| return rest_args |
| |
| def add_extra_arguments(self, parser): |
| class BinaryPassThroughArgs(PassThroughArgs): |
| pass_through_args = self.pass_through_binary_args |
| class WPTPassThroughArgs(PassThroughArgs): |
| pass_through_args = self.pass_through_wpt_args |
| |
| parser.add_argument('--webdriver-binary', default=DEFAULT_WEBDRIVER, |
| help='Path of the webdriver binary. It needs to have' |
| ' the same major version as the apk. Defaults to cipd' |
| ' archived version (near ToT).') |
| parser.add_argument('--wpt-path', default=DEFAULT_WPT, |
| help='Controls the path of the WPT runner to use' |
| ' (therefore tests). Defaults the revision rolled into' |
| ' Chromium.') |
| parser.add_argument('--test-type', default='testharness', |
| help='Specify to experiment with other test types.' |
| ' Currently only the default is expected to work.') |
| parser.add_argument('--product', choices = FLAGS_FILE_MAP.keys(), |
| required=True) |
| parser.add_argument('--apk', help='Apk to install during test. Defaults to' |
| ' the on-device WebView provider or Chrome.') |
| parser.add_argument('--system-webview-shell', help='System' |
| ' WebView Shell apk to install during test. Defaults' |
| ' to the on-device WebView Shell apk.') |
| parser.add_argument('--package-name', help='The package name of Chrome' |
| ' to test, defaults to that of the --apk.') |
| parser.add_argument('--verbose', '-v', action='count', |
| help='Verbosity level.') |
| parser.add_argument('--include', metavar='TEST_OR_DIR', |
| action=WPTPassThroughArgs, |
| help='Test(s) to run, defaults to run all tests.') |
| parser.add_argument('--list-tests', action=WPTPassThroughArgs, nargs=0, |
| help="Don't run any tests, just print out a list of" |
| ' tests that would be run.') |
| parser.add_argument('--webdriver-arg', action=WPTPassThroughArgs, |
| help='WebDriver args.') |
| parser.add_argument('--log-wptreport', metavar='WPT_REPORT_FILE', |
| action=WPTPassThroughArgs, |
| help="Log wptreport with subtest details.") |
| parser.add_argument('--log-raw', metavar='RAW_REPORT_FILE', |
| action=WPTPassThroughArgs, |
| help="Log raw report.") |
| parser.add_argument('--log-html', metavar='HTML_REPORT_FILE', |
| action=WPTPassThroughArgs, |
| help="Log html report.") |
| parser.add_argument('--log-xunit', metavar='XUNIT_REPORT_FILE', |
| action=WPTPassThroughArgs, |
| help="Log xunit report.") |
| parser.add_argument('--enable-features', action=BinaryPassThroughArgs, |
| help='Chromium features to enable during testing.') |
| parser.add_argument('--disable-features', action=BinaryPassThroughArgs, |
| help='Chromium features to disable during testing.') |
| parser.add_argument('--disable-field-trial-config', |
| action=BinaryPassThroughArgs, |
| help='Disable test trials for Chromium features.') |
| parser.add_argument('--force-fieldtrials', action=BinaryPassThroughArgs, |
| help='Force trials for Chromium features.') |
| parser.add_argument('--force-fieldtrial-params', |
| action=BinaryPassThroughArgs, |
| help='Force trial params for Chromium features.') |
| |
| |
| def run_android_webview(device, adapter): |
| if adapter.options.package_name: |
| logger.warn('--package-name has no effect for android_webview, provider' |
| 'will be set to the --apk if it is provided.') |
| |
| if adapter.options.system_webview_shell: |
| shell_pkg = apk_helper.GetPackageName(adapter.options.system_webview_shell) |
| if shell_pkg != SYSTEM_WEBVIEW_SHELL_PKG: |
| raise Exception('{} has incorrect package name: {}, expected {}.'.format( |
| '--system-webview-shell apk', shell_pkg, SYSTEM_WEBVIEW_SHELL_PKG)) |
| install_shell_as_needed = system_app.ReplaceSystemApp(device, shell_pkg, |
| adapter.options.system_webview_shell) |
| logger.info('Will install ' + shell_pkg + ' at ' |
| + adapter.options.system_webview_shell) |
| else: |
| install_shell_as_needed = no_op() |
| |
| if adapter.options.apk: |
| install_webview_as_needed = webview_app.UseWebViewProvider(device, |
| adapter.options.apk) |
| logger.info('Will install WebView apk at ' + adapter.options.apk) |
| else: |
| install_webview_as_needed = no_op() |
| |
| with install_shell_as_needed, install_webview_as_needed: |
| return adapter.run_test() |
| |
| |
| def run_chrome_android(device, adapter): |
| if adapter.options.apk: |
| with app_installed(device, adapter.options.apk): |
| return adapter.run_test() |
| else: |
| return adapter.run_test() |
| |
| |
| @contextlib.contextmanager |
| def app_installed(device, apk): |
| pkg = apk_helper.GetPackageName(apk) |
| device.Install(apk) |
| try: |
| yield |
| finally: |
| device.Uninstall(pkg) |
| |
| |
| # Dummy contextmanager to simplify multiple optional managers. |
| @contextlib.contextmanager |
| def no_op(): |
| yield |
| |
| |
| # This is not really a "script test" so does not need to manually add |
| # any additional compile targets. |
| def main_compile_targets(args): |
| json.dump([], args.output) |
| |
| |
| def main(): |
| adapter = WPTAndroidAdapter() |
| adapter.parse_args() |
| |
| if adapter.options.verbose: |
| if adapter.options.verbose == 1: |
| logger.setLevel(logging.INFO) |
| else: |
| logger.setLevel(logging.DEBUG) |
| |
| devil_chromium.Initialize() |
| |
| # Only 1 device is supported for Android locally, this will work well with |
| # sharding support via swarming infra. |
| device = device_utils.DeviceUtils.HealthyDevices()[0] |
| |
| flags_file = FLAGS_FILE_MAP[adapter.options.product] |
| all_flags = HOST_RESOLVER_ARGS + adapter.pass_through_binary_args |
| logger.info('Setting flags in ' + flags_file + ' to: ' + str(all_flags)) |
| flags = flag_changer.CustomCommandLineFlags(device, flags_file, all_flags) |
| |
| # WPT setup for chrome and webview requires that PATH contains adb. |
| platform_tools_path = os.path.dirname(devil_env.config.FetchPath('adb')) |
| os.environ['PATH'] = ':'.join([platform_tools_path] + |
| os.environ['PATH'].split(':')) |
| |
| with flags: |
| if adapter.options.product == 'android_webview': |
| run_android_webview(device, adapter) |
| elif adapter.options.product == 'chrome_android': |
| run_chrome_android(device, adapter) |
| |
| |
| if __name__ == '__main__': |
| # Conform minimally to the protocol defined by ScriptTest. |
| if 'compile_targets' in sys.argv: |
| funcs = { |
| 'run': None, |
| 'compile_targets': main_compile_targets, |
| } |
| sys.exit(common.run_script(sys.argv[1:], funcs)) |
| logging.basicConfig(level=logging.WARNING) |
| logger = logging.getLogger() |
| sys.exit(main()) |