Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | # This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
michael@0 | 4 | |
michael@0 | 5 | from __future__ import print_function, unicode_literals |
michael@0 | 6 | |
michael@0 | 7 | import os |
michael@0 | 8 | import re |
michael@0 | 9 | import subprocess |
michael@0 | 10 | |
michael@0 | 11 | from mach.decorators import ( |
michael@0 | 12 | Command, |
michael@0 | 13 | CommandArgument, |
michael@0 | 14 | CommandProvider, |
michael@0 | 15 | ) |
michael@0 | 16 | from mozbuild.base import ( |
michael@0 | 17 | MachCommandBase, |
michael@0 | 18 | MachCommandConditions as conditions, |
michael@0 | 19 | ) |
michael@0 | 20 | |
michael@0 | 21 | |
michael@0 | 22 | def is_valgrind_build(cls): |
michael@0 | 23 | '''Must be a build with --enable-valgrind and --disable-jemalloc.''' |
michael@0 | 24 | defines = cls.config_environment.defines |
michael@0 | 25 | return 'MOZ_VALGRIND' in defines and 'MOZ_MEMORY' not in defines |
michael@0 | 26 | |
michael@0 | 27 | |
michael@0 | 28 | @CommandProvider |
michael@0 | 29 | class MachCommands(MachCommandBase): |
michael@0 | 30 | ''' |
michael@0 | 31 | Run Valgrind tests. |
michael@0 | 32 | ''' |
michael@0 | 33 | def __init__(self, context): |
michael@0 | 34 | MachCommandBase.__init__(self, context) |
michael@0 | 35 | |
michael@0 | 36 | @Command('valgrind-test', category='testing', |
michael@0 | 37 | conditions=[conditions.is_firefox, is_valgrind_build], |
michael@0 | 38 | description='Run the Valgrind test job.') |
michael@0 | 39 | @CommandArgument('--suppressions', default=[], action='append', |
michael@0 | 40 | metavar='FILENAME', |
michael@0 | 41 | help='Specify a suppression file for Valgrind to use. Use ' |
michael@0 | 42 | '--suppression multiple times to specify multiple suppression ' |
michael@0 | 43 | 'files.') |
michael@0 | 44 | def valgrind_test(self, suppressions): |
michael@0 | 45 | import json |
michael@0 | 46 | import sys |
michael@0 | 47 | import tempfile |
michael@0 | 48 | |
michael@0 | 49 | from mozbuild.base import MozbuildObject |
michael@0 | 50 | from mozfile import TemporaryDirectory |
michael@0 | 51 | from mozhttpd import MozHttpd |
michael@0 | 52 | from mozprofile import FirefoxProfile, Preferences |
michael@0 | 53 | from mozprofile.permissions import ServerLocations |
michael@0 | 54 | from mozrunner import FirefoxRunner |
michael@0 | 55 | from mozrunner.utils import findInPath |
michael@0 | 56 | from valgrind.output_handler import OutputHandler |
michael@0 | 57 | |
michael@0 | 58 | build_dir = os.path.join(self.topsrcdir, 'build') |
michael@0 | 59 | |
michael@0 | 60 | # XXX: currently we just use the PGO inputs for Valgrind runs. This may |
michael@0 | 61 | # change in the future. |
michael@0 | 62 | httpd = MozHttpd(docroot=os.path.join(build_dir, 'pgo')) |
michael@0 | 63 | httpd.start(block=False) |
michael@0 | 64 | |
michael@0 | 65 | with TemporaryDirectory() as profilePath: |
michael@0 | 66 | #TODO: refactor this into mozprofile |
michael@0 | 67 | prefpath = os.path.join(self.topsrcdir, 'testing', 'profiles', 'prefs_general.js') |
michael@0 | 68 | prefs = {} |
michael@0 | 69 | prefs.update(Preferences.read_prefs(prefpath)) |
michael@0 | 70 | interpolation = { 'server': '%s:%d' % httpd.httpd.server_address, |
michael@0 | 71 | 'OOP': 'false'} |
michael@0 | 72 | prefs = json.loads(json.dumps(prefs) % interpolation) |
michael@0 | 73 | for pref in prefs: |
michael@0 | 74 | prefs[pref] = Preferences.cast(prefs[pref]) |
michael@0 | 75 | |
michael@0 | 76 | quitter = os.path.join(self.distdir, 'xpi-stage', 'quitter') |
michael@0 | 77 | |
michael@0 | 78 | locations = ServerLocations() |
michael@0 | 79 | locations.add_host(host='127.0.0.1', |
michael@0 | 80 | port=httpd.httpd.server_port, |
michael@0 | 81 | options='primary') |
michael@0 | 82 | |
michael@0 | 83 | profile = FirefoxProfile(profile=profilePath, |
michael@0 | 84 | preferences=prefs, |
michael@0 | 85 | addons=[quitter], |
michael@0 | 86 | locations=locations) |
michael@0 | 87 | |
michael@0 | 88 | firefox_args = [httpd.get_url()] |
michael@0 | 89 | |
michael@0 | 90 | env = os.environ.copy() |
michael@0 | 91 | env['G_SLICE'] = 'always-malloc' |
michael@0 | 92 | env['MOZ_CC_RUN_DURING_SHUTDOWN'] = '1' |
michael@0 | 93 | env['MOZ_CRASHREPORTER_NO_REPORT'] = '1' |
michael@0 | 94 | env['XPCOM_DEBUG_BREAK'] = 'warn' |
michael@0 | 95 | |
michael@0 | 96 | outputHandler = OutputHandler() |
michael@0 | 97 | kp_kwargs = {'processOutputLine': [outputHandler]} |
michael@0 | 98 | |
michael@0 | 99 | valgrind = 'valgrind' |
michael@0 | 100 | if not os.path.exists(valgrind): |
michael@0 | 101 | valgrind = findInPath(valgrind) |
michael@0 | 102 | |
michael@0 | 103 | valgrind_args = [ |
michael@0 | 104 | valgrind, |
michael@0 | 105 | '--smc-check=all-non-file', |
michael@0 | 106 | '--vex-iropt-register-updates=allregs-at-mem-access', |
michael@0 | 107 | '--gen-suppressions=all', |
michael@0 | 108 | '--num-callers=36', |
michael@0 | 109 | '--leak-check=full', |
michael@0 | 110 | '--show-possibly-lost=no', |
michael@0 | 111 | '--track-origins=yes' |
michael@0 | 112 | ] |
michael@0 | 113 | |
michael@0 | 114 | for s in suppressions: |
michael@0 | 115 | valgrind_args.append('--suppressions=' + s) |
michael@0 | 116 | |
michael@0 | 117 | supps_dir = os.path.join(build_dir, 'valgrind') |
michael@0 | 118 | supps_file1 = os.path.join(supps_dir, 'cross-architecture.sup') |
michael@0 | 119 | valgrind_args.append('--suppressions=' + supps_file1) |
michael@0 | 120 | |
michael@0 | 121 | # MACHTYPE is an odd bash-only environment variable that doesn't |
michael@0 | 122 | # show up in os.environ, so we have to get it another way. |
michael@0 | 123 | machtype = subprocess.check_output(['bash', '-c', 'echo $MACHTYPE']).rstrip() |
michael@0 | 124 | supps_file2 = os.path.join(supps_dir, machtype + '.sup') |
michael@0 | 125 | if os.path.isfile(supps_file2): |
michael@0 | 126 | valgrind_args.append('--suppressions=' + supps_file2) |
michael@0 | 127 | |
michael@0 | 128 | exitcode = None |
michael@0 | 129 | try: |
michael@0 | 130 | runner = FirefoxRunner(profile=profile, |
michael@0 | 131 | binary=self.get_binary_path(), |
michael@0 | 132 | cmdargs=firefox_args, |
michael@0 | 133 | env=env, |
michael@0 | 134 | kp_kwargs=kp_kwargs) |
michael@0 | 135 | runner.start(debug_args=valgrind_args) |
michael@0 | 136 | exitcode = runner.wait() |
michael@0 | 137 | |
michael@0 | 138 | finally: |
michael@0 | 139 | errs = outputHandler.error_count |
michael@0 | 140 | supps = outputHandler.suppression_count |
michael@0 | 141 | if errs != supps: |
michael@0 | 142 | status = 1 # turns the TBPL job orange |
michael@0 | 143 | print('TEST-UNEXPECTED-FAILURE | valgrind-test | error parsing:', errs, "errors seen, but", supps, "generated suppressions seen") |
michael@0 | 144 | |
michael@0 | 145 | elif errs == 0: |
michael@0 | 146 | status = 0 |
michael@0 | 147 | print('TEST-PASS | valgrind-test | valgrind found no errors') |
michael@0 | 148 | else: |
michael@0 | 149 | status = 1 # turns the TBPL job orange |
michael@0 | 150 | # We've already printed details of the errors. |
michael@0 | 151 | |
michael@0 | 152 | if exitcode != 0: |
michael@0 | 153 | status = 2 # turns the TBPL job red |
michael@0 | 154 | print('TEST-UNEXPECTED-FAIL | valgrind-test | non-zero exit code from Valgrind') |
michael@0 | 155 | |
michael@0 | 156 | httpd.stop() |
michael@0 | 157 | |
michael@0 | 158 | return status |