tor-browser

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

mach_commands.py (9817B)


      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 import logging
      6 import os
      7 import pathlib
      8 import time
      9 
     10 import mozinfo
     11 from mach.decorators import Command, CommandArgument
     12 from mozbuild.base import BinaryNotFoundException
     13 from mozbuild.base import MachCommandConditions as conditions
     14 from mozfile import json
     15 
     16 
     17 def is_valgrind_build(cls):
     18    """Must be a build with --enable-valgrind and --disable-jemalloc."""
     19    defines = cls.config_environment.defines
     20    return "MOZ_VALGRIND" in defines and "MOZ_MEMORY" not in defines
     21 
     22 
     23 @Command(
     24    "valgrind-test",
     25    category="testing",
     26    conditions=[conditions.is_firefox_or_thunderbird, is_valgrind_build],
     27    description="Run the Valgrind test job (memory-related errors).",
     28 )
     29 @CommandArgument(
     30    "--suppressions",
     31    default=[],
     32    action="append",
     33    metavar="FILENAME",
     34    help="Specify a suppression file for Valgrind to use. Use "
     35    "--suppression multiple times to specify multiple suppression "
     36    "files.",
     37 )
     38 def valgrind_test(command_context, suppressions):
     39    """
     40    Run Valgrind tests.
     41    """
     42 
     43    from mozfile import TemporaryDirectory
     44    from mozhttpd import MozHttpd
     45    from mozprofile import FirefoxProfile, Preferences
     46    from mozprofile.permissions import ServerLocations
     47    from mozrunner import FirefoxRunner
     48    from mozrunner.utils import findInPath
     49    from valgrind.output_handler import OutputHandler
     50 
     51    build_dir = os.path.join(command_context.topsrcdir, "build")
     52 
     53    # XXX: currently we just use the PGO inputs for Valgrind runs.  This may
     54    # change in the future.
     55    httpd = MozHttpd(docroot=os.path.join(build_dir, "pgo"))
     56    httpd.start(block=False)
     57 
     58    with TemporaryDirectory() as profilePath:
     59        # TODO: refactor this into mozprofile
     60        profile_data_dir = os.path.join(
     61            command_context.topsrcdir, "testing", "profiles"
     62        )
     63        with open(os.path.join(profile_data_dir, "profiles.json")) as fh:
     64            base_profiles = json.load(fh)["valgrind"]
     65 
     66        prefpaths = [
     67            os.path.join(profile_data_dir, profile, "user.js")
     68            for profile in base_profiles
     69        ]
     70        prefs = {}
     71        for path in prefpaths:
     72            prefs.update(Preferences.read_prefs(path))
     73 
     74        interpolation = {
     75            "server": "%s:%d" % httpd.httpd.server_address,
     76        }
     77        for k, v in prefs.items():
     78            if isinstance(v, str):
     79                v = v.format(**interpolation)
     80            prefs[k] = Preferences.cast(v)
     81 
     82        quitter = os.path.join(
     83            command_context.topsrcdir, "tools", "quitter", "quitter@mozilla.org.xpi"
     84        )
     85 
     86        locations = ServerLocations()
     87        locations.add_host(
     88            host="127.0.0.1", port=httpd.httpd.server_port, options="primary"
     89        )
     90 
     91        profile = FirefoxProfile(
     92            profile=profilePath,
     93            preferences=prefs,
     94            addons=[quitter],
     95            locations=locations,
     96        )
     97 
     98        firefox_args = [httpd.get_url()]
     99 
    100        env = os.environ.copy()
    101        env["G_SLICE"] = "always-malloc"
    102        env["MOZ_FORCE_DISABLE_E10S"] = "1"
    103        env["MOZ_CC_RUN_DURING_SHUTDOWN"] = "1"
    104        env["MOZ_CRASHREPORTER_NO_REPORT"] = "1"
    105        env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1"
    106        env["XPCOM_DEBUG_BREAK"] = "warn"
    107 
    108        outputHandler = OutputHandler(command_context.log)
    109        kp_kwargs = {
    110            "processOutputLine": [outputHandler],
    111            "universal_newlines": True,
    112        }
    113 
    114        valgrind = "valgrind"
    115        if not os.path.exists(valgrind):
    116            valgrind = findInPath(valgrind)
    117 
    118        valgrind_args = [
    119            valgrind,
    120            "--sym-offsets=yes",
    121            "--smc-check=all-non-file",
    122            "--vex-iropt-register-updates=allregs-at-mem-access",
    123            "--gen-suppressions=all",
    124            "--num-callers=36",
    125            "--leak-check=full",
    126            "--show-possibly-lost=no",
    127            "--track-origins=yes",
    128            "--trace-children=yes",
    129            "--trace-children-skip=*/dbus-launch",
    130            "-v",  # Enable verbosity to get the list of used suppressions
    131            # Avoid excessive delays in the presence of spinlocks.
    132            # See bug 1309851.
    133            "--fair-sched=yes",
    134            # Keep debuginfo after library unmap.  See bug 1382280.
    135            "--keep-debuginfo=yes",
    136            # Reduce noise level on rustc and/or LLVM compiled code.
    137            # See bug 1365915
    138            "--expensive-definedness-checks=yes",
    139            # Compensate for the compiler inlining `new` but not `delete`
    140            # or vice versa.
    141            "--show-mismatched-frees=no",
    142        ]
    143 
    144        for s in suppressions:
    145            valgrind_args.append("--suppressions=" + s)
    146 
    147        supps_dir = os.path.join(build_dir, "valgrind")
    148        supps_file1 = os.path.join(supps_dir, "cross-architecture.sup")
    149        valgrind_args.append("--suppressions=" + supps_file1)
    150 
    151        if mozinfo.os == "linux":
    152            machtype = {
    153                "x86_64": "x86_64-pc-linux-gnu",
    154                "x86": "i386-pc-linux-gnu",
    155            }.get(mozinfo.processor)
    156            if machtype:
    157                supps_file2 = os.path.join(supps_dir, machtype + ".sup")
    158                if os.path.isfile(supps_file2):
    159                    valgrind_args.append("--suppressions=" + supps_file2)
    160 
    161        exitcode = None
    162        timeout = 3600
    163        binary_not_found_exception = None
    164        try:
    165            runner = FirefoxRunner(
    166                profile=profile,
    167                binary=command_context.get_binary_path(),
    168                cmdargs=firefox_args,
    169                env=env,
    170                process_args=kp_kwargs,
    171            )
    172            start_time = time.monotonic()
    173            runner.start(debug_args=valgrind_args)
    174            exitcode = runner.wait(timeout=timeout)
    175            end_time = time.monotonic()
    176            if "MOZ_AUTOMATION" in os.environ:
    177                data = {
    178                    "framework": {"name": "build_metrics"},
    179                    "suites": [
    180                        {
    181                            "name": "valgrind",
    182                            "value": end_time - start_time,
    183                            "lowerIsBetter": True,
    184                            "shouldAlert": False,
    185                            "subtests": [],
    186                        }
    187                    ],
    188                }
    189                if "TASKCLUSTER_INSTANCE_TYPE" in os.environ:
    190                    # Include the instance type so results can be grouped.
    191                    data["suites"][0]["extraOptions"] = [
    192                        "taskcluster-%s" % os.environ["TASKCLUSTER_INSTANCE_TYPE"],
    193                    ]
    194                command_context.log(
    195                    logging.INFO,
    196                    "valgrind-perfherder",
    197                    {"data": json.dumps(data)},
    198                    "PERFHERDER_DATA: {data}",
    199                )
    200                upload_path = pathlib.Path(os.environ.get("MOZ_PERFHERDER_UPLOAD"))
    201                upload_path.parent.mkdir(parents=True, exist_ok=True)
    202                with upload_path.open("w", encoding="utf-8") as f:
    203                    json.dump(data, f)
    204 
    205        except BinaryNotFoundException as e:
    206            binary_not_found_exception = e
    207        finally:
    208            errs = outputHandler.error_count
    209            supps = outputHandler.suppression_count
    210            if errs != supps:
    211                status = 1  # turns the TBPL job orange
    212                command_context.log(
    213                    logging.ERROR,
    214                    "valgrind-fail-parsing",
    215                    {"errs": errs, "supps": supps},
    216                    "TEST-UNEXPECTED-FAIL | valgrind-test | error parsing: {errs} errors "
    217                    "seen, but {supps} generated suppressions seen",
    218                )
    219 
    220            elif errs == 0:
    221                status = 0
    222                command_context.log(
    223                    logging.INFO,
    224                    "valgrind-pass",
    225                    {},
    226                    "TEST-PASS | valgrind-test | valgrind found no errors",
    227                )
    228            else:
    229                status = 1  # turns the TBPL job orange
    230                # We've already printed details of the errors.
    231 
    232            if binary_not_found_exception:
    233                status = 2  # turns the TBPL job red
    234                command_context.log(
    235                    logging.ERROR,
    236                    "valgrind-fail-errors",
    237                    {"error": str(binary_not_found_exception)},
    238                    "TEST-UNEXPECTED-FAIL | valgrind-test | {error}",
    239                )
    240                command_context.log(
    241                    logging.INFO,
    242                    "valgrind-fail-errors",
    243                    {"help": binary_not_found_exception.help()},
    244                    "{help}",
    245                )
    246            elif exitcode is None:
    247                status = 2  # turns the TBPL job red
    248                command_context.log(
    249                    logging.ERROR,
    250                    "valgrind-fail-timeout",
    251                    {"timeout": timeout},
    252                    "TEST-UNEXPECTED-FAIL | valgrind-test | Valgrind timed out "
    253                    "(reached {timeout} second limit)",
    254                )
    255            elif exitcode != 0:
    256                status = 2  # turns the TBPL job red
    257                command_context.log(
    258                    logging.ERROR,
    259                    "valgrind-fail-errors",
    260                    {"exitcode": exitcode},
    261                    "TEST-UNEXPECTED-FAIL | valgrind-test | non-zero exit code "
    262                    "from Valgrind: {exitcode}",
    263                )
    264 
    265            httpd.stop()
    266 
    267        return status