diff options
Diffstat (limited to 'python/mozbuild/mozbuild/testing.py')
-rw-r--r-- | python/mozbuild/mozbuild/testing.py | 266 |
1 files changed, 266 insertions, 0 deletions
diff --git a/python/mozbuild/mozbuild/testing.py b/python/mozbuild/mozbuild/testing.py new file mode 100644 index 0000000000..f951434f97 --- /dev/null +++ b/python/mozbuild/mozbuild/testing.py @@ -0,0 +1,266 @@ +# 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 os +import sys + +import manifestparser +import mozpack.path as mozpath +from mozpack.copier import FileCopier +from mozpack.manifests import InstallManifest + +# These definitions provide a single source of truth for modules attempting +# to get a view of all tests for a build. Used by the emitter to figure out +# how to read/install manifests and by test dependency annotations in Files() +# entries to enumerate test flavors. + +# While there are multiple test manifests, the behavior is very similar +# across them. We enforce this by having common handling of all +# manifests and outputting a single class type with the differences +# described inside the instance. +# +# Keys are variable prefixes and values are tuples describing how these +# manifests should be handled: +# +# (flavor, install_root, install_subdir, package_tests) +# +# flavor identifies the flavor of this test. +# install_root is the path prefix to install the files starting from the root +# directory and not as specified by the manifest location. (bug 972168) +# install_subdir is the path of where to install the files in +# the tests directory. +# package_tests indicates whether to package test files into the test +# package; suites that compile the test files should not install +# them into the test package. +# +TEST_MANIFESTS = dict( + A11Y=("a11y", "testing/mochitest", "a11y", True), + BROWSER_CHROME=("browser-chrome", "testing/mochitest", "browser", True), + ANDROID_INSTRUMENTATION=("instrumentation", "instrumentation", ".", False), + FIREFOX_UI_FUNCTIONAL=("firefox-ui-functional", "firefox-ui", ".", False), + FIREFOX_UI_UPDATE=("firefox-ui-update", "firefox-ui", ".", False), + PYTHON_UNITTEST=("python", "python", ".", False), + CRAMTEST=("cram", "cram", ".", False), + TELEMETRY_TESTS_CLIENT=( + "telemetry-tests-client", + "toolkit/components/telemetry/tests/marionette/", + ".", + False, + ), + # marionette tests are run from the srcdir + # TODO(ato): make packaging work as for other test suites + MARIONETTE=("marionette", "marionette", ".", False), + MARIONETTE_UNIT=("marionette", "marionette", ".", False), + MARIONETTE_WEBAPI=("marionette", "marionette", ".", False), + MOCHITEST=("mochitest", "testing/mochitest", "tests", True), + MOCHITEST_CHROME=("chrome", "testing/mochitest", "chrome", True), + WEBRTC_SIGNALLING_TEST=("steeplechase", "steeplechase", ".", True), + XPCSHELL_TESTS=("xpcshell", "xpcshell", ".", True), + PERFTESTS=("perftest", "testing/perf", "perf", True), +) + +# reftests, wpt, and puppeteer all have their own manifest formats +# and are processed separately +REFTEST_FLAVORS = ("crashtest", "reftest") +PUPPETEER_FLAVORS = ("puppeteer",) +WEB_PLATFORM_TESTS_FLAVORS = ("web-platform-tests",) + + +def all_test_flavors(): + return ( + [v[0] for v in TEST_MANIFESTS.values()] + + list(REFTEST_FLAVORS) + + list(PUPPETEER_FLAVORS) + + list(WEB_PLATFORM_TESTS_FLAVORS) + ) + + +class TestInstallInfo(object): + def __init__(self): + self.seen = set() + self.pattern_installs = [] + self.installs = [] + self.external_installs = set() + self.deferred_installs = set() + + def __ior__(self, other): + self.pattern_installs.extend(other.pattern_installs) + self.installs.extend(other.installs) + self.external_installs |= other.external_installs + self.deferred_installs |= other.deferred_installs + return self + + +class SupportFilesConverter(object): + """Processes a "support-files" entry from a test object, either from + a parsed object from a test manifests or its representation in + moz.build and returns the installs to perform for this test object. + + Processing the same support files multiple times will not have any further + effect, and the structure of the parsed objects from manifests will have a + lot of repeated entries, so this class takes care of memoizing. + """ + + def __init__(self): + self._fields = ( + ("head", set()), + ("support-files", set()), + ("generated-files", set()), + ) + + def convert_support_files(self, test, install_root, manifest_dir, out_dir): + # Arguments: + # test - The test object to process. + # install_root - The directory under $objdir/_tests that will contain + # the tests for this harness (examples are "testing/mochitest", + # "xpcshell"). + # manifest_dir - Absoulute path to the (srcdir) directory containing the + # manifest that included this test + # out_dir - The path relative to $objdir/_tests used as the destination for the + # test, based on the relative path to the manifest in the srcdir and + # the install_root. + info = TestInstallInfo() + for field, seen in self._fields: + value = test.get(field, "") + for pattern in value.split(): + + # We track uniqueness locally (per test) where duplicates are forbidden, + # and globally, where they are permitted. If a support file appears multiple + # times for a single test, there are unnecessary entries in the manifest. But + # many entries will be shared across tests that share defaults. + key = field, pattern, out_dir + if key in info.seen: + raise ValueError( + "%s appears multiple times in a test manifest under a %s field," + " please omit the duplicate entry." % (pattern, field) + ) + info.seen.add(key) + if key in seen: + continue + seen.add(key) + + if field == "generated-files": + info.external_installs.add( + mozpath.normpath(mozpath.join(out_dir, pattern)) + ) + # '!' indicates our syntax for inter-directory support file + # dependencies. These receive special handling in the backend. + elif pattern[0] == "!": + info.deferred_installs.add(pattern) + # We only support globbing on support-files because + # the harness doesn't support * for head. + elif "*" in pattern and field == "support-files": + info.pattern_installs.append((manifest_dir, pattern, out_dir)) + # "absolute" paths identify files that are to be + # placed in the install_root directory (no globs) + elif pattern[0] == "/": + full = mozpath.normpath( + mozpath.join(manifest_dir, mozpath.basename(pattern)) + ) + info.installs.append( + (full, mozpath.join(install_root, pattern[1:])) + ) + else: + full = mozpath.normpath(mozpath.join(manifest_dir, pattern)) + dest_path = mozpath.join(out_dir, pattern) + + # If the path resolves to a different directory + # tree, we take special behavior depending on the + # entry type. + if not full.startswith(manifest_dir): + # If it's a support file, we install the file + # into the current destination directory. + # This implementation makes installing things + # with custom prefixes impossible. If this is + # needed, we can add support for that via a + # special syntax later. + if field == "support-files": + dest_path = mozpath.join(out_dir, os.path.basename(pattern)) + # If it's not a support file, we ignore it. + # This preserves old behavior so things like + # head files doesn't get installed multiple + # times. + else: + continue + info.installs.append((full, mozpath.normpath(dest_path))) + return info + + +def install_test_files(topsrcdir, topobjdir, tests_root): + """Installs the requested test files to the objdir. This is invoked by + test runners to avoid installing tens of thousands of test files when + only a few tests need to be run. + """ + + manifest = InstallManifest( + mozpath.join(topobjdir, "_build_manifests", "install", "_test_files") + ) + + harness_files_manifest = mozpath.join( + topobjdir, "_build_manifests", "install", tests_root + ) + + if os.path.isfile(harness_files_manifest): + # If the backend has generated an install manifest for test harness + # files they are treated as a monolith and installed each time we + # run tests. Fortunately there are not very many. + manifest |= InstallManifest(harness_files_manifest) + + copier = FileCopier() + manifest.populate_registry(copier) + copier.copy(mozpath.join(topobjdir, tests_root), remove_unaccounted=False) + + +# Convenience methods for test manifest reading. +def read_manifestparser_manifest(context, manifest_path): + path = manifest_path.full_path + return manifestparser.TestManifest( + manifests=[path], + strict=True, + rootdir=context.config.topsrcdir, + finder=context._finder, + handle_defaults=False, + ) + + +def read_reftest_manifest(context, manifest_path): + import reftest + + path = manifest_path.full_path + manifest = reftest.ReftestManifest(finder=context._finder) + manifest.load(path) + return manifest + + +def read_wpt_manifest(context, paths): + manifest_path, tests_root = paths + full_path = mozpath.normpath(mozpath.join(context.srcdir, manifest_path)) + old_path = sys.path[:] + try: + # Setup sys.path to include all the dependencies required to import + # the web-platform-tests manifest parser. web-platform-tests provides + # a the localpaths.py to do the path manipulation, which we load, + # providing the __file__ variable so it can resolve the relative + # paths correctly. + paths_file = os.path.join( + context.config.topsrcdir, + "testing", + "web-platform", + "tests", + "tools", + "localpaths.py", + ) + _globals = {"__file__": paths_file} + execfile(paths_file, _globals) + import manifest as wptmanifest + finally: + sys.path = old_path + f = context._finder.get(full_path) + try: + rv = wptmanifest.manifest.load(tests_root, f) + except wptmanifest.manifest.ManifestVersionMismatch: + # If we accidentially end up with a committed manifest that's the wrong + # version, then return an empty manifest here just to not break the build + rv = wptmanifest.manifest.Manifest() + return rv |