blob: 4c3332aa0df22aa77854df14f1f9a17d7deffee9 [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
Brian Sheedy1102f122019-09-11 23:07:168from __future__ import print_function
9
Trent Aptedb1852432018-01-25 02:15:1010import io
[email protected]0a88a652012-03-09 00:34:4511import os
Ben Pastenee5ece812018-06-20 22:39:3412import signal
[email protected]76361be452012-08-30 22:48:1413import stat
[email protected]0a88a652012-03-09 00:34:4514import subprocess
15import sys
Trent Aptedb1852432018-01-25 02:15:1016import time
[email protected]0a88a652012-03-09 00:34:4517
18# This is hardcoded to be src/ relative to this script.
19ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
20
[email protected]76361be452012-08-30 22:48:1421CHROME_SANDBOX_ENV = 'CHROME_DEVEL_SANDBOX'
22CHROME_SANDBOX_PATH = '/opt/chromium/chrome_sandbox'
23
24
earthdok748e1352015-01-27 23:04:0825def get_sandbox_env(env):
26 """Returns the environment flags needed for the SUID sandbox to work."""
John Abd-El-Malek14ae0542014-10-15 17:52:3127 extra_env = {}
[email protected]76361be452012-08-30 22:48:1428 chrome_sandbox_path = env.get(CHROME_SANDBOX_ENV, CHROME_SANDBOX_PATH)
earthdok748e1352015-01-27 23:04:0829 # The above would silently disable the SUID sandbox if the env value were
30 # an empty string. We don't want to allow that. http://crbug.com/245376
31 # TODO(jln): Remove this check once it's no longer possible to disable the
32 # sandbox that way.
33 if not chrome_sandbox_path:
34 chrome_sandbox_path = CHROME_SANDBOX_PATH
35 extra_env[CHROME_SANDBOX_ENV] = chrome_sandbox_path
John Abd-El-Malek14ae0542014-10-15 17:52:3136
37 return extra_env
38
39
40def trim_cmd(cmd):
41 """Removes internal flags from cmd since they're just used to communicate from
42 the host machine to this script running on the swarm slaves."""
earthdokeeb065302015-02-04 18:18:0443 sanitizers = ['asan', 'lsan', 'msan', 'tsan']
44 internal_flags = frozenset('--%s=%d' % (name, value)
45 for name in sanitizers
46 for value in [0, 1])
John Abd-El-Malek14ae0542014-10-15 17:52:3147 return [i for i in cmd if i not in internal_flags]
[email protected]76361be452012-08-30 22:48:1448
[email protected]0a88a652012-03-09 00:34:4549
[email protected]4bf4d632012-05-31 15:50:3050def fix_python_path(cmd):
51 """Returns the fixed command line to call the right python executable."""
52 out = cmd[:]
53 if out[0] == 'python':
54 out[0] = sys.executable
55 elif out[0].endswith('.py'):
56 out.insert(0, sys.executable)
57 return out
58
59
pcc46233c22017-06-20 22:11:4160def get_sanitizer_env(cmd, asan, lsan, msan, tsan, cfi_diag):
earthdokeeb065302015-02-04 18:18:0461 """Returns the envirnoment flags needed for sanitizer tools."""
John Abd-El-Malek14ae0542014-10-15 17:52:3162
63 extra_env = {}
64
earthdokeeb065302015-02-04 18:18:0465 # Instruct GTK to use malloc while running sanitizer-instrumented tests.
earthdoke6c54a42014-10-16 18:02:3666 extra_env['G_SLICE'] = 'always-malloc'
John Abd-El-Malek14ae0542014-10-15 17:52:3167
68 extra_env['NSS_DISABLE_ARENA_FREE_LIST'] = '1'
69 extra_env['NSS_DISABLE_UNLOAD'] = '1'
70
71 # TODO(glider): remove the symbolizer path once
72 # https://code.google.com/p/address-sanitizer/issues/detail?id=134 is fixed.
Nico Weber19097312016-01-29 18:13:1573 symbolizer_path = os.path.join(ROOT_DIR,
74 'third_party', 'llvm-build', 'Release+Asserts', 'bin', 'llvm-symbolizer')
John Abd-El-Malek14ae0542014-10-15 17:52:3175
earthdokeeb065302015-02-04 18:18:0476 if lsan or tsan:
John Abd-El-Malek14ae0542014-10-15 17:52:3177 # LSan is not sandbox-compatible, so we can use online symbolization. In
78 # fact, it needs symbolization to be able to apply suppressions.
79 symbolization_options = ['symbolize=1',
80 'external_symbolizer_path=%s' % symbolizer_path]
pcc46233c22017-06-20 22:11:4181 elif (asan or msan or cfi_diag) and sys.platform not in ['win32', 'cygwin']:
timurrrr064f9522015-02-12 15:46:3282 # ASan uses a script for offline symbolization, except on Windows.
John Abd-El-Malek14ae0542014-10-15 17:52:3183 # Important note: when running ASan with leak detection enabled, we must use
84 # the LSan symbolization options above.
85 symbolization_options = ['symbolize=0']
vadimsh5cf3c442014-10-20 10:22:3886 # Set the path to llvm-symbolizer to be used by asan_symbolize.py
87 extra_env['LLVM_SYMBOLIZER_PATH'] = symbolizer_path
timurrrr7e67df92015-02-12 21:50:5288 else:
89 symbolization_options = []
John Abd-El-Malek14ae0542014-10-15 17:52:3190
Yves Gerey2e221522019-03-13 08:46:1991 # Leverage sanitizer to print stack trace on abort (e.g. assertion failure).
92 symbolization_options.append('handle_abort=1')
93
earthdokeeb065302015-02-04 18:18:0494 if asan:
95 asan_options = symbolization_options[:]
96 if lsan:
97 asan_options.append('detect_leaks=1')
John Abd-El-Malek14ae0542014-10-15 17:52:3198
timurrrr7e67df92015-02-12 21:50:5299 if asan_options:
100 extra_env['ASAN_OPTIONS'] = ' '.join(asan_options)
John Abd-El-Malek14ae0542014-10-15 17:52:31101
earthdokeeb065302015-02-04 18:18:04102 if sys.platform == 'darwin':
103 isolate_output_dir = os.path.abspath(os.path.dirname(cmd[0]))
104 # This is needed because the test binary has @executable_path embedded in
105 # it that the OS tries to resolve to the cache directory and not the
106 # mapped directory.
107 extra_env['DYLD_LIBRARY_PATH'] = str(isolate_output_dir)
108
109 if lsan:
110 if asan or msan:
111 lsan_options = []
112 else:
113 lsan_options = symbolization_options[:]
114 if sys.platform == 'linux2':
115 # Use the debug version of libstdc++ under LSan. If we don't, there will
116 # be a lot of incomplete stack traces in the reports.
117 extra_env['LD_LIBRARY_PATH'] = '/usr/lib/x86_64-linux-gnu/debug:'
118
earthdokeeb065302015-02-04 18:18:04119 extra_env['LSAN_OPTIONS'] = ' '.join(lsan_options)
120
121 if msan:
122 msan_options = symbolization_options[:]
123 if lsan:
124 msan_options.append('detect_leaks=1')
125 extra_env['MSAN_OPTIONS'] = ' '.join(msan_options)
126
127 if tsan:
128 tsan_options = symbolization_options[:]
129 extra_env['TSAN_OPTIONS'] = ' '.join(tsan_options)
John Abd-El-Malek14ae0542014-10-15 17:52:31130
pcc46233c22017-06-20 22:11:41131 # CFI uses the UBSan runtime to provide diagnostics.
132 if cfi_diag:
133 ubsan_options = symbolization_options[:] + ['print_stacktrace=1']
134 extra_env['UBSAN_OPTIONS'] = ' '.join(ubsan_options)
135
John Abd-El-Malek14ae0542014-10-15 17:52:31136 return extra_env
137
138
glider9d919342015-02-06 17:42:27139def get_sanitizer_symbolize_command(json_path=None, executable_path=None):
earthdok9bd5d582015-01-29 21:19:36140 """Construct the command to invoke offline symbolization script."""
Nico Weber19097312016-01-29 18:13:15141 script_path = os.path.join(
142 ROOT_DIR, 'tools', 'valgrind', 'asan', 'asan_symbolize.py')
earthdok9bd5d582015-01-29 21:19:36143 cmd = [sys.executable, script_path]
144 if json_path is not None:
145 cmd.append('--test-summary-json-file=%s' % json_path)
glider9d919342015-02-06 17:42:27146 if executable_path is not None:
147 cmd.append('--executable-path=%s' % executable_path)
earthdok9bd5d582015-01-29 21:19:36148 return cmd
149
150
151def get_json_path(cmd):
152 """Extract the JSON test summary path from a command line."""
153 json_path_flag = '--test-launcher-summary-output='
154 for arg in cmd:
155 if arg.startswith(json_path_flag):
156 return arg.split(json_path_flag).pop()
157 return None
158
159
160def symbolize_snippets_in_json(cmd, env):
161 """Symbolize output snippets inside the JSON test summary."""
162 json_path = get_json_path(cmd)
163 if json_path is None:
164 return
165
166 try:
glider9d919342015-02-06 17:42:27167 symbolize_command = get_sanitizer_symbolize_command(
168 json_path=json_path, executable_path=cmd[0])
earthdok9bd5d582015-01-29 21:19:36169 p = subprocess.Popen(symbolize_command, stderr=subprocess.PIPE, env=env)
170 (_, stderr) = p.communicate()
171 except OSError as e:
Brian Sheedy1102f122019-09-11 23:07:16172 print('Exception while symbolizing snippets: %s' % e, file=sys.stderr)
thakis45643b42016-01-29 21:53:42173 raise
earthdok9bd5d582015-01-29 21:19:36174
175 if p.returncode != 0:
Brian Sheedy1102f122019-09-11 23:07:16176 print("Error: failed to symbolize snippets in JSON:\n", file=sys.stderr)
177 print(stderr, file=sys.stderr)
thakis45643b42016-01-29 21:53:42178 raise subprocess.CalledProcessError(p.returncode, symbolize_command)
earthdok9bd5d582015-01-29 21:19:36179
180
Trent Aptedb1852432018-01-25 02:15:10181def run_command_with_output(argv, stdoutfile, env=None, cwd=None):
Caleb Rouleaufd511292019-02-22 18:22:00182 """Run command and stream its stdout/stderr to the console & |stdoutfile|.
183
184 Also forward_signals to obey
185 https://chromium.googlesource.com/infra/luci/luci-py/+/master/appengine/swarming/doc/Bot.md#graceful-termination_aka-the-sigterm-and-sigkill-dance
186
187 Returns:
188 integer returncode of the subprocess.
Trent Aptedb1852432018-01-25 02:15:10189 """
Brian Sheedy1102f122019-09-11 23:07:16190 print(('Running %r in %r (env: %r)' % (argv, cwd, env)))
Trent Aptedb1852432018-01-25 02:15:10191 assert stdoutfile
Ned Nguyen7f43ff62018-10-04 04:58:46192 with io.open(stdoutfile, 'wb') as writer, \
193 io.open(stdoutfile, 'rb', 1) as reader:
Caleb Rouleau6844df152019-09-11 01:11:59194 process = _popen(argv, env=env, cwd=cwd, stdout=writer,
195 stderr=subprocess.STDOUT)
Ben Pastenee5ece812018-06-20 22:39:34196 forward_signals([process])
Trent Aptedb1852432018-01-25 02:15:10197 while process.poll() is None:
198 sys.stdout.write(reader.read())
Caleb Rouleaufd511292019-02-22 18:22:00199 # This sleep is needed for signal propagation. See the
200 # wait_with_signals() docstring.
Trent Aptedb1852432018-01-25 02:15:10201 time.sleep(0.1)
202 # Read the remaining.
203 sys.stdout.write(reader.read())
Brian Sheedy1102f122019-09-11 23:07:16204 print(('Command %r returned exit code %d' % (argv, process.returncode)))
Trent Aptedb1852432018-01-25 02:15:10205 return process.returncode
206
207
Caleb Rouleaufd511292019-02-22 18:22:00208def run_command(argv, env=None, cwd=None, log=True):
209 """Run command and stream its stdout/stderr both to stdout.
210
211 Also forward_signals to obey
212 https://chromium.googlesource.com/infra/luci/luci-py/+/master/appengine/swarming/doc/Bot.md#graceful-termination_aka-the-sigterm-and-sigkill-dance
213
214 Returns:
215 integer returncode of the subprocess.
216 """
217 if log:
Brian Sheedy1102f122019-09-11 23:07:16218 print(('Running %r in %r (env: %r)' % (argv, cwd, env)))
Caleb Rouleau6844df152019-09-11 01:11:59219 process = _popen(argv, env=env, cwd=cwd, stderr=subprocess.STDOUT)
Caleb Rouleaufd511292019-02-22 18:22:00220 forward_signals([process])
221 return wait_with_signals(process)
222
223
Caleb Rouleau84e3e812019-05-30 23:34:50224def run_command_output_to_handle(argv, file_handle, env=None, cwd=None):
225 """Run command and stream its stdout/stderr both to |file_handle|.
226
227 Also forward_signals to obey
228 https://chromium.googlesource.com/infra/luci/luci-py/+/master/appengine/swarming/doc/Bot.md#graceful-termination_aka-the-sigterm-and-sigkill-dance
229
230 Returns:
231 integer returncode of the subprocess.
232 """
Brian Sheedy1102f122019-09-11 23:07:16233 print(('Running %r in %r (env: %r)' % (argv, cwd, env)))
Caleb Rouleau6844df152019-09-11 01:11:59234 process = _popen(
Caleb Rouleau84e3e812019-05-30 23:34:50235 argv, env=env, cwd=cwd, stderr=file_handle, stdout=file_handle)
236 forward_signals([process])
237 exit_code = wait_with_signals(process)
Brian Sheedy1102f122019-09-11 23:07:16238 print(('Command returned exit code %d' % exit_code))
Caleb Rouleau84e3e812019-05-30 23:34:50239 return exit_code
240
241
Caleb Rouleaufd511292019-02-22 18:22:00242def wait_with_signals(process):
243 """A version of process.wait() that works cross-platform.
244
245 This version properly surfaces the SIGBREAK signal.
246
247 From reading the subprocess.py source code, it seems we need to explicitly
248 call time.sleep(). The reason is that subprocess.Popen.wait() on Windows
249 directly calls WaitForSingleObject(), but only time.sleep() properly surface
250 the SIGBREAK signal.
251
252 Refs:
253 https://github.com/python/cpython/blob/v2.7.15/Lib/subprocess.py#L692
254 https://github.com/python/cpython/blob/v2.7.15/Modules/timemodule.c#L1084
255
256 Returns:
257 returncode of the process.
258 """
259 while process.poll() is None:
260 time.sleep(0.1)
261 return process.returncode
262
263
Ben Pastenee5ece812018-06-20 22:39:34264def forward_signals(procs):
265 """Forwards unix's SIGTERM or win's CTRL_BREAK_EVENT to the given processes.
266
267 This plays nicely with swarming's timeout handling. See also
268 https://chromium.googlesource.com/infra/luci/luci-py/+/master/appengine/swarming/doc/Bot.md#graceful-termination_aka-the-sigterm-and-sigkill-dance
269
270 Args:
271 procs: A list of subprocess.Popen objects representing child processes.
272 """
273 assert all(isinstance(p, subprocess.Popen) for p in procs)
274 def _sig_handler(sig, _):
275 for p in procs:
Ilia Samsonova00835302019-04-19 17:37:59276 if p.poll() is not None:
277 continue
Andrew Grieve95a2d332018-06-28 02:22:59278 # SIGBREAK is defined only for win32.
279 if sys.platform == 'win32' and sig == signal.SIGBREAK:
Ben Pastenee5ece812018-06-20 22:39:34280 p.send_signal(signal.CTRL_BREAK_EVENT)
281 else:
282 p.send_signal(sig)
283 if sys.platform == 'win32':
284 signal.signal(signal.SIGBREAK, _sig_handler)
285 else:
286 signal.signal(signal.SIGTERM, _sig_handler)
Ilia Samsonova00835302019-04-19 17:37:59287 signal.signal(signal.SIGINT, _sig_handler)
Ben Pastenee5ece812018-06-20 22:39:34288
Haiyang Pan94c26592019-08-13 17:48:11289
Trent Aptedb1852432018-01-25 02:15:10290def run_executable(cmd, env, stdoutfile=None):
[email protected]0a88a652012-03-09 00:34:45291 """Runs an executable with:
Dirk Pranke50e557b2017-12-01 23:48:09292 - CHROME_HEADLESS set to indicate that the test is running on a
293 bot and shouldn't do anything interactive like show modal dialogs.
[email protected]3766ed1c2012-07-26 20:53:56294 - environment variable CR_SOURCE_ROOT set to the root directory.
[email protected]0a88a652012-03-09 00:34:45295 - environment variable LANGUAGE to en_US.UTF-8.
earthdok748e1352015-01-27 23:04:08296 - environment variable CHROME_DEVEL_SANDBOX set
[email protected]0a88a652012-03-09 00:34:45297 - Reuses sys.executable automatically.
298 """
Dirk Pranke50e557b2017-12-01 23:48:09299 extra_env = {
300 # Set to indicate that the executable is running non-interactively on
301 # a bot.
302 'CHROME_HEADLESS': '1',
303
304 # Many tests assume a English interface...
305 'LANG': 'en_US.UTF-8',
306 }
307
[email protected]3766ed1c2012-07-26 20:53:56308 # Used by base/base_paths_linux.cc as an override. Just make sure the default
309 # logic is used.
310 env.pop('CR_SOURCE_ROOT', None)
earthdok748e1352015-01-27 23:04:08311 extra_env.update(get_sandbox_env(env))
John Abd-El-Malek4569c422014-10-09 05:10:53312
313 # Copy logic from tools/build/scripts/slave/runtest.py.
314 asan = '--asan=1' in cmd
315 lsan = '--lsan=1' in cmd
earthdokeeb065302015-02-04 18:18:04316 msan = '--msan=1' in cmd
317 tsan = '--tsan=1' in cmd
pcc46233c22017-06-20 22:11:41318 cfi_diag = '--cfi-diag=1' in cmd
Trent Aptedb1852432018-01-25 02:15:10319 if stdoutfile or sys.platform in ['win32', 'cygwin']:
timurrrr064f9522015-02-12 15:46:32320 # Symbolization works in-process on Windows even when sandboxed.
321 use_symbolization_script = False
322 else:
323 # LSan doesn't support sandboxing yet, so we use the in-process symbolizer.
324 # Note that ASan and MSan can work together with LSan.
pcc46233c22017-06-20 22:11:41325 use_symbolization_script = (asan or msan or cfi_diag) and not lsan
John Abd-El-Malek4569c422014-10-09 05:10:53326
pcc46233c22017-06-20 22:11:41327 if asan or lsan or msan or tsan or cfi_diag:
328 extra_env.update(get_sanitizer_env(cmd, asan, lsan, msan, tsan, cfi_diag))
earthdokeeb065302015-02-04 18:18:04329
Timur Iskhodzhanovfdbfd4e12015-02-05 13:50:23330 if lsan or tsan:
331 # LSan and TSan are not sandbox-friendly.
332 cmd.append('--no-sandbox')
John Abd-El-Malek14ae0542014-10-15 17:52:31333
334 cmd = trim_cmd(cmd)
John Abd-El-Malek4569c422014-10-09 05:10:53335
[email protected]8ba98352012-05-23 20:43:59336 # Ensure paths are correctly separated on windows.
337 cmd[0] = cmd[0].replace('/', os.path.sep)
[email protected]4bf4d632012-05-31 15:50:30338 cmd = fix_python_path(cmd)
John Abd-El-Malek14ae0542014-10-15 17:52:31339
Dirk Pranke2cf1a152019-04-03 01:34:38340 # We also want to print the GTEST env vars that were set by the caller,
341 # because you need them to reproduce the task properly.
342 env_to_print = extra_env.copy()
343 for env_var_name in ('GTEST_SHARD_INDEX', 'GTEST_TOTAL_SHARDS'):
344 if env_var_name in env:
345 env_to_print[env_var_name] = env[env_var_name]
346
Brian Sheedy1102f122019-09-11 23:07:16347 print(('Additional test environment:\n%s\n'
John Abd-El-Malek14ae0542014-10-15 17:52:31348 'Command: %s\n' % (
349 '\n'.join(' %s=%s' %
Brian Sheedy1102f122019-09-11 23:07:16350 (k, v) for k, v in sorted(env_to_print.items())),
351 ' '.join(cmd))))
maruelf00125f82016-11-19 00:01:14352 sys.stdout.flush()
John Abd-El-Malek14ae0542014-10-15 17:52:31353 env.update(extra_env or {})
[email protected]50ec9f232012-03-16 04:18:23354 try:
Trent Aptedb1852432018-01-25 02:15:10355 if stdoutfile:
356 # Write to stdoutfile and poll to produce terminal output.
357 return run_command_with_output(cmd, env=env, stdoutfile=stdoutfile)
358 elif use_symbolization_script:
359 # See above comment regarding offline symbolization.
John Abd-El-Malek4569c422014-10-09 05:10:53360 # Need to pipe to the symbolizer script.
Caleb Rouleau6844df152019-09-11 01:11:59361 p1 = _popen(cmd, env=env, stdout=subprocess.PIPE,
362 stderr=sys.stdout)
363 p2 = _popen(
glider9d919342015-02-06 17:42:27364 get_sanitizer_symbolize_command(executable_path=cmd[0]),
365 env=env, stdin=p1.stdout)
John Abd-El-Malek4569c422014-10-09 05:10:53366 p1.stdout.close() # Allow p1 to receive a SIGPIPE if p2 exits.
Ben Pastenee5ece812018-06-20 22:39:34367 forward_signals([p1, p2])
Caleb Rouleaufd511292019-02-22 18:22:00368 wait_with_signals(p1)
369 wait_with_signals(p2)
earthdok9bd5d582015-01-29 21:19:36370 # Also feed the out-of-band JSON output to the symbolizer script.
371 symbolize_snippets_in_json(cmd, env)
vadimsh1eaeb2982014-10-20 12:28:45372 return p1.returncode
John Abd-El-Malek4569c422014-10-09 05:10:53373 else:
Caleb Rouleaufd511292019-02-22 18:22:00374 return run_command(cmd, env=env, log=False)
[email protected]50ec9f232012-03-16 04:18:23375 except OSError:
Brian Sheedy1102f122019-09-11 23:07:16376 print('Failed to start %s' % cmd, file=sys.stderr)
[email protected]50ec9f232012-03-16 04:18:23377 raise
[email protected]0a88a652012-03-09 00:34:45378
379
Caleb Rouleau6844df152019-09-11 01:11:59380def _popen(*args, **kwargs):
381 assert 'creationflags' not in kwargs
382 if sys.platform == 'win32':
383 # Necessary for signal handling. See crbug.com/733612#c6.
384 kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP
385 return subprocess.Popen(*args, **kwargs)
386
387
[email protected]0a88a652012-03-09 00:34:45388def main():
gliderb73f1872014-10-09 16:24:56389 return run_executable(sys.argv[1:], os.environ.copy())
[email protected]0a88a652012-03-09 00:34:45390
391
[email protected]ed763a72012-08-29 03:51:22392if __name__ == '__main__':
[email protected]0a88a652012-03-09 00:34:45393 sys.exit(main())