michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: # file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: from __future__ import print_function, unicode_literals michael@0: michael@0: import os michael@0: import re michael@0: import subprocess michael@0: michael@0: from mach.decorators import ( michael@0: Command, michael@0: CommandArgument, michael@0: CommandProvider, michael@0: ) michael@0: from mozbuild.base import ( michael@0: MachCommandBase, michael@0: MachCommandConditions as conditions, michael@0: ) michael@0: michael@0: michael@0: def is_valgrind_build(cls): michael@0: '''Must be a build with --enable-valgrind and --disable-jemalloc.''' michael@0: defines = cls.config_environment.defines michael@0: return 'MOZ_VALGRIND' in defines and 'MOZ_MEMORY' not in defines michael@0: michael@0: michael@0: @CommandProvider michael@0: class MachCommands(MachCommandBase): michael@0: ''' michael@0: Run Valgrind tests. michael@0: ''' michael@0: def __init__(self, context): michael@0: MachCommandBase.__init__(self, context) michael@0: michael@0: @Command('valgrind-test', category='testing', michael@0: conditions=[conditions.is_firefox, is_valgrind_build], michael@0: description='Run the Valgrind test job.') michael@0: @CommandArgument('--suppressions', default=[], action='append', michael@0: metavar='FILENAME', michael@0: help='Specify a suppression file for Valgrind to use. Use ' michael@0: '--suppression multiple times to specify multiple suppression ' michael@0: 'files.') michael@0: def valgrind_test(self, suppressions): michael@0: import json michael@0: import sys michael@0: import tempfile michael@0: michael@0: from mozbuild.base import MozbuildObject michael@0: from mozfile import TemporaryDirectory michael@0: from mozhttpd import MozHttpd michael@0: from mozprofile import FirefoxProfile, Preferences michael@0: from mozprofile.permissions import ServerLocations michael@0: from mozrunner import FirefoxRunner michael@0: from mozrunner.utils import findInPath michael@0: from valgrind.output_handler import OutputHandler michael@0: michael@0: build_dir = os.path.join(self.topsrcdir, 'build') michael@0: michael@0: # XXX: currently we just use the PGO inputs for Valgrind runs. This may michael@0: # change in the future. michael@0: httpd = MozHttpd(docroot=os.path.join(build_dir, 'pgo')) michael@0: httpd.start(block=False) michael@0: michael@0: with TemporaryDirectory() as profilePath: michael@0: #TODO: refactor this into mozprofile michael@0: prefpath = os.path.join(self.topsrcdir, 'testing', 'profiles', 'prefs_general.js') michael@0: prefs = {} michael@0: prefs.update(Preferences.read_prefs(prefpath)) michael@0: interpolation = { 'server': '%s:%d' % httpd.httpd.server_address, michael@0: 'OOP': 'false'} michael@0: prefs = json.loads(json.dumps(prefs) % interpolation) michael@0: for pref in prefs: michael@0: prefs[pref] = Preferences.cast(prefs[pref]) michael@0: michael@0: quitter = os.path.join(self.distdir, 'xpi-stage', 'quitter') michael@0: michael@0: locations = ServerLocations() michael@0: locations.add_host(host='127.0.0.1', michael@0: port=httpd.httpd.server_port, michael@0: options='primary') michael@0: michael@0: profile = FirefoxProfile(profile=profilePath, michael@0: preferences=prefs, michael@0: addons=[quitter], michael@0: locations=locations) michael@0: michael@0: firefox_args = [httpd.get_url()] michael@0: michael@0: env = os.environ.copy() michael@0: env['G_SLICE'] = 'always-malloc' michael@0: env['MOZ_CC_RUN_DURING_SHUTDOWN'] = '1' michael@0: env['MOZ_CRASHREPORTER_NO_REPORT'] = '1' michael@0: env['XPCOM_DEBUG_BREAK'] = 'warn' michael@0: michael@0: outputHandler = OutputHandler() michael@0: kp_kwargs = {'processOutputLine': [outputHandler]} michael@0: michael@0: valgrind = 'valgrind' michael@0: if not os.path.exists(valgrind): michael@0: valgrind = findInPath(valgrind) michael@0: michael@0: valgrind_args = [ michael@0: valgrind, michael@0: '--smc-check=all-non-file', michael@0: '--vex-iropt-register-updates=allregs-at-mem-access', michael@0: '--gen-suppressions=all', michael@0: '--num-callers=36', michael@0: '--leak-check=full', michael@0: '--show-possibly-lost=no', michael@0: '--track-origins=yes' michael@0: ] michael@0: michael@0: for s in suppressions: michael@0: valgrind_args.append('--suppressions=' + s) michael@0: michael@0: supps_dir = os.path.join(build_dir, 'valgrind') michael@0: supps_file1 = os.path.join(supps_dir, 'cross-architecture.sup') michael@0: valgrind_args.append('--suppressions=' + supps_file1) michael@0: michael@0: # MACHTYPE is an odd bash-only environment variable that doesn't michael@0: # show up in os.environ, so we have to get it another way. michael@0: machtype = subprocess.check_output(['bash', '-c', 'echo $MACHTYPE']).rstrip() michael@0: supps_file2 = os.path.join(supps_dir, machtype + '.sup') michael@0: if os.path.isfile(supps_file2): michael@0: valgrind_args.append('--suppressions=' + supps_file2) michael@0: michael@0: exitcode = None michael@0: try: michael@0: runner = FirefoxRunner(profile=profile, michael@0: binary=self.get_binary_path(), michael@0: cmdargs=firefox_args, michael@0: env=env, michael@0: kp_kwargs=kp_kwargs) michael@0: runner.start(debug_args=valgrind_args) michael@0: exitcode = runner.wait() michael@0: michael@0: finally: michael@0: errs = outputHandler.error_count michael@0: supps = outputHandler.suppression_count michael@0: if errs != supps: michael@0: status = 1 # turns the TBPL job orange michael@0: print('TEST-UNEXPECTED-FAILURE | valgrind-test | error parsing:', errs, "errors seen, but", supps, "generated suppressions seen") michael@0: michael@0: elif errs == 0: michael@0: status = 0 michael@0: print('TEST-PASS | valgrind-test | valgrind found no errors') michael@0: else: michael@0: status = 1 # turns the TBPL job orange michael@0: # We've already printed details of the errors. michael@0: michael@0: if exitcode != 0: michael@0: status = 2 # turns the TBPL job red michael@0: print('TEST-UNEXPECTED-FAIL | valgrind-test | non-zero exit code from Valgrind') michael@0: michael@0: httpd.stop() michael@0: michael@0: return status