summaryrefslogtreecommitdiffstats
path: root/python/mozbuild/mozbuild/testing.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/mozbuild/mozbuild/testing.py')
-rw-r--r--python/mozbuild/mozbuild/testing.py266
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