summaryrefslogtreecommitdiffstats
path: root/testing/xpcshell/mach_commands.py
diff options
context:
space:
mode:
Diffstat (limited to 'testing/xpcshell/mach_commands.py')
-rw-r--r--testing/xpcshell/mach_commands.py285
1 files changed, 285 insertions, 0 deletions
diff --git a/testing/xpcshell/mach_commands.py b/testing/xpcshell/mach_commands.py
new file mode 100644
index 0000000000..6cb0aaac97
--- /dev/null
+++ b/testing/xpcshell/mach_commands.py
@@ -0,0 +1,285 @@
+# 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/.
+
+# Integrates the xpcshell test runner with mach.
+
+from __future__ import absolute_import, division, print_function, unicode_literals
+
+import errno
+import logging
+import os
+import sys
+
+from mozlog import structured
+
+from mozbuild.base import (
+ MachCommandBase,
+ MozbuildObject,
+ MachCommandConditions as conditions,
+ BinaryNotFoundException,
+)
+
+from mach.decorators import (
+ CommandProvider,
+ Command,
+)
+
+from multiprocessing import cpu_count
+from xpcshellcommandline import parser_desktop, parser_remote
+
+here = os.path.abspath(os.path.dirname(__file__))
+
+
+# This should probably be consolidated with similar classes in other test
+# runners.
+class InvalidTestPathError(Exception):
+ """Exception raised when the test path is not valid."""
+
+
+class XPCShellRunner(MozbuildObject):
+ """Run xpcshell tests."""
+
+ def run_suite(self, **kwargs):
+ return self._run_xpcshell_harness(**kwargs)
+
+ def run_test(self, **kwargs):
+ """Runs an individual xpcshell test."""
+
+ # TODO Bug 794506 remove once mach integrates with virtualenv.
+ build_path = os.path.join(self.topobjdir, "build")
+ if build_path not in sys.path:
+ sys.path.append(build_path)
+
+ src_build_path = os.path.join(self.topsrcdir, "mozilla", "build")
+ if os.path.isdir(src_build_path):
+ sys.path.append(src_build_path)
+
+ return self.run_suite(**kwargs)
+
+ def _run_xpcshell_harness(self, **kwargs):
+ # Obtain a reference to the xpcshell test runner.
+ import runxpcshelltests
+
+ log = kwargs.pop("log")
+
+ xpcshell = runxpcshelltests.XPCShellTests(log=log)
+ self.log_manager.enable_unstructured()
+
+ tests_dir = os.path.join(self.topobjdir, "_tests", "xpcshell")
+ # We want output from the test to be written immediately if we are only
+ # running a single test.
+ single_test = (
+ len(kwargs["testPaths"]) == 1
+ and os.path.isfile(kwargs["testPaths"][0])
+ or kwargs["manifest"]
+ and (len(kwargs["manifest"].test_paths()) == 1)
+ )
+
+ if single_test:
+ kwargs["verbose"] = True
+
+ if kwargs["xpcshell"] is None:
+ try:
+ kwargs["xpcshell"] = self.get_binary_path("xpcshell")
+ except BinaryNotFoundException as e:
+ self.log(
+ logging.ERROR, "xpcshell-test", {"error": str(e)}, "ERROR: {error}"
+ )
+ self.log(logging.INFO, "xpcshell-test", {"help": e.help()}, "{help}")
+ return 1
+
+ if kwargs["mozInfo"] is None:
+ kwargs["mozInfo"] = os.path.join(self.topobjdir, "mozinfo.json")
+
+ if kwargs["symbolsPath"] is None:
+ kwargs["symbolsPath"] = os.path.join(self.distdir, "crashreporter-symbols")
+
+ if kwargs["logfiles"] is None:
+ kwargs["logfiles"] = False
+
+ if kwargs["profileName"] is None:
+ kwargs["profileName"] = "firefox"
+
+ if kwargs["pluginsPath"] is None:
+ kwargs["pluginsPath"] = os.path.join(self.distdir, "plugins")
+
+ if kwargs["testingModulesDir"] is None:
+ kwargs["testingModulesDir"] = os.path.join(self.topobjdir, "_tests/modules")
+
+ if kwargs["utility_path"] is None:
+ kwargs["utility_path"] = self.bindir
+
+ if kwargs["manifest"] is None:
+ kwargs["manifest"] = os.path.join(tests_dir, "xpcshell.ini")
+
+ if kwargs["failure_manifest"] is None:
+ kwargs["failure_manifest"] = os.path.join(
+ self.statedir, "xpcshell.failures.ini"
+ )
+
+ # Use the object directory for the temp directory to minimize the chance
+ # of file scanning. The overhead from e.g. search indexers and anti-virus
+ # scanners like Windows Defender can add tons of overhead to test execution.
+ # We encourage people to disable these things in the object directory.
+ temp_dir = os.path.join(self.topobjdir, "temp")
+ try:
+ os.mkdir(temp_dir)
+ except OSError as e:
+ if e.errno != errno.EEXIST:
+ raise
+ kwargs["tempDir"] = temp_dir
+
+ result = xpcshell.runTests(kwargs)
+
+ self.log_manager.disable_unstructured()
+
+ if not result and not xpcshell.sequential:
+ print(
+ "Tests were run in parallel. Try running with --sequential "
+ "to make sure the failures were not caused by this."
+ )
+ return int(not result)
+
+
+class AndroidXPCShellRunner(MozbuildObject):
+ """Run Android xpcshell tests."""
+
+ def run_test(self, **kwargs):
+ # TODO Bug 794506 remove once mach integrates with virtualenv.
+ build_path = os.path.join(self.topobjdir, "build")
+ if build_path not in sys.path:
+ sys.path.append(build_path)
+
+ import remotexpcshelltests
+
+ log = kwargs.pop("log")
+ self.log_manager.enable_unstructured()
+
+ if kwargs["xpcshell"] is None:
+ kwargs["xpcshell"] = "xpcshell"
+
+ if not kwargs["objdir"]:
+ kwargs["objdir"] = self.topobjdir
+
+ if not kwargs["localBin"]:
+ kwargs["localBin"] = os.path.join(self.topobjdir, "dist/bin")
+
+ if not kwargs["testingModulesDir"]:
+ kwargs["testingModulesDir"] = os.path.join(self.topobjdir, "_tests/modules")
+
+ if not kwargs["mozInfo"]:
+ kwargs["mozInfo"] = os.path.join(self.topobjdir, "mozinfo.json")
+
+ if not kwargs["manifest"]:
+ kwargs["manifest"] = os.path.join(
+ self.topobjdir, "_tests/xpcshell/xpcshell.ini"
+ )
+
+ if not kwargs["symbolsPath"]:
+ kwargs["symbolsPath"] = os.path.join(self.distdir, "crashreporter-symbols")
+
+ if self.substs.get("MOZ_BUILD_APP") == "b2g":
+ kwargs["localAPK"] = None
+ elif not kwargs["localAPK"]:
+ for root, _, paths in os.walk(os.path.join(kwargs["objdir"], "gradle")):
+ for file_name in paths:
+ if file_name.endswith(".apk") and file_name.startswith(
+ "geckoview-withGeckoBinaries"
+ ):
+ kwargs["localAPK"] = os.path.join(root, file_name)
+ print("using APK: %s" % kwargs["localAPK"])
+ break
+ if kwargs["localAPK"]:
+ break
+ else:
+ raise Exception("APK not found in objdir. You must specify an APK.")
+
+ if not kwargs["sequential"]:
+ kwargs["sequential"] = True
+
+ xpcshell = remotexpcshelltests.XPCShellRemote(kwargs, log)
+
+ result = xpcshell.runTests(
+ kwargs,
+ testClass=remotexpcshelltests.RemoteXPCShellTestThread,
+ mobileArgs=xpcshell.mobileArgs,
+ )
+
+ self.log_manager.disable_unstructured()
+
+ return int(not result)
+
+
+def get_parser():
+ build_obj = MozbuildObject.from_environment(cwd=here)
+ if (
+ conditions.is_android(build_obj)
+ or build_obj.substs.get("MOZ_BUILD_APP") == "b2g"
+ ):
+ return parser_remote()
+ else:
+ return parser_desktop()
+
+
+@CommandProvider
+class MachCommands(MachCommandBase):
+ @Command(
+ "xpcshell-test",
+ category="testing",
+ description="Run XPCOM Shell tests (API direct unit testing)",
+ conditions=[lambda *args: True],
+ parser=get_parser,
+ )
+ def run_xpcshell_test(self, test_objects=None, **params):
+ from mozbuild.controller.building import BuildDriver
+
+ if test_objects is not None:
+ from manifestparser import TestManifest
+
+ m = TestManifest()
+ m.tests.extend(test_objects)
+ params["manifest"] = m
+
+ driver = self._spawn(BuildDriver)
+ driver.install_tests()
+
+ # We should probably have a utility function to ensure the tree is
+ # ready to run tests. Until then, we just create the state dir (in
+ # case the tree wasn't built with mach).
+ self._ensure_state_subdir_exists(".")
+
+ if not params.get("log"):
+ log_defaults = {self._mach_context.settings["test"]["format"]: sys.stdout}
+ fmt_defaults = {
+ "level": self._mach_context.settings["test"]["level"],
+ "verbose": True,
+ }
+ params["log"] = structured.commandline.setup_logging(
+ "XPCShellTests", params, log_defaults, fmt_defaults
+ )
+
+ if not params["threadCount"]:
+ # pylint --py3k W1619
+ params["threadCount"] = int((cpu_count() * 3) / 2)
+
+ if conditions.is_android(self) or self.substs.get("MOZ_BUILD_APP") == "b2g":
+ from mozrunner.devices.android_device import (
+ verify_android_device,
+ get_adb_path,
+ )
+
+ device_serial = params.get("deviceSerial")
+ verify_android_device(self, network=True, device_serial=device_serial)
+ if not params["adbPath"]:
+ params["adbPath"] = get_adb_path(self)
+ xpcshell = self._spawn(AndroidXPCShellRunner)
+ else:
+ xpcshell = self._spawn(XPCShellRunner)
+ xpcshell.cwd = self._mach_context.cwd
+
+ try:
+ return xpcshell.run_test(**params)
+ except InvalidTestPathError as e:
+ print(str(e))
+ return 1