#!/usr/bin/env python # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at http://mozilla.org/MPL/2.0/. import copy import gzip import json import os import sys from datetime import datetime, timedelta
# load modules from parent dir
sys.path.insert(1, os.path.dirname(sys.path[0]))
import mozinfo from mozharness.base.errors import BaseErrorList from mozharness.base.log import INFO from mozharness.base.script import PreScriptAction from mozharness.base.vcs.vcsbase import MercurialScript from mozharness.mozilla.automation import TBPL_RETRY from mozharness.mozilla.structuredlog import StructuredOutputParser from mozharness.mozilla.testing.android import AndroidMixin from mozharness.mozilla.testing.codecoverage import (
CodeCoverageMixin,
code_coverage_config_options,
) from mozharness.mozilla.testing.errors import WptHarnessErrorList from mozharness.mozilla.testing.testbase import TestingMixin, testing_config_options
class WebPlatformTest(TestingMixin, MercurialScript, CodeCoverageMixin, AndroidMixin):
config_options = (
[
[
["--test-type"],
{ "action": "extend", "dest": "test_type", "help": "Specify the test types to run.",
},
],
[
["--disable-e10s"],
{ "action": "store_false", "dest": "e10s", "default": True, "help": "Run without e10s enabled",
},
],
[
["--disable-fission"],
{ "action": "store_true", "dest": "disable_fission", "default": False, "help": "Run without fission enabled",
},
],
[
["--total-chunks"],
{ "action": "store", "dest": "total_chunks", "help": "Number of total chunks",
},
],
[
["--this-chunk"],
{ "action": "store", "dest": "this_chunk", "help": "Number of this chunk",
},
],
[
["--allow-software-gl-layers"],
{ "action": "store_true", "dest": "allow_software_gl_layers", "default": False, "help": "Permits a software GL implementation (such as LLVMPipe) " "to use the GL compositor.",
},
],
[
["--headless"],
{ "action": "store_true", "dest": "headless", "default": False, "help": "Run tests in headless mode.",
},
],
[
["--headless-width"],
{ "action": "store", "dest": "headless_width", "default": "1600", "help": "Specify headless virtual screen width (default: 1600).",
},
],
[
["--headless-height"],
{ "action": "store", "dest": "headless_height", "default": "1200", "help": "Specify headless virtual screen height (default: 1200).",
},
],
[
["--setpref"],
{ "action": "append", "metavar": "PREF=VALUE", "dest": "extra_prefs", "default": [], "help": "Defines an extra user preference.",
},
],
[
["--skip-implementation-status"],
{ "action": "extend", "dest": "skip_implementation_status", "default": [], "help": "Defines a way to not run a specific implementation status " " (i.e. not implemented).",
},
],
[
["--backlog"],
{ "action": "store_true", "dest": "backlog", "default": False, "help": "Defines if test category is backlog.",
},
],
[
["--skip-timeout"],
{ "action": "store_true", "dest": "skip_timeout", "default": False, "help": "Ignore tests that are expected status of TIMEOUT",
},
],
[
["--skip-crash"],
{ "action": "store_true", "dest": "skip_crash", "default": False, "help": "Ignore tests that are expected status of CRASH",
},
],
[
["--default-exclude"],
{ "action": "store_true", "dest": "default_exclude", "default": False, "help": "Only run the tests explicitly given in arguments",
},
],
[
["--include"],
{ "action": "append", "dest": "include", "default": [], "help": "Add URL prefix to include.",
},
],
[
["--exclude"],
{ "action": "append", "dest": "exclude", "default": [], "help": "Add URL prefix to exclude.",
},
],
[
["--tag"],
{ "action": "append", "dest": "tag", "default": [], "help": "Add test tag (which includes URL prefix) to include.",
},
],
[
["--exclude-tag"],
{ "action": "append", "dest": "exclude_tag", "default": [], "help": "Add test tag (which includes URL prefix) to exclude.",
},
],
[
["--repeat"],
{ "action": "store", "dest": "repeat", "default": 0, "type": int, "help": "Repeat tests (used for confirm-failures) X times.",
},
],
[
["--timeout-multiplier"],
{ "action": "store", "dest": "timeout_multiplier", "type": float, "help": "Sets the timeout multiplier (0.25 for `--backlog` tests by default)",
},
],
]
+ copy.deepcopy(testing_config_options)
+ copy.deepcopy(code_coverage_config_options)
)
# Surely this should be in the superclass
c = self.config
self.installer_url = c.get("installer_url")
self.test_url = c.get("test_url")
self.test_packages_url = c.get("test_packages_url")
self.installer_path = c.get("installer_path")
self.binary_path = c.get("binary_path")
self.repeat = c.get("repeat")
self.abs_app_dir = None
self.xre_path = None if self.is_emulator:
self.device_serial = "emulator-5554"
def query_abs_app_dir(self): """We can't set this in advance, because OSX install directories
change depending on branding and opt/debug. """ if self.abs_app_dir: return self.abs_app_dir ifnot self.binary_path:
self.fatal("Can't determine abs_app_dir (binary_path not set!)")
self.abs_app_dir = os.path.dirname(self.binary_path) return self.abs_app_dir
def query_abs_dirs(self): if self.abs_dirs: return self.abs_dirs
abs_dirs = super(WebPlatformTest, self).query_abs_dirs()
dirs = {}
dirs["abs_app_install_dir"] = os.path.join(
abs_dirs["abs_work_dir"], "application"
)
dirs["abs_test_install_dir"] = os.path.join(abs_dirs["abs_work_dir"], "tests")
dirs["abs_test_bin_dir"] = os.path.join(dirs["abs_test_install_dir"], "bin")
dirs["abs_wpttest_dir"] = os.path.join(
dirs["abs_test_install_dir"], "web-platform"
)
dirs["abs_blob_upload_dir"] = os.path.join(
abs_dirs["abs_work_dir"], "blobber_upload_dir"
)
dirs["abs_test_extensions_dir"] = os.path.join(
dirs["abs_test_install_dir"], "extensions"
)
work_dir = os.environ.get("MOZ_FETCHES_DIR") or abs_dirs["abs_work_dir"] if self.is_android:
dirs["abs_xre_dir"] = os.path.join(work_dir, "hostutils") if self.is_emulator:
dirs["abs_sdk_dir"] = os.path.join(work_dir, "android-sdk-linux")
dirs["abs_avds_dir"] = os.path.join(work_dir, "android-device")
dirs["abs_bundletool_path"] = os.path.join(work_dir, "bundletool.jar") # AndroidMixin uses this when launching the emulator. We only want # GLES3 if we're running WebRender (default)
self.use_gles3 = True
cmd += [ "--log-raw=-", "--log-wptreport=%s"
% os.path.join(dirs["abs_blob_upload_dir"], "wptreport.json"), "--log-errorsummary=%s" % error_summary_file, "--symbols-path=%s" % self.symbols_path, "--stackwalk-binary=%s" % self.query_minidump_stackwalk(), "--stackfix-dir=%s" % os.path.join(dirs["abs_test_install_dir"], "bin"), "--no-pause-after-test", "--instrument-to-file=%s"
% os.path.join(dirs["abs_blob_upload_dir"], "wpt_instruments.txt"), "--specialpowers-path=%s"
% os.path.join(
dirs["abs_test_extensions_dir"], "specialpowers@mozilla.org.xpi"
), # Ensure that we don't get a Python traceback from handlers that will be # added to the log summary "--suppress-handler-traceback",
]
if self.repeat > 0: # repeat should repeat the original test, so +1 for first run
cmd.append("--repeat=%s" % (self.repeat + 1))
if (
self.is_android or mozinfo.info["tsan"] or"wdspec"in test_types ornot c["disable_fission"] # reftest on osx needs to be 1 process or"reftest"in test_types and sys.platform.startswith("darwin")
):
processes = 1 else:
processes = 2
cmd.append("--processes=%s" % processes)
test_paths = set() ifnot (self.verify_enabled or self.per_test_coverage): # mozharness_test_paths is a set of test groups (directories) to run # if we have confirm_paths, this is a specific path we want to run and ignore the group
mozharness_test_paths = json.loads(
os.environ.get("MOZHARNESS_TEST_PATHS", '""')
)
confirm_paths = json.loads(os.environ.get("MOZHARNESS_CONFIRM_PATHS", '""'))
if mozharness_test_paths: if confirm_paths:
mozharness_test_paths = confirm_paths
ifnot os.path.exists(path):
self.critical("Unable to locate web-platform-test groups file.")
cmd.append("--test-groups={}".format(path))
for key in mozharness_test_paths.keys(): if"web-platform"notin key:
self.info("Ignoring test_paths for {} harness".format(key)) continue
paths = mozharness_test_paths.get(key, []) for p in paths: ifnot p.startswith("/"): # Assume this is a filesystem path rather than a test id
path = os.path.relpath(p, "testing/web-platform") if".."in path:
self.fatal("Invalid WPT path: {}".format(path))
path = os.path.join(dirs["abs_wpttest_dir"], path) else:
path = p
test_paths.add(path) else: # As per WPT harness, the --run-by-dir flag is incompatible with # the --test-groups flag.
cmd.append("--run-by-dir=%i" % (3 ifnot mozinfo.info["asan"] else 0)) for opt in ["total_chunks", "this_chunk"]:
val = c.get(opt) if val:
cmd.append("--%s=%s" % (opt.replace("_", "-"), val))
options = list(c.get("options", []))
if"wdspec"in test_types:
geckodriver_path = self._query_geckodriver() ifnot geckodriver_path ornot os.path.isfile(geckodriver_path):
self.fatal( "Unable to find geckodriver binary " "in common test package: %s" % str(geckodriver_path)
)
cmd.append("--webdriver-binary=%s" % geckodriver_path)
cmd.append("--webdriver-arg=-vv") # enable trace logs
for url_prefix in c["include"]:
cmd.append(f"--include={url_prefix}") for url_prefix in c["exclude"]:
cmd.append(f"--exclude={url_prefix}") for tag in c["tag"]:
cmd.append(f"--tag={tag}") for tag in c["exclude_tag"]:
cmd.append(f"--exclude-tag={tag}")
if mozinfo.info["os"] == "win": # Because of a limit on the length of CLI command line string length in Windows, we # should prefer to pass paths by a file instead. import tempfile
with tempfile.NamedTemporaryFile(delete=False) as tmp:
tmp.write("\n".join(test_paths).encode())
cmd.append(f"--include-file={tmp.name}") else:
cmd.extend(test_paths)
return cmd
def download_and_extract(self):
super(WebPlatformTest, self).download_and_extract(
extract_dirs=[ "mach", "bin/*", "config/*", "extensions/*", "mozbase/*", "marionette/*", "tools/*", "web-platform/*", "mozpack/*", "mozbuild/*",
],
suite_categories=["web-platform"],
)
dirs = self.query_abs_dirs() if self.is_android:
self.xre_path = dirs["abs_xre_dir"] # Make sure that the logging directory exists if self.mkdir_p(dirs["abs_blob_upload_dir"]) == -1:
self.fatal("Could not create blobber upload directory") # Exit
def download_and_process_manifest(self): """Downloads the tests-by-manifest JSON mapping generated by the decision task.
web-platform-tests are chunked in the decision task as of Bug 1608837 and this means tests are resolved by the TestResolver as part of this process.
The manifest file contains tests keyed by the groups generated in
TestResolver.get_wpt_group().
Upon successful call, a JSON file containing only the web-platform test
groups are saved in the fetch directory.
# Save file to the MOZ_FETCHES dir.
self.download_file(
url, file_name=artifact_name, parent_dir=dirs["abs_fetches_dir"]
)
with gzip.open(os.path.join(dirs["abs_fetches_dir"], artifact_name), "r") as f:
tests_by_manifest = json.loads(f.read())
# We need to filter out non-web-platform-tests without knowing what the # groups are. Fortunately, all web-platform test 'manifests' begin with a # forward slash.
test_groups = {
key: tests_by_manifest[key] for key in tests_by_manifest.keys() if key.startswith("/")
}
outfile = os.path.join(dirs["abs_fetches_dir"], "wpt_tests_by_group.json") with open(outfile, "w+") as f:
json.dump(test_groups, f, indent=2, sort_keys=True)
def install(self): if self.is_android:
self.install_android_app(self.installer_path) else:
super(WebPlatformTest, self).install()
def _install_fonts(self): if self.is_android: return # Ensure the Ahem font is available
dirs = self.query_abs_dirs()
if self.per_test_coverage or self.verify_enabled:
suites = self.query_per_test_category_suites(None, None) if"wdspec"in suites: # geckodriver is required for wdspec, but not always available
geckodriver_path = self._query_geckodriver() ifnot geckodriver_path ornot os.path.isfile(geckodriver_path):
suites.remove("wdspec")
self.info("Skipping 'wdspec' tests - no geckodriver") else:
test_types = self.config.get("test_type", [])
suites = [None] for suite in suites: if executed_too_many_tests andnot self.per_test_coverage: continue
if suite:
test_types = [suite]
summary = {} for per_test_args in self.query_args(suite): # Make sure baseline code coverage tests are never # skipped and that having them run has no influence # on the max number of actual tests that are to be run.
is_baseline_test = ( "baselinecoverage"in per_test_args[-1] if self.per_test_coverage elseFalse
) if executed_too_many_tests andnot is_baseline_test: continue
ifnot is_baseline_test: if (datetime.now() - start_time) > max_per_test_time: # Running tests has run out of time. That is okay! Stop running # them so that a task timeout is not triggered, and so that # (partial) results are made available in a timely manner.
self.info( "TinderboxPrint: Running tests took too long: Not all tests " "were executed. "
) return if executed_tests >= max_per_test_tests: # When changesets are merged between trees or many tests are # otherwise updated at once, there probably is not enough time # to run all tests, and attempting to do so may cause other # problems, such as generating too much log output.
self.info( "TinderboxPrint: Too many modified tests: Not all tests " "were executed. "
)
executed_too_many_tests = True
if len(per_test_args) > 0:
self.log_per_test_status(per_test_args[-1], tbpl_status, log_level) if tbpl_status == TBPL_RETRY:
self.info("Per-test run abandoned due to RETRY status") return
# main {{{1 if __name__ == "__main__":
web_platform_tests = WebPlatformTest()
web_platform_tests.run_and_exit()
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.