# 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 fnmatch import os import pickle import sys from abc import ABCMeta, abstractmethod from collections import defaultdict from urllib.parse import urlsplit
import mozpack.path as mozpath import six from manifestparser import TestManifest, combine_fields from mozbuild.base import MozbuildObject from mozbuild.testing import REFTEST_FLAVORS, TEST_MANIFESTS from mozpack.files import FileFinder
Arguments:
aliases (tuple): A tuple containing shorthands used to refer to this suite.
build_flavor (str): The flavor assigned to this suite by the build system in `mozbuild.testing.TEST_MANIFESTS` (or similar).
mach_command (str): Name of the mach command used to run this suite.
kwargs (dict): Arguments needed to pass into the mach command.
task_regex (list): A list of regexes used to filter task labels that run
this suite. """
for i in range(1, MOCHITEST_TOTAL_CHUNKS + 1):
TEST_SUITES["mochitest-%d" % i] = { "aliases": ("m%d" % i,), "mach_command": "mochitest", "kwargs": { "flavor": "mochitest", "subsuite": "default", "chunk_by_dir": MOCHITEST_CHUNK_BY_DIR, "total_chunks": MOCHITEST_TOTAL_CHUNKS, "this_chunk": i, "test_paths": None,
},
}
WPT_TYPES = set() for suite, data in TEST_SUITES.items(): if suite.startswith("web-platform-tests"):
WPT_TYPES.add(data["kwargs"]["subsuite"])
def get_suite_definition(flavor, subsuite=None, strict=False): """Return a suite definition given a flavor and optional subsuite.
If strict isTrue, a subsuite must have its own entry in TEST_SUITES.
Otherwise, the entry for'flavor' will be returned with the 'subsuite'
keyword arg set.
Withor without strict mode, an empty dict will be returned if no
matching suite definition was found. """ ifnot subsuite:
suite_name = _test_flavors.get(flavor) return suite_name, TEST_SUITES.get(suite_name, {}).copy()
suite_name = _test_subsuites.get((flavor, subsuite)) if suite_name or strict: return suite_name, TEST_SUITES.get(suite_name, {}).copy()
suite_name = _test_flavors.get(flavor) if suite_name notin TEST_SUITES: return suite_name, {}
suite = TEST_SUITES[suite_name].copy()
suite.setdefault("kwargs", {})
suite["kwargs"]["subsuite"] = subsuite return suite_name, suite
def rewrite_test_base(test, new_base): """Rewrite paths in a test to be under a new base path.
This is useful for running tests from a separate location from where they
were defined. """
test["here"] = mozpath.join(new_base, test["dir_relpath"])
test["path"] = mozpath.join(new_base, test["file_relpath"]) return test
@six.add_metaclass(ABCMeta) class TestLoader(MozbuildObject):
@abstractmethod def __call__(self): """Generate test metadata."""
class BuildBackendLoader(TestLoader): def __call__(self): """Loads the test metadata generated by the TestManifest build backend.
The 'all-tests.pkl' file is a mapping of source path to test objects. The 'test-defaults.pkl' file maps manifests to their DEFAULT configuration.
These manifest defaults will be merged into the test configuration of the
contained tests. """ # If installing tests is going to result in re-generating the build # backend, we need to do this here, so that the updated contents of # all-tests.pkl make it to the set of tests to run. if self.backend_out_of_date(
mozpath.join(self.topobjdir, "backend.TestManifestBackend")
):
print("Test configuration changed. Regenerating backend.") from mozbuild.gen_test_backend import gen_test_backend
with open(all_tests, "rb") as fh:
test_data = pickle.load(fh)
with open(test_defaults, "rb") as fh:
defaults = pickle.load(fh)
# The keys in defaults use platform-specific path separators. # self.topsrcdir was normalized to use /, revert back to \ if needed.
topsrcdir = os.path.normpath(self.topsrcdir)
for path, tests in six.iteritems(test_data): for metadata in tests:
defaults_manifests = [metadata["manifest"]]
# For test coverage on the generation and the reading of # manifest_defaults with ancestor_manifest, see # to_ancestor_manifest_path in the following test files: # testing/mozbase/moztest/tests/test_resolve.py # python/mozbuild/mozbuild/test/backend/test_test_manifest.py
ancestor_manifest = metadata.get("ancestor_manifest") if ancestor_manifest: # The (ancestor manifest, included manifest) tuple # contains the defaults of the included manifest, so # use it instead of [metadata['manifest']].
# ancestor_manifest is a relative path with # platform-specific path separators.
defaults_manifests[0] = (ancestor_manifest, metadata["manifest"])
for manifest in defaults_manifests:
manifest_defaults = defaults.get(manifest) if manifest_defaults:
metadata = combine_fields(manifest_defaults, metadata)
yield metadata
class TestManifestLoader(TestLoader): def __init__(self, *args, **kwargs):
super(TestManifestLoader, self).__init__(*args, **kwargs)
self.finder = FileFinder(self.topsrcdir)
self.reader = self.mozbuild_reader(config_mode="empty")
self.variables = { "{}_MANIFESTS".format(k): v[0] for k, v in six.iteritems(TEST_MANIFESTS)
}
self.variables.update(
{"{}_MANIFESTS".format(f.upper()): f for f in REFTEST_FLAVORS}
)
def _load_manifestparser_manifest(self, mpath):
mp = TestManifest(
manifests=[mpath],
strict=True,
rootdir=self.topsrcdir,
finder=self.finder,
handle_defaults=True,
) return (test for test in mp.tests)
@property def tests(self): ifnot self._tests_loaded:
self._reset_state() for test in self.load_tests():
self._tests.append(test)
self._tests_loaded = True return self._tests
@property def tests_by_path(self): ifnot self._tests_by_path: for test in self.tests:
self._tests_by_path[test["file_relpath"]].append(test) return self._tests_by_path
@property def tests_by_flavor(self): ifnot self._tests_by_flavor: for test in self.tests:
self._tests_by_flavor[test["flavor"]].add(test["file_relpath"]) return self._tests_by_flavor
@property def tests_by_manifest(self): ifnot self._tests_by_manifest: for test in self.tests: if test["flavor"] == "web-platform-tests": # Use test ids instead of paths for WPT.
self._tests_by_manifest[test["manifest"]].append(test["name"]) else:
relpath = mozpath.relpath(
test["path"], mozpath.dirname(test["manifest"])
)
self._tests_by_manifest[test["manifest_relpath"]].append(relpath) return self._tests_by_manifest
@property def test_dirs(self): ifnot self._test_dirs: for test in self.tests:
self._test_dirs.add(test["dir_relpath"]) return self._test_dirs
# similar logic to wpt TestLoader::load_dir_metadata
path_parts = os.path.dirname(path).split(os.path.sep) for i in range(1, len(path_parts) + 1):
p = os.path.join(
metadata_base, os.path.sep.join(path_parts[:i]), "__dir__.ini"
) ifnot p: break if os.path.exists(p):
paths.append(p)
for file_path in paths: if file_path in self.meta_tags:
test_tags.extend(self.meta_tags[file_path]) continue
try: with open(file_path, "rb") as f: # __dir__.ini are not proper .ini files, configParser doesn't work # WPT uses a custom reader for __dir__.ini, but hard to load/use here.
data = f.read().decode("utf-8") for line in data.split("\n"): if"tags: ["in line:
self.meta_tags[file_path] = (
line.split("[")[1].split("]")[0].split(" ")
)
test_tags.extend(self.meta_tags[file_path]) except IOError: pass
return list(set(test_tags))
def _resolve(
self, paths=None, flavor="", subsuite=None, under_path=None, tags=None
): """Given parameters, resolve them to produce an appropriate list of tests.
Args:
paths (list):
By default, set to None. If provided as a list of paths, then
this method will attempt to load the appropriate set of tests
that live in this path.
flavor (string):
By default, an empty string. If provided as a string, then this
method will attempt to load tests that belong to this flavor.
Additional filtering also takes the flavor into consideration.
subsuite (string):
By default, set to None. If provided as a string, then this value is used to perform filtering of a candidate set of tests. """ if tags:
tags = set(tags)
def fltr(tests): """Filters tests based on several criteria.
Args:
tests (list):
List of tests that belong to the same candidate path.
Returns:
test (dict): If the test survived the filtering process, it is returned as a valid test. """ for test in tests: if flavor: if flavor == "devtools"and test.get("flavor") != "browser-chrome": continue if flavor != "devtools"and test.get("flavor") != flavor: continue
if under_path andnot test["file_relpath"].startswith(under_path): continue
# Make a copy so modifications don't change the source. yield dict(test)
paths = paths or []
paths = [mozpath.normpath(p) for p in paths] ifnot paths:
paths = [None]
if flavor in ("", "puppeteer", None) and (
any(self.is_puppeteer_path(p) for p in paths) or paths == [None]
):
self.add_puppeteer_manifest_data()
if flavor in ("", "web-platform-tests", None) and (
any(self.is_wpt_path(p) for p in paths) or paths == [None]
):
self.add_wpt_manifest_data()
if flavor in ("", "fenix", None) and (
any(self.is_fenix_path(p) for p in paths) or paths == [None]
):
self.add_fenix_manifest_data()
if flavor in ("", "focus", None) and (
any(self.is_focus_path(p) for p in paths) or paths == [None]
):
self.add_focus_manifest_data()
if flavor in ("", "android-components", None) and (
any(self.is_ac_path(p) for p in paths) or paths == [None]
):
self.add_ac_manifest_data()
if flavor in ("", "geckoview", "junit", None) and (
any(self.is_geckoview_junit_path(p) for p in paths) or paths == [None]
):
self.add_geckoview_junit_manifest_data()
candidate_paths = set()
for path in sorted(paths): if path isNone:
candidate_paths |= set(self.tests_by_path.keys()) continue
if"*"in path:
candidate_paths |= {
p for p in self.tests_by_path if mozpath.match(p, path)
} continue
# If the path is a directory, or the path is a prefix of a directory # containing tests, pull in all tests in that directory. if path in self.test_dirs or any(
p.startswith(path) for p in self.tests_by_path
):
candidate_paths |= {p for p in self.tests_by_path if p.startswith(path)} continue
# If the path is a manifest, add all tests defined in that manifest. if any(path.endswith(e) for e in (".toml", ".ini", ".list")):
key = "manifest"if os.path.isabs(path) else"manifest_relpath"
candidate_paths |= {
t["file_relpath"] for t in self.tests if mozpath.normpath(t[key]) == path
} continue
# If it's a test file, add just that file.
candidate_paths |= {p for p in self.tests_by_path if path in p}
for p in sorted(candidate_paths):
tests = self.tests_by_path[p] for test in fltr(tests): yield test
def is_puppeteer_path(self, path): if path isNone: returnTrue return mozpath.match(path, "remote/test/puppeteer/test/**")
def is_fenix_path(self, path): if path isNone: returnTrue return mozpath.match(path, "mobile/android/fenix/**")
def is_focus_path(self, path): if path isNone: returnTrue return mozpath.match(path, "mobile/android/focus-android/**")
def is_ac_path(self, path): if path isNone: returnTrue return mozpath.match(path, "mobile/android/android-components/**")
def is_geckoview_junit_path(self, path): if path isNone: returnTrue return mozpath.match(path, "mobile/android/geckoview/**")
def add_puppeteer_manifest_data(self): if self._puppeteer_loaded: return
def is_wpt_path(self, path): """Checks if path forms part of the known web-platform-test paths.
Args:
path (str orNone):
Path to check against the list of known web-platform-test paths.
Returns:
Boolean value. Trueif path is part of web-platform-tests path, or
path isNone. False otherwise. """ if path isNone: returnTrue if mozpath.match(path, "testing/web-platform/tests/**"): returnTrue if mozpath.match(path, "testing/web-platform/mozilla/tests/**"): returnTrue returnFalse
def get_wpt_group(self, test, depth=3): """Given a test object set the group (aka manifest) that it belongs to.
If a custom value for `depth` is provided, it will override the default
value of 3 path components.
Args:
test (dict): Test object for the particular suite and subsuite.
depth (int, optional): Custom number of path elements.
Returns:
str: The group the given test belongs to. """ # This takes into account that for mozilla-specific WPT tests, the path # contains an extra '/_mozilla' prefix that must be accounted for. if test["name"].startswith("/_mozilla"):
depth = depth + 1
# Webdriver tests are nested in "classic" and "bidi" folders. Increase # the depth to avoid grouping all classic or bidi tests in one chunk. if test["name"].startswith(("/webdriver", "/_mozilla/webdriver")):
depth = depth + 1
# Webdriver BiDi tests are nested even further as tests are grouped by # module but also by command / event name. if test["name"].startswith(
("/webdriver/tests/bidi", "/_mozilla/webdriver/bidi")
):
depth = depth + 1
# wpt canvas tests are mostly nested under subfolders of /html/canvas, # increase the depth to ensure chunks can be balanced correctly. if test["name"].startswith("/html/canvas"):
depth = depth + 1
if test["name"].startswith("/_mozilla/webgpu"):
depth = 9001
# We have a leading / so the first component is always ""
components = depth + 1 return"/".join(urlsplit(test["name"]).path.split("/")[:-1][:components])
def add_wpt_manifest_data(self): """Adds manifest data for web-platform-tests into the list of available tests.
Upon invocation, this method will download from firefox-ci the most recent
version of the web-platform-tests manifests.
Once manifest is downloaded, this method will add details about each test
into the list of available tests. """ if self._wpt_loaded: return
for test in tests:
testobj = { "head": "", "support-files": "", "path": full_path, "flavor": "web-platform-tests", "subsuite": test_type, "here": mozpath.dirname(path), "name": test.id, "file_relpath": src_path, "srcdir_relpath": src_path, "dir_relpath": mozpath.dirname(src_path), "tags": " ".join(test_tags),
}
group = self.get_wpt_group(testobj)
testobj["manifest"] = group
test_root = "tests" if group.startswith("/_mozilla"):
test_root = os.path.join("mozilla", "tests")
group = group[len("/_mozilla") :]
group = group.lstrip("/")
testobj["manifest_relpath"] = os.path.join(
wpt_path, test_root, group
)
self._tests.append(testobj)
self._wpt_loaded = True
def resolve_tests(self, cwd=None, **kwargs): """Resolve tests from an identifier.
This is a generator of dicts describing each test. All arguments are
optional.
Paths in returned tests are automatically translated to the paths in
the _tests directory under the object directory.
Args:
cwd (str): If specified, we will limit our results to tests under this
directory. The directory should be defined as an absolute path
under topsrcdir or topobjdir.
paths (list):
An iterable of values to use to identify tests to run. If an
entry is a known test file, tests associated with that file are
returned (there may be multiple configurations for a single
file). If an entry is a directory, or a prefix of a directory
containing tests, all tests in that directory are returned. If
the string appears in a known test file, that test file is
considered. If the path contains a wildcard pattern, tests
matching that pattern are returned.
under_path (str): If specified, will be used to filter out tests that aren't in
the specified path prefix relative to topsrcdir or the test's
installed dir.
flavor (str): If specified, will be used to filter returned tests to only be
the flavor specified. A flavor is something like ``xpcshell``.
subsuite (str): If specified will be used to filter returned tests to only be in the subsuite specified. To filter only tests that *don't*
have any subsuite, pass the string 'undefined'.
tags (list): If specified, will be used to filter out tests that don't contain
a matching tag. """ if cwd:
norm_cwd = mozpath.normpath(cwd)
norm_srcdir = mozpath.normpath(self.topsrcdir)
norm_objdir = mozpath.normpath(self.topobjdir)
rewrite_base = None for test in self._resolve(**kwargs):
rewrite_base = self.test_rewrites.get(test["flavor"], None)
if rewrite_base:
rewrite_base = os.path.join(
self.topobjdir, os.path.normpath(rewrite_base)
) yield rewrite_test_base(test, rewrite_base) else: yield test
def resolve_metadata(self, what): """Resolve tests based on the given metadata. If not specified, metadata from outgoing files will be used instead. """ # Parse arguments and assemble a test "plan."
run_suites = set()
run_tests = []
for entry in what: # If the path matches the name or alias of an entire suite, run # the entire suite. if entry in TEST_SUITES:
run_suites.add(entry) continue
suitefound = False for suite, v in six.iteritems(TEST_SUITES): if entry.lower() in v.get("aliases", []):
run_suites.add(suite)
suitefound = True if suitefound: continue
# Now look for file/directory matches in the TestResolver.
relpath = self._wrap_path_argument(entry).relpath() # since either path or tag can be defined (but not both), here we assume # one or none are defined, but not both
tests = list(self.resolve_tests(paths=[relpath])) ifnot tests:
tests = list(self.resolve_tests(tags=entry))
run_tests.extend(tests)
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 und die Messung sind noch experimentell.