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