tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

mach_commands.py (11740B)


      1 # This Source Code Form is subject to the terms of the Mozilla Public
      2 # License, v. 2.0. If a copy of the MPL was not distributed with this
      3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
      4 
      5 # Integrates the xpcshell test runner with mach.
      6 
      7 import errno
      8 import logging
      9 import os
     10 import sys
     11 
     12 from mach.decorators import Command
     13 from mozbuild.base import BinaryNotFoundException, MozbuildObject
     14 from mozbuild.base import MachCommandConditions as conditions
     15 from mozbuild.util import cpu_count, macos_performance_cores
     16 from mozlog import structured
     17 from xpcshellcommandline import parser_desktop, parser_remote
     18 
     19 here = os.path.abspath(os.path.dirname(__file__))
     20 
     21 
     22 # This should probably be consolidated with similar classes in other test
     23 # runners.
     24 class InvalidTestPathError(Exception):
     25    """Exception raised when the test path is not valid."""
     26 
     27 
     28 class XPCShellRunner(MozbuildObject):
     29    """Run xpcshell tests."""
     30 
     31    def run_suite(self, **kwargs):
     32        return self._run_xpcshell_harness(**kwargs)
     33 
     34    def run_test(self, **kwargs):
     35        """Runs an individual xpcshell test."""
     36 
     37        # TODO Bug 794506 remove once mach integrates with virtualenv.
     38        build_path = os.path.join(self.topobjdir, "build")
     39        if build_path not in sys.path:
     40            sys.path.append(build_path)
     41 
     42        src_build_path = os.path.join(self.topsrcdir, "mozilla", "build")
     43        if os.path.isdir(src_build_path):
     44            sys.path.append(src_build_path)
     45 
     46        return self.run_suite(**kwargs)
     47 
     48    def _run_xpcshell_harness(self, **kwargs):
     49        # Obtain a reference to the xpcshell test runner.
     50        import runxpcshelltests
     51 
     52        log = kwargs.pop("log")
     53 
     54        xpcshell = runxpcshelltests.XPCShellTests(log=log)
     55        self.log_manager.enable_unstructured()
     56 
     57        tests_dir = os.path.join(self.topobjdir, "_tests", "xpcshell")
     58        # We want output from the test to be written immediately if we are only
     59        # running a single test.
     60        single_test = (
     61            len(kwargs["testPaths"]) == 1
     62            and os.path.isfile(kwargs["testPaths"][0])
     63            or kwargs["manifest"]
     64            and (len(kwargs["manifest"].test_paths()) == 1)
     65        )
     66 
     67        if single_test:
     68            kwargs["verbose"] = True
     69 
     70        if kwargs["xpcshell"] is None:
     71            try:
     72                kwargs["xpcshell"] = self.get_binary_path("xpcshell")
     73            except BinaryNotFoundException as e:
     74                self.log(
     75                    logging.ERROR, "xpcshell-test", {"error": str(e)}, "ERROR: {error}"
     76                )
     77                self.log(logging.INFO, "xpcshell-test", {"help": e.help()}, "{help}")
     78                return 1
     79 
     80        if kwargs["mozInfo"] is None:
     81            kwargs["mozInfo"] = os.path.join(self.topobjdir, "mozinfo.json")
     82 
     83        if kwargs["symbolsPath"] is None:
     84            kwargs["symbolsPath"] = os.path.join(self.distdir, "crashreporter-symbols")
     85 
     86        if kwargs["logfiles"] is None:
     87            kwargs["logfiles"] = False
     88 
     89        if kwargs["profileName"] is None:
     90            kwargs["profileName"] = "firefox"
     91 
     92        if kwargs["testingModulesDir"] is None:
     93            kwargs["testingModulesDir"] = os.path.join(self.topobjdir, "_tests/modules")
     94 
     95        if kwargs["utility_path"] is None:
     96            kwargs["utility_path"] = self.bindir
     97 
     98        if kwargs["manifest"] is None:
     99            kwargs["manifest"] = os.path.join(tests_dir, "xpcshell.toml")
    100 
    101        if kwargs["failure_manifest"] is None:
    102            kwargs["failure_manifest"] = os.path.join(
    103                self.statedir, "xpcshell.failures.toml"
    104            )
    105 
    106        # Use the object directory for the temp directory to minimize the chance
    107        # of file scanning. The overhead from e.g. search indexers and anti-virus
    108        # scanners like Windows Defender can add tons of overhead to test execution.
    109        # We encourage people to disable these things in the object directory.
    110        temp_dir = os.path.join(self.topobjdir, "temp")
    111        try:
    112            os.mkdir(temp_dir)
    113        except OSError as e:
    114            if e.errno != errno.EEXIST:
    115                raise
    116        kwargs["tempDir"] = temp_dir
    117 
    118        result = xpcshell.runTests(kwargs)
    119 
    120        self.log_manager.disable_unstructured()
    121 
    122        if not result and not xpcshell.sequential:
    123            print(
    124                "Tests were run in parallel. Try running with --sequential "
    125                "to make sure the failures were not caused by this."
    126            )
    127        return int(not result)
    128 
    129 
    130 class AndroidXPCShellRunner(MozbuildObject):
    131    """Run Android xpcshell tests."""
    132 
    133    def run_test(self, **kwargs):
    134        # TODO Bug 794506 remove once mach integrates with virtualenv.
    135        build_path = os.path.join(self.topobjdir, "build")
    136        if build_path not in sys.path:
    137            sys.path.append(build_path)
    138 
    139        import remotexpcshelltests
    140 
    141        log = kwargs.pop("log")
    142        self.log_manager.enable_unstructured()
    143 
    144        if kwargs["xpcshell"] is None:
    145            kwargs["xpcshell"] = "xpcshell"
    146 
    147        if not kwargs["objdir"]:
    148            kwargs["objdir"] = self.topobjdir
    149 
    150        if not kwargs["localBin"]:
    151            kwargs["localBin"] = os.path.join(self.topobjdir, "dist/bin")
    152 
    153        if not kwargs["testingModulesDir"]:
    154            kwargs["testingModulesDir"] = os.path.join(self.topobjdir, "_tests/modules")
    155 
    156        if not kwargs["mozInfo"]:
    157            kwargs["mozInfo"] = os.path.join(self.topobjdir, "mozinfo.json")
    158 
    159        if not kwargs["manifest"]:
    160            kwargs["manifest"] = os.path.join(
    161                self.topobjdir, "_tests/xpcshell/xpcshell.toml"
    162            )
    163 
    164        if not kwargs["symbolsPath"]:
    165            kwargs["symbolsPath"] = os.path.join(self.distdir, "crashreporter-symbols")
    166 
    167        if not kwargs["localAPK"]:
    168            for root, _, paths in os.walk(os.path.join(kwargs["objdir"], "gradle")):
    169                for file_name in paths:
    170                    if file_name.endswith(".apk") and file_name.startswith(
    171                        "test_runner"
    172                    ):
    173                        kwargs["localAPK"] = os.path.join(root, file_name)
    174                        print("using APK: %s" % kwargs["localAPK"])
    175                        break
    176                if kwargs["localAPK"]:
    177                    break
    178            else:
    179                raise Exception("APK not found in objdir. You must specify an APK.")
    180 
    181        if not kwargs["xrePath"]:
    182            MOZ_HOST_BIN = os.environ.get("MOZ_HOST_BIN")
    183            if MOZ_HOST_BIN:
    184                kwargs["xrePath"] = MOZ_HOST_BIN
    185 
    186        xpcshell = remotexpcshelltests.XPCShellRemote(kwargs, log)
    187 
    188        result = xpcshell.runTests(
    189            kwargs,
    190            testClass=remotexpcshelltests.RemoteXPCShellTestThread,
    191            mobileArgs=xpcshell.mobileArgs,
    192        )
    193 
    194        self.log_manager.disable_unstructured()
    195 
    196        return int(not result)
    197 
    198 
    199 def get_parser():
    200    build_obj = MozbuildObject.from_environment(cwd=here)
    201    if conditions.is_android(build_obj):
    202        return parser_remote()
    203    else:
    204        return parser_desktop()
    205 
    206 
    207 @Command(
    208    "xpcshell-test",
    209    category="testing",
    210    description="Run XPCOM Shell tests (API direct unit testing)",
    211    conditions=[lambda *args: True],
    212    parser=get_parser,
    213 )
    214 def run_xpcshell_test(command_context, test_objects=None, **params):
    215    from mozbuild.controller.building import BuildDriver
    216    from mozlog.handlers import ResourceHandler
    217 
    218    if test_objects is not None:
    219        from manifestparser import TestManifest
    220 
    221        m = TestManifest()
    222        m.tests.extend(test_objects)
    223        params["manifest"] = m
    224 
    225    driver = command_context._spawn(BuildDriver)
    226    driver.install_tests()
    227 
    228    # We should probably have a utility function to ensure the tree is
    229    # ready to run tests. Until then, we just create the state dir (in
    230    # case the tree wasn't built with mach).
    231    command_context._ensure_state_subdir_exists(".")
    232 
    233    created_logger = False
    234    if not params.get("log"):
    235        log_defaults = {
    236            command_context._mach_context.settings["test"]["format"]: sys.stdout
    237        }
    238        fmt_defaults = {
    239            "level": command_context._mach_context.settings["test"]["level"],
    240            "verbose": True,
    241        }
    242        params["log"] = structured.commandline.setup_logging(
    243            "XPCShellTests", params, log_defaults, fmt_defaults
    244        )
    245        created_logger = True
    246        params["log"].add_handler(ResourceHandler(command_context))
    247 
    248    if not params["threadCount"]:
    249        if sys.platform == "darwin":
    250            # On Apple Silicon, we have found that increasing the number of
    251            # threads (processes) above the CPU count reduces the performance
    252            # (bug 1917833), and makes the machine less performant. It is even
    253            # better if we can use the exact number of performance cores, so we
    254            # attempt to do that here.
    255            perf_cores = macos_performance_cores()
    256            if perf_cores > 0:
    257                params["threadCount"] = perf_cores
    258            else:
    259                params["threadCount"] = int((cpu_count() * 3) / 2)
    260        else:
    261            # pylint --py3k W1619
    262            params["threadCount"] = int((cpu_count() * 3) / 2)
    263 
    264    if conditions.is_android(command_context):
    265        from mozrunner.devices.android_device import (
    266            InstallIntent,
    267            get_adb_path,
    268            verify_android_device,
    269        )
    270 
    271        install = InstallIntent.YES if params["setup"] else InstallIntent.NO
    272        device_serial = params.get("deviceSerial")
    273        verify_android_device(
    274            command_context,
    275            network=True,
    276            install=install,
    277            device_serial=device_serial,
    278        )
    279        if not params["adbPath"]:
    280            params["adbPath"] = get_adb_path(command_context)
    281        xpcshell = command_context._spawn(AndroidXPCShellRunner)
    282    else:
    283        xpcshell = command_context._spawn(XPCShellRunner)
    284    xpcshell.cwd = command_context._mach_context.cwd
    285 
    286    if sys.platform == "linux":
    287        install_portal_test_dependencies = False
    288        if "manifest" in params and params["manifest"]:
    289            # When run from "mach test", the manifest is available now.
    290            try:
    291                tags = " ".join(params["manifest"].get("tags")).split(" ")
    292            except KeyError:
    293                # .get("tags") may raise KeyError.
    294                tags = []
    295            if "webextensions" in tags and "portal" in tags:
    296                install_portal_test_dependencies = True
    297        else:
    298            # When run from "mach xpcshell-test", the manifest is not available
    299            # yet. We could default to True to force the initialization of the
    300            # virtualenv, but that would force this dependency on every use of
    301            # "mach xpcshell-test". So for now, force it to False.
    302            # If a dev wants to run the test, they can run "mach test" instead.
    303            install_portal_test_dependencies = False
    304 
    305        if install_portal_test_dependencies:
    306            dir_relpath = params["manifest"].get("dir_relpath")[0]
    307            # Only Linux Native Messaging Portal xpcshell tests need this.
    308            req = os.path.join(
    309                dir_relpath,
    310                "linux_native-messaging-portal_requirements.txt",
    311            )
    312            command_context.virtualenv_manager.activate()
    313            command_context.virtualenv_manager.install_pip_requirements(
    314                req, require_hashes=False
    315            )
    316 
    317    try:
    318        return xpcshell.run_test(**params)
    319    except InvalidTestPathError as e:
    320        print(str(e))
    321        return 1
    322    finally:
    323        if created_logger:
    324            params["log"].shutdown()