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/.
5 from __future__
import absolute_import
, unicode_literals
12 from mach
.decorators
import (
17 from mozbuild
.base
import (
19 MachCommandConditions
as conditions
,
23 def is_valgrind_build(cls
):
24 '''Must be a build with --enable-valgrind and --disable-jemalloc.'''
25 defines
= cls
.config_environment
.defines
26 return 'MOZ_VALGRIND' in defines
and 'MOZ_MEMORY' not in defines
30 class MachCommands(MachCommandBase
):
35 def __init__(self
, context
):
36 MachCommandBase
.__init
__(self
, context
)
38 @Command('valgrind-test', category
='testing',
39 conditions
=[conditions
.is_firefox
, is_valgrind_build
],
40 description
='Run the Valgrind test job (memory-related errors).')
41 @CommandArgument('--suppressions', default
=[], action
='append',
43 help='Specify a suppression file for Valgrind to use. Use '
44 '--suppression multiple times to specify multiple suppression '
46 def valgrind_test(self
, suppressions
):
48 from mozfile
import TemporaryDirectory
49 from mozhttpd
import MozHttpd
50 from mozprofile
import FirefoxProfile
, Preferences
51 from mozprofile
.permissions
import ServerLocations
52 from mozrunner
import FirefoxRunner
53 from mozrunner
.utils
import findInPath
54 from six
import string_types
55 from valgrind
.output_handler
import OutputHandler
57 build_dir
= os
.path
.join(self
.topsrcdir
, 'build')
59 # XXX: currently we just use the PGO inputs for Valgrind runs. This may
60 # change in the future.
61 httpd
= MozHttpd(docroot
=os
.path
.join(build_dir
, 'pgo'))
62 httpd
.start(block
=False)
64 with
TemporaryDirectory() as profilePath
:
65 # TODO: refactor this into mozprofile
66 profile_data_dir
= os
.path
.join(
67 self
.topsrcdir
, 'testing', 'profiles')
68 with
open(os
.path
.join(profile_data_dir
, 'profiles.json'), 'r') as fh
:
69 base_profiles
= json
.load(fh
)['valgrind']
71 prefpaths
= [os
.path
.join(profile_data_dir
, profile
, 'user.js')
72 for profile
in base_profiles
]
74 for path
in prefpaths
:
75 prefs
.update(Preferences
.read_prefs(path
))
78 'server': '%s:%d' % httpd
.httpd
.server_address
,
80 for k
, v
in prefs
.items():
81 if isinstance(v
, string_types
):
82 v
= v
.format(**interpolation
)
83 prefs
[k
] = Preferences
.cast(v
)
85 quitter
= os
.path
.join(
86 self
.topsrcdir
, 'tools', 'quitter', 'quitter@mozilla.org.xpi')
88 locations
= ServerLocations()
89 locations
.add_host(host
='127.0.0.1',
90 port
=httpd
.httpd
.server_port
,
93 profile
= FirefoxProfile(profile
=profilePath
,
98 firefox_args
= [httpd
.get_url()]
100 env
= os
.environ
.copy()
101 env
['G_SLICE'] = 'always-malloc'
102 env
['MOZ_CC_RUN_DURING_SHUTDOWN'] = '1'
103 env
['MOZ_CRASHREPORTER_NO_REPORT'] = '1'
104 env
['MOZ_DISABLE_NONLOCAL_CONNECTIONS'] = '1'
105 env
['XPCOM_DEBUG_BREAK'] = 'warn'
107 env
.update(self
.extra_environment_variables
)
109 outputHandler
= OutputHandler(self
.log
)
110 kp_kwargs
= {'processOutputLine': [outputHandler
]}
112 valgrind
= 'valgrind'
113 if not os
.path
.exists(valgrind
):
114 valgrind
= findInPath(valgrind
)
119 '--smc-check=all-non-file',
120 '--vex-iropt-register-updates=allregs-at-mem-access',
121 '--gen-suppressions=all',
124 '--show-possibly-lost=no',
125 '--track-origins=yes',
126 '--trace-children=yes',
127 '-v', # Enable verbosity to get the list of used suppressions
128 # Avoid excessive delays in the presence of spinlocks.
131 # Keep debuginfo after library unmap. See bug 1382280.
132 '--keep-debuginfo=yes',
133 # Reduce noise level on rustc and/or LLVM compiled code.
135 '--expensive-definedness-checks=yes',
138 for s
in suppressions
:
139 valgrind_args
.append('--suppressions=' + s
)
141 supps_dir
= os
.path
.join(build_dir
, 'valgrind')
142 supps_file1
= os
.path
.join(supps_dir
, 'cross-architecture.sup')
143 valgrind_args
.append('--suppressions=' + supps_file1
)
145 if mozinfo
.os
== 'linux':
147 'x86_64': 'x86_64-pc-linux-gnu',
148 'x86': 'i386-pc-linux-gnu',
149 }.get(mozinfo
.processor
)
151 supps_file2
= os
.path
.join(supps_dir
, machtype
+ '.sup')
152 if os
.path
.isfile(supps_file2
):
153 valgrind_args
.append('--suppressions=' + supps_file2
)
158 runner
= FirefoxRunner(profile
=profile
,
159 binary
=self
.get_binary_path(),
160 cmdargs
=firefox_args
,
162 process_args
=kp_kwargs
)
163 runner
.start(debug_args
=valgrind_args
)
164 exitcode
= runner
.wait(timeout
=timeout
)
167 errs
= outputHandler
.error_count
168 supps
= outputHandler
.suppression_count
170 status
= 1 # turns the TBPL job orange
171 self
.log(logging
.ERROR
, 'valgrind-fail-parsing',
172 {'errs': errs
, 'supps': supps
},
173 'TEST-UNEXPECTED-FAIL | valgrind-test | error parsing: {errs} errors '
174 'seen, but {supps} generated suppressions seen')
178 self
.log(logging
.INFO
, 'valgrind-pass', {},
179 'TEST-PASS | valgrind-test | valgrind found no errors')
181 status
= 1 # turns the TBPL job orange
182 # We've already printed details of the errors.
185 status
= 2 # turns the TBPL job red
186 self
.log(logging
.ERROR
, 'valgrind-fail-timeout',
187 {'timeout': timeout
},
188 'TEST-UNEXPECTED-FAIL | valgrind-test | Valgrind timed out '
189 '(reached {timeout} second limit)')
191 status
= 2 # turns the TBPL job red
192 self
.log(logging
.ERROR
, 'valgrind-fail-errors',
193 {'exitcode': exitcode
},
194 'TEST-UNEXPECTED-FAIL | valgrind-test | non-zero exit code '
195 'from Valgrind: {exitcode}')