зеркало из https://github.com/mozilla/gecko-dev.git
244 строки
8.9 KiB
Python
244 строки
8.9 KiB
Python
# 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/.
|
|
|
|
from __future__ import absolute_import, unicode_literals
|
|
|
|
import json
|
|
import logging
|
|
import mozinfo
|
|
import os
|
|
|
|
from mach.decorators import (
|
|
Command,
|
|
CommandArgument,
|
|
CommandProvider,
|
|
)
|
|
from mozbuild.base import (
|
|
MachCommandBase,
|
|
MachCommandConditions as conditions,
|
|
BinaryNotFoundException,
|
|
)
|
|
|
|
|
|
def is_valgrind_build(cls):
|
|
"""Must be a build with --enable-valgrind and --disable-jemalloc."""
|
|
defines = cls.config_environment.defines
|
|
return "MOZ_VALGRIND" in defines and "MOZ_MEMORY" not in defines
|
|
|
|
|
|
@CommandProvider
|
|
class MachCommands(MachCommandBase):
|
|
"""
|
|
Run Valgrind tests.
|
|
"""
|
|
|
|
@Command(
|
|
"valgrind-test",
|
|
category="testing",
|
|
conditions=[conditions.is_firefox_or_thunderbird, is_valgrind_build],
|
|
description="Run the Valgrind test job (memory-related errors).",
|
|
)
|
|
@CommandArgument(
|
|
"--suppressions",
|
|
default=[],
|
|
action="append",
|
|
metavar="FILENAME",
|
|
help="Specify a suppression file for Valgrind to use. Use "
|
|
"--suppression multiple times to specify multiple suppression "
|
|
"files.",
|
|
)
|
|
def valgrind_test(self, suppressions):
|
|
|
|
from mozfile import TemporaryDirectory
|
|
from mozhttpd import MozHttpd
|
|
from mozprofile import FirefoxProfile, Preferences
|
|
from mozprofile.permissions import ServerLocations
|
|
from mozrunner import FirefoxRunner
|
|
from mozrunner.utils import findInPath
|
|
from six import string_types
|
|
from valgrind.output_handler import OutputHandler
|
|
|
|
build_dir = os.path.join(self.topsrcdir, "build")
|
|
|
|
# XXX: currently we just use the PGO inputs for Valgrind runs. This may
|
|
# change in the future.
|
|
httpd = MozHttpd(docroot=os.path.join(build_dir, "pgo"))
|
|
httpd.start(block=False)
|
|
|
|
with TemporaryDirectory() as profilePath:
|
|
# TODO: refactor this into mozprofile
|
|
profile_data_dir = os.path.join(self.topsrcdir, "testing", "profiles")
|
|
with open(os.path.join(profile_data_dir, "profiles.json"), "r") as fh:
|
|
base_profiles = json.load(fh)["valgrind"]
|
|
|
|
prefpaths = [
|
|
os.path.join(profile_data_dir, profile, "user.js")
|
|
for profile in base_profiles
|
|
]
|
|
prefs = {}
|
|
for path in prefpaths:
|
|
prefs.update(Preferences.read_prefs(path))
|
|
|
|
interpolation = {
|
|
"server": "%s:%d" % httpd.httpd.server_address,
|
|
}
|
|
for k, v in prefs.items():
|
|
if isinstance(v, string_types):
|
|
v = v.format(**interpolation)
|
|
prefs[k] = Preferences.cast(v)
|
|
|
|
quitter = os.path.join(
|
|
self.topsrcdir, "tools", "quitter", "quitter@mozilla.org.xpi"
|
|
)
|
|
|
|
locations = ServerLocations()
|
|
locations.add_host(
|
|
host="127.0.0.1", port=httpd.httpd.server_port, options="primary"
|
|
)
|
|
|
|
profile = FirefoxProfile(
|
|
profile=profilePath,
|
|
preferences=prefs,
|
|
addons=[quitter],
|
|
locations=locations,
|
|
)
|
|
|
|
firefox_args = [httpd.get_url()]
|
|
|
|
env = os.environ.copy()
|
|
env["G_SLICE"] = "always-malloc"
|
|
env["MOZ_CC_RUN_DURING_SHUTDOWN"] = "1"
|
|
env["MOZ_CRASHREPORTER_NO_REPORT"] = "1"
|
|
env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1"
|
|
env["XPCOM_DEBUG_BREAK"] = "warn"
|
|
|
|
outputHandler = OutputHandler(self.log)
|
|
kp_kwargs = {
|
|
"processOutputLine": [outputHandler],
|
|
"universal_newlines": True,
|
|
}
|
|
|
|
valgrind = "valgrind"
|
|
if not os.path.exists(valgrind):
|
|
valgrind = findInPath(valgrind)
|
|
|
|
valgrind_args = [
|
|
valgrind,
|
|
"--sym-offsets=yes",
|
|
"--smc-check=all-non-file",
|
|
"--vex-iropt-register-updates=allregs-at-mem-access",
|
|
"--gen-suppressions=all",
|
|
"--num-callers=36",
|
|
"--leak-check=full",
|
|
"--show-possibly-lost=no",
|
|
"--track-origins=yes",
|
|
"--trace-children=yes",
|
|
"-v", # Enable verbosity to get the list of used suppressions
|
|
# Avoid excessive delays in the presence of spinlocks.
|
|
# See bug 1309851.
|
|
"--fair-sched=yes",
|
|
# Keep debuginfo after library unmap. See bug 1382280.
|
|
"--keep-debuginfo=yes",
|
|
# Reduce noise level on rustc and/or LLVM compiled code.
|
|
# See bug 1365915
|
|
"--expensive-definedness-checks=yes",
|
|
# Compensate for the compiler inlining `new` but not `delete`
|
|
# or vice versa.
|
|
"--show-mismatched-frees=no",
|
|
]
|
|
|
|
for s in suppressions:
|
|
valgrind_args.append("--suppressions=" + s)
|
|
|
|
supps_dir = os.path.join(build_dir, "valgrind")
|
|
supps_file1 = os.path.join(supps_dir, "cross-architecture.sup")
|
|
valgrind_args.append("--suppressions=" + supps_file1)
|
|
|
|
if mozinfo.os == "linux":
|
|
machtype = {
|
|
"x86_64": "x86_64-pc-linux-gnu",
|
|
"x86": "i386-pc-linux-gnu",
|
|
}.get(mozinfo.processor)
|
|
if machtype:
|
|
supps_file2 = os.path.join(supps_dir, machtype + ".sup")
|
|
if os.path.isfile(supps_file2):
|
|
valgrind_args.append("--suppressions=" + supps_file2)
|
|
|
|
exitcode = None
|
|
timeout = 1800
|
|
binary_not_found_exception = None
|
|
try:
|
|
runner = FirefoxRunner(
|
|
profile=profile,
|
|
binary=self.get_binary_path(),
|
|
cmdargs=firefox_args,
|
|
env=env,
|
|
process_args=kp_kwargs,
|
|
)
|
|
runner.start(debug_args=valgrind_args)
|
|
exitcode = runner.wait(timeout=timeout)
|
|
except BinaryNotFoundException as e:
|
|
binary_not_found_exception = e
|
|
finally:
|
|
errs = outputHandler.error_count
|
|
supps = outputHandler.suppression_count
|
|
if errs != supps:
|
|
status = 1 # turns the TBPL job orange
|
|
self.log(
|
|
logging.ERROR,
|
|
"valgrind-fail-parsing",
|
|
{"errs": errs, "supps": supps},
|
|
"TEST-UNEXPECTED-FAIL | valgrind-test | error parsing: {errs} errors "
|
|
"seen, but {supps} generated suppressions seen",
|
|
)
|
|
|
|
elif errs == 0:
|
|
status = 0
|
|
self.log(
|
|
logging.INFO,
|
|
"valgrind-pass",
|
|
{},
|
|
"TEST-PASS | valgrind-test | valgrind found no errors",
|
|
)
|
|
else:
|
|
status = 1 # turns the TBPL job orange
|
|
# We've already printed details of the errors.
|
|
|
|
if binary_not_found_exception:
|
|
status = 2 # turns the TBPL job red
|
|
self.log(
|
|
logging.ERROR,
|
|
"valgrind-fail-errors",
|
|
{"error": str(binary_not_found_exception)},
|
|
"TEST-UNEXPECTED-FAIL | valgrind-test | {error}",
|
|
)
|
|
self.log(
|
|
logging.INFO,
|
|
"valgrind-fail-errors",
|
|
{"help": binary_not_found_exception.help()},
|
|
"{help}",
|
|
)
|
|
elif exitcode is None:
|
|
status = 2 # turns the TBPL job red
|
|
self.log(
|
|
logging.ERROR,
|
|
"valgrind-fail-timeout",
|
|
{"timeout": timeout},
|
|
"TEST-UNEXPECTED-FAIL | valgrind-test | Valgrind timed out "
|
|
"(reached {timeout} second limit)",
|
|
)
|
|
elif exitcode != 0:
|
|
status = 2 # turns the TBPL job red
|
|
self.log(
|
|
logging.ERROR,
|
|
"valgrind-fail-errors",
|
|
{"exitcode": exitcode},
|
|
"TEST-UNEXPECTED-FAIL | valgrind-test | non-zero exit code "
|
|
"from Valgrind: {exitcode}",
|
|
)
|
|
|
|
httpd.stop()
|
|
|
|
return status
|