blob: c8c27524b9062353989b3dfe4d7f20494271068f [file] [log] [blame]
[email protected]0a88a652012-03-09 00:34:451#!/usr/bin/env python
2# Copyright (c) 2012 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Sets environment variables needed to run a chromium unit test."""
7
Trent Aptedb1852432018-01-25 02:15:108import io
[email protected]0a88a652012-03-09 00:34:459import os
Ben Pastenee5ece812018-06-20 22:39:3410import signal
[email protected]76361be452012-08-30 22:48:1411import stat
[email protected]0a88a652012-03-09 00:34:4512import subprocess
13import sys
Trent Aptedb1852432018-01-25 02:15:1014import time
[email protected]0a88a652012-03-09 00:34:4515
16# This is hardcoded to be src/ relative to this script.
17ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
18
[email protected]76361be452012-08-30 22:48:1419CHROME_SANDBOX_ENV = 'CHROME_DEVEL_SANDBOX'
20CHROME_SANDBOX_PATH = '/opt/chromium/chrome_sandbox'
21
22
earthdok748e1352015-01-27 23:04:0823def get_sandbox_env(env):
24 """Returns the environment flags needed for the SUID sandbox to work."""
John Abd-El-Malek14ae0542014-10-15 17:52:3125 extra_env = {}
[email protected]76361be452012-08-30 22:48:1426 chrome_sandbox_path = env.get(CHROME_SANDBOX_ENV, CHROME_SANDBOX_PATH)
earthdok748e1352015-01-27 23:04:0827 # The above would silently disable the SUID sandbox if the env value were
28 # an empty string. We don't want to allow that. http://crbug.com/245376
29 # TODO(jln): Remove this check once it's no longer possible to disable the
30 # sandbox that way.
31 if not chrome_sandbox_path:
32 chrome_sandbox_path = CHROME_SANDBOX_PATH
33 extra_env[CHROME_SANDBOX_ENV] = chrome_sandbox_path
John Abd-El-Malek14ae0542014-10-15 17:52:3134
35 return extra_env
36
37
38def trim_cmd(cmd):
39 """Removes internal flags from cmd since they're just used to communicate from
40 the host machine to this script running on the swarm slaves."""
earthdokeeb065302015-02-04 18:18:0441 sanitizers = ['asan', 'lsan', 'msan', 'tsan']
42 internal_flags = frozenset('--%s=%d' % (name, value)
43 for name in sanitizers
44 for value in [0, 1])
John Abd-El-Malek14ae0542014-10-15 17:52:3145 return [i for i in cmd if i not in internal_flags]
[email protected]76361be452012-08-30 22:48:1446
[email protected]0a88a652012-03-09 00:34:4547
[email protected]4bf4d632012-05-31 15:50:3048def fix_python_path(cmd):
49 """Returns the fixed command line to call the right python executable."""
50 out = cmd[:]
51 if out[0] == 'python':
52 out[0] = sys.executable
53 elif out[0].endswith('.py'):
54 out.insert(0, sys.executable)
55 return out
56
57
pcc46233c22017-06-20 22:11:4158def get_sanitizer_env(cmd, asan, lsan, msan, tsan, cfi_diag):
earthdokeeb065302015-02-04 18:18:0459 """Returns the envirnoment flags needed for sanitizer tools."""
John Abd-El-Malek14ae0542014-10-15 17:52:3160
61 extra_env = {}
62
earthdokeeb065302015-02-04 18:18:0463 # Instruct GTK to use malloc while running sanitizer-instrumented tests.
earthdoke6c54a42014-10-16 18:02:3664 extra_env['G_SLICE'] = 'always-malloc'
John Abd-El-Malek14ae0542014-10-15 17:52:3165
66 extra_env['NSS_DISABLE_ARENA_FREE_LIST'] = '1'
67 extra_env['NSS_DISABLE_UNLOAD'] = '1'
68
69 # TODO(glider): remove the symbolizer path once
70 # https://code.google.com/p/address-sanitizer/issues/detail?id=134 is fixed.
Nico Weber19097312016-01-29 18:13:1571 symbolizer_path = os.path.join(ROOT_DIR,
72 'third_party', 'llvm-build', 'Release+Asserts', 'bin', 'llvm-symbolizer')
John Abd-El-Malek14ae0542014-10-15 17:52:3173
earthdokeeb065302015-02-04 18:18:0474 if lsan or tsan:
John Abd-El-Malek14ae0542014-10-15 17:52:3175 # LSan is not sandbox-compatible, so we can use online symbolization. In
76 # fact, it needs symbolization to be able to apply suppressions.
77 symbolization_options = ['symbolize=1',
78 'external_symbolizer_path=%s' % symbolizer_path]
pcc46233c22017-06-20 22:11:4179 elif (asan or msan or cfi_diag) and sys.platform not in ['win32', 'cygwin']:
timurrrr064f9522015-02-12 15:46:3280 # ASan uses a script for offline symbolization, except on Windows.
John Abd-El-Malek14ae0542014-10-15 17:52:3181 # Important note: when running ASan with leak detection enabled, we must use
82 # the LSan symbolization options above.
83 symbolization_options = ['symbolize=0']
vadimsh5cf3c442014-10-20 10:22:3884 # Set the path to llvm-symbolizer to be used by asan_symbolize.py
85 extra_env['LLVM_SYMBOLIZER_PATH'] = symbolizer_path
timurrrr7e67df92015-02-12 21:50:5286 else:
87 symbolization_options = []
John Abd-El-Malek14ae0542014-10-15 17:52:3188
Yves Gerey2e221522019-03-13 08:46:1989 # Leverage sanitizer to print stack trace on abort (e.g. assertion failure).
90 symbolization_options.append('handle_abort=1')
91
earthdokeeb065302015-02-04 18:18:0492 if asan:
93 asan_options = symbolization_options[:]
94 if lsan:
95 asan_options.append('detect_leaks=1')
John Abd-El-Malek14ae0542014-10-15 17:52:3196
timurrrr7e67df92015-02-12 21:50:5297 if asan_options:
98 extra_env['ASAN_OPTIONS'] = ' '.join(asan_options)
John Abd-El-Malek14ae0542014-10-15 17:52:3199
earthdokeeb065302015-02-04 18:18:04100 if sys.platform == 'darwin':
101 isolate_output_dir = os.path.abspath(os.path.dirname(cmd[0]))
102 # This is needed because the test binary has @executable_path embedded in
103 # it that the OS tries to resolve to the cache directory and not the
104 # mapped directory.
105 extra_env['DYLD_LIBRARY_PATH'] = str(isolate_output_dir)
106
107 if lsan:
108 if asan or msan:
109 lsan_options = []
110 else:
111 lsan_options = symbolization_options[:]
112 if sys.platform == 'linux2':
113 # Use the debug version of libstdc++ under LSan. If we don't, there will
114 # be a lot of incomplete stack traces in the reports.
115 extra_env['LD_LIBRARY_PATH'] = '/usr/lib/x86_64-linux-gnu/debug:'
116
earthdokeeb065302015-02-04 18:18:04117 extra_env['LSAN_OPTIONS'] = ' '.join(lsan_options)
118
119 if msan:
120 msan_options = symbolization_options[:]
121 if lsan:
122 msan_options.append('detect_leaks=1')
123 extra_env['MSAN_OPTIONS'] = ' '.join(msan_options)
124
125 if tsan:
126 tsan_options = symbolization_options[:]
127 extra_env['TSAN_OPTIONS'] = ' '.join(tsan_options)
John Abd-El-Malek14ae0542014-10-15 17:52:31128
pcc46233c22017-06-20 22:11:41129 # CFI uses the UBSan runtime to provide diagnostics.
130 if cfi_diag:
131 ubsan_options = symbolization_options[:] + ['print_stacktrace=1']
132 extra_env['UBSAN_OPTIONS'] = ' '.join(ubsan_options)
133
John Abd-El-Malek14ae0542014-10-15 17:52:31134 return extra_env
135
136
glider9d919342015-02-06 17:42:27137def get_sanitizer_symbolize_command(json_path=None, executable_path=None):
earthdok9bd5d582015-01-29 21:19:36138 """Construct the command to invoke offline symbolization script."""
Nico Weber19097312016-01-29 18:13:15139 script_path = os.path.join(
140 ROOT_DIR, 'tools', 'valgrind', 'asan', 'asan_symbolize.py')
earthdok9bd5d582015-01-29 21:19:36141 cmd = [sys.executable, script_path]
142 if json_path is not None:
143 cmd.append('--test-summary-json-file=%s' % json_path)
glider9d919342015-02-06 17:42:27144 if executable_path is not None:
145 cmd.append('--executable-path=%s' % executable_path)
earthdok9bd5d582015-01-29 21:19:36146 return cmd
147
148
149def get_json_path(cmd):
150 """Extract the JSON test summary path from a command line."""
151 json_path_flag = '--test-launcher-summary-output='
152 for arg in cmd:
153 if arg.startswith(json_path_flag):
154 return arg.split(json_path_flag).pop()
155 return None
156
157
158def symbolize_snippets_in_json(cmd, env):
159 """Symbolize output snippets inside the JSON test summary."""
160 json_path = get_json_path(cmd)
161 if json_path is None:
162 return
163
164 try:
glider9d919342015-02-06 17:42:27165 symbolize_command = get_sanitizer_symbolize_command(
166 json_path=json_path, executable_path=cmd[0])
earthdok9bd5d582015-01-29 21:19:36167 p = subprocess.Popen(symbolize_command, stderr=subprocess.PIPE, env=env)
168 (_, stderr) = p.communicate()
169 except OSError as e:
Sorin Jianue11c9852019-09-12 20:43:55170 print >> sys.stderr, 'Exception while symbolizing snippets: %s' % e
thakis45643b42016-01-29 21:53:42171 raise
earthdok9bd5d582015-01-29 21:19:36172
173 if p.returncode != 0:
Sorin Jianue11c9852019-09-12 20:43:55174 print >> sys.stderr, "Error: failed to symbolize snippets in JSON:\n"
175 print >> sys.stderr, stderr
thakis45643b42016-01-29 21:53:42176 raise subprocess.CalledProcessError(p.returncode, symbolize_command)
earthdok9bd5d582015-01-29 21:19:36177
178
Trent Aptedb1852432018-01-25 02:15:10179def run_command_with_output(argv, stdoutfile, env=None, cwd=None):
Caleb Rouleaufd511292019-02-22 18:22:00180 """Run command and stream its stdout/stderr to the console & |stdoutfile|.
181
182 Also forward_signals to obey
183 https://chromium.googlesource.com/infra/luci/luci-py/+/master/appengine/swarming/doc/Bot.md#graceful-termination_aka-the-sigterm-and-sigkill-dance
184
185 Returns:
186 integer returncode of the subprocess.
Trent Aptedb1852432018-01-25 02:15:10187 """
Sorin Jianue11c9852019-09-12 20:43:55188 print('Running %r in %r (env: %r)' % (argv, cwd, env))
Trent Aptedb1852432018-01-25 02:15:10189 assert stdoutfile
Ned Nguyen7f43ff62018-10-04 04:58:46190 with io.open(stdoutfile, 'wb') as writer, \
191 io.open(stdoutfile, 'rb', 1) as reader:
Caleb Rouleau6844df152019-09-11 01:11:59192 process = _popen(argv, env=env, cwd=cwd, stdout=writer,
193 stderr=subprocess.STDOUT)
Ben Pastenee5ece812018-06-20 22:39:34194 forward_signals([process])
Trent Aptedb1852432018-01-25 02:15:10195 while process.poll() is None:
196 sys.stdout.write(reader.read())
Caleb Rouleaufd511292019-02-22 18:22:00197 # This sleep is needed for signal propagation. See the
198 # wait_with_signals() docstring.
Trent Aptedb1852432018-01-25 02:15:10199 time.sleep(0.1)
200 # Read the remaining.
201 sys.stdout.write(reader.read())
Sorin Jianue11c9852019-09-12 20:43:55202 print('Command %r returned exit code %d' % (argv, process.returncode))
Trent Aptedb1852432018-01-25 02:15:10203 return process.returncode
204
205
Caleb Rouleaufd511292019-02-22 18:22:00206def run_command(argv, env=None, cwd=None, log=True):
207 """Run command and stream its stdout/stderr both to stdout.
208
209 Also forward_signals to obey
210 https://chromium.googlesource.com/infra/luci/luci-py/+/master/appengine/swarming/doc/Bot.md#graceful-termination_aka-the-sigterm-and-sigkill-dance
211
212 Returns:
213 integer returncode of the subprocess.
214 """
215 if log:
Sorin Jianue11c9852019-09-12 20:43:55216 print('Running %r in %r (env: %r)' % (argv, cwd, env))
Caleb Rouleau6844df152019-09-11 01:11:59217 process = _popen(argv, env=env, cwd=cwd, stderr=subprocess.STDOUT)
Caleb Rouleaufd511292019-02-22 18:22:00218 forward_signals([process])
219 return wait_with_signals(process)
220
221
Caleb Rouleau84e3e812019-05-30 23:34:50222def run_command_output_to_handle(argv, file_handle, env=None, cwd=None):
223 """Run command and stream its stdout/stderr both to |file_handle|.
224
225 Also forward_signals to obey
226 https://chromium.googlesource.com/infra/luci/luci-py/+/master/appengine/swarming/doc/Bot.md#graceful-termination_aka-the-sigterm-and-sigkill-dance
227
228 Returns:
229 integer returncode of the subprocess.
230 """
Sorin Jianue11c9852019-09-12 20:43:55231 print('Running %r in %r (env: %r)' % (argv, cwd, env))
Caleb Rouleau6844df152019-09-11 01:11:59232 process = _popen(
Caleb Rouleau84e3e812019-05-30 23:34:50233 argv, env=env, cwd=cwd, stderr=file_handle, stdout=file_handle)
234 forward_signals([process])
235 exit_code = wait_with_signals(process)
Sorin Jianue11c9852019-09-12 20:43:55236 print('Command returned exit code %d' % exit_code)
Caleb Rouleau84e3e812019-05-30 23:34:50237 return exit_code
238
239
Caleb Rouleaufd511292019-02-22 18:22:00240def wait_with_signals(process):
241 """A version of process.wait() that works cross-platform.
242
243 This version properly surfaces the SIGBREAK signal.
244
245 From reading the subprocess.py source code, it seems we need to explicitly
246 call time.sleep(). The reason is that subprocess.Popen.wait() on Windows
247 directly calls WaitForSingleObject(), but only time.sleep() properly surface
248 the SIGBREAK signal.
249
250 Refs:
251 https://github.com/python/cpython/blob/v2.7.15/Lib/subprocess.py#L692
252 https://github.com/python/cpython/blob/v2.7.15/Modules/timemodule.c#L1084
253
254 Returns:
255 returncode of the process.
256 """
257 while process.poll() is None:
258 time.sleep(0.1)
259 return process.returncode
260
261
Ben Pastenee5ece812018-06-20 22:39:34262def forward_signals(procs):
263 """Forwards unix's SIGTERM or win's CTRL_BREAK_EVENT to the given processes.
264
265 This plays nicely with swarming's timeout handling. See also
266 https://chromium.googlesource.com/infra/luci/luci-py/+/master/appengine/swarming/doc/Bot.md#graceful-termination_aka-the-sigterm-and-sigkill-dance
267
268 Args:
269 procs: A list of subprocess.Popen objects representing child processes.
270 """
271 assert all(isinstance(p, subprocess.Popen) for p in procs)
272 def _sig_handler(sig, _):
273 for p in procs:
Ilia Samsonova00835302019-04-19 17:37:59274 if p.poll() is not None:
275 continue
Andrew Grieve95a2d332018-06-28 02:22:59276 # SIGBREAK is defined only for win32.
277 if sys.platform == 'win32' and sig == signal.SIGBREAK:
Ben Pastenee5ece812018-06-20 22:39:34278 p.send_signal(signal.CTRL_BREAK_EVENT)
279 else:
280 p.send_signal(sig)
281 if sys.platform == 'win32':
282 signal.signal(signal.SIGBREAK, _sig_handler)
283 else:
284 signal.signal(signal.SIGTERM, _sig_handler)
Ilia Samsonova00835302019-04-19 17:37:59285 signal.signal(signal.SIGINT, _sig_handler)
Ben Pastenee5ece812018-06-20 22:39:34286
Haiyang Pan94c26592019-08-13 17:48:11287
Trent Aptedb1852432018-01-25 02:15:10288def run_executable(cmd, env, stdoutfile=None):
[email protected]0a88a652012-03-09 00:34:45289 """Runs an executable with:
Dirk Pranke50e557b2017-12-01 23:48:09290 - CHROME_HEADLESS set to indicate that the test is running on a
291 bot and shouldn't do anything interactive like show modal dialogs.
[email protected]3766ed1c2012-07-26 20:53:56292 - environment variable CR_SOURCE_ROOT set to the root directory.
[email protected]0a88a652012-03-09 00:34:45293 - environment variable LANGUAGE to en_US.UTF-8.
earthdok748e1352015-01-27 23:04:08294 - environment variable CHROME_DEVEL_SANDBOX set
[email protected]0a88a652012-03-09 00:34:45295 - Reuses sys.executable automatically.
296 """
Dirk Pranke50e557b2017-12-01 23:48:09297 extra_env = {
298 # Set to indicate that the executable is running non-interactively on
299 # a bot.
300 'CHROME_HEADLESS': '1',
301
302 # Many tests assume a English interface...
303 'LANG': 'en_US.UTF-8',
304 }
305
[email protected]3766ed1c2012-07-26 20:53:56306 # Used by base/base_paths_linux.cc as an override. Just make sure the default
307 # logic is used.
308 env.pop('CR_SOURCE_ROOT', None)
earthdok748e1352015-01-27 23:04:08309 extra_env.update(get_sandbox_env(env))
John Abd-El-Malek4569c422014-10-09 05:10:53310
311 # Copy logic from tools/build/scripts/slave/runtest.py.
312 asan = '--asan=1' in cmd
313 lsan = '--lsan=1' in cmd
earthdokeeb065302015-02-04 18:18:04314 msan = '--msan=1' in cmd
315 tsan = '--tsan=1' in cmd
pcc46233c22017-06-20 22:11:41316 cfi_diag = '--cfi-diag=1' in cmd
Trent Aptedb1852432018-01-25 02:15:10317 if stdoutfile or sys.platform in ['win32', 'cygwin']:
timurrrr064f9522015-02-12 15:46:32318 # Symbolization works in-process on Windows even when sandboxed.
319 use_symbolization_script = False
320 else:
Ilia Samsonov4ea016762019-12-02 22:11:13321 # If any sanitizer is enabled, we print unsymbolized stack trace
322 # that is required to run through symbolization script.
323 use_symbolization_script = (asan or msan or cfi_diag or lsan or tsan)
John Abd-El-Malek4569c422014-10-09 05:10:53324
pcc46233c22017-06-20 22:11:41325 if asan or lsan or msan or tsan or cfi_diag:
326 extra_env.update(get_sanitizer_env(cmd, asan, lsan, msan, tsan, cfi_diag))
earthdokeeb065302015-02-04 18:18:04327
Timur Iskhodzhanovfdbfd4e12015-02-05 13:50:23328 if lsan or tsan:
329 # LSan and TSan are not sandbox-friendly.
330 cmd.append('--no-sandbox')
John Abd-El-Malek14ae0542014-10-15 17:52:31331
332 cmd = trim_cmd(cmd)
John Abd-El-Malek4569c422014-10-09 05:10:53333
[email protected]8ba98352012-05-23 20:43:59334 # Ensure paths are correctly separated on windows.
335 cmd[0] = cmd[0].replace('/', os.path.sep)
[email protected]4bf4d632012-05-31 15:50:30336 cmd = fix_python_path(cmd)
John Abd-El-Malek14ae0542014-10-15 17:52:31337
Dirk Pranke2cf1a152019-04-03 01:34:38338 # We also want to print the GTEST env vars that were set by the caller,
339 # because you need them to reproduce the task properly.
340 env_to_print = extra_env.copy()
341 for env_var_name in ('GTEST_SHARD_INDEX', 'GTEST_TOTAL_SHARDS'):
342 if env_var_name in env:
343 env_to_print[env_var_name] = env[env_var_name]
344
Sorin Jianue11c9852019-09-12 20:43:55345 print('Additional test environment:\n%s\n'
John Abd-El-Malek14ae0542014-10-15 17:52:31346 'Command: %s\n' % (
347 '\n'.join(' %s=%s' %
Sorin Jianue11c9852019-09-12 20:43:55348 (k, v) for k, v in sorted(env_to_print.iteritems())),
349 ' '.join(cmd)))
maruelf00125f82016-11-19 00:01:14350 sys.stdout.flush()
John Abd-El-Malek14ae0542014-10-15 17:52:31351 env.update(extra_env or {})
[email protected]50ec9f232012-03-16 04:18:23352 try:
Trent Aptedb1852432018-01-25 02:15:10353 if stdoutfile:
354 # Write to stdoutfile and poll to produce terminal output.
355 return run_command_with_output(cmd, env=env, stdoutfile=stdoutfile)
356 elif use_symbolization_script:
357 # See above comment regarding offline symbolization.
John Abd-El-Malek4569c422014-10-09 05:10:53358 # Need to pipe to the symbolizer script.
Caleb Rouleau6844df152019-09-11 01:11:59359 p1 = _popen(cmd, env=env, stdout=subprocess.PIPE,
360 stderr=sys.stdout)
361 p2 = _popen(
glider9d919342015-02-06 17:42:27362 get_sanitizer_symbolize_command(executable_path=cmd[0]),
363 env=env, stdin=p1.stdout)
John Abd-El-Malek4569c422014-10-09 05:10:53364 p1.stdout.close() # Allow p1 to receive a SIGPIPE if p2 exits.
Ben Pastenee5ece812018-06-20 22:39:34365 forward_signals([p1, p2])
Caleb Rouleaufd511292019-02-22 18:22:00366 wait_with_signals(p1)
367 wait_with_signals(p2)
earthdok9bd5d582015-01-29 21:19:36368 # Also feed the out-of-band JSON output to the symbolizer script.
369 symbolize_snippets_in_json(cmd, env)
vadimsh1eaeb2982014-10-20 12:28:45370 return p1.returncode
John Abd-El-Malek4569c422014-10-09 05:10:53371 else:
Caleb Rouleaufd511292019-02-22 18:22:00372 return run_command(cmd, env=env, log=False)
[email protected]50ec9f232012-03-16 04:18:23373 except OSError:
Sorin Jianue11c9852019-09-12 20:43:55374 print >> sys.stderr, 'Failed to start %s' % cmd
[email protected]50ec9f232012-03-16 04:18:23375 raise
[email protected]0a88a652012-03-09 00:34:45376
377
Caleb Rouleau6844df152019-09-11 01:11:59378def _popen(*args, **kwargs):
379 assert 'creationflags' not in kwargs
380 if sys.platform == 'win32':
381 # Necessary for signal handling. See crbug.com/733612#c6.
382 kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP
383 return subprocess.Popen(*args, **kwargs)
384
385
[email protected]0a88a652012-03-09 00:34:45386def main():
gliderb73f1872014-10-09 16:24:56387 return run_executable(sys.argv[1:], os.environ.copy())
[email protected]0a88a652012-03-09 00:34:45388
389
[email protected]ed763a72012-08-29 03:51:22390if __name__ == '__main__':
[email protected]0a88a652012-03-09 00:34:45391 sys.exit(main())