blob: 6e39aa40121af8bc8c809220ced84f776c37246a [file] [log] [blame]
Chris McDonaldc3e0f26b2020-06-04 02:15:141#!/usr/bin/env python
[email protected]0a88a652012-03-09 00:34:452# 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
Chris McDonaldc3e0f26b2020-06-04 02:15:148from __future__ import print_function
Trent Aptedb1852432018-01-25 02:15:109import io
[email protected]0a88a652012-03-09 00:34:4510import os
Ben Pastenee5ece812018-06-20 22:39:3411import signal
[email protected]76361be452012-08-30 22:48:1412import stat
[email protected]0a88a652012-03-09 00:34:4513import subprocess
14import sys
Trent Aptedb1852432018-01-25 02:15:1015import time
[email protected]0a88a652012-03-09 00:34:4516
Chris McDonaldc3e0f26b2020-06-04 02:15:1417SIX_DIR = os.path.join(
18 os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'third_party',
19 'six')
20sys.path.insert(0, SIX_DIR)
21
22import six
23
[email protected]0a88a652012-03-09 00:34:4524# This is hardcoded to be src/ relative to this script.
25ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
26
[email protected]76361be452012-08-30 22:48:1427CHROME_SANDBOX_ENV = 'CHROME_DEVEL_SANDBOX'
28CHROME_SANDBOX_PATH = '/opt/chromium/chrome_sandbox'
29
30
earthdok748e1352015-01-27 23:04:0831def get_sandbox_env(env):
32 """Returns the environment flags needed for the SUID sandbox to work."""
John Abd-El-Malek14ae0542014-10-15 17:52:3133 extra_env = {}
[email protected]76361be452012-08-30 22:48:1434 chrome_sandbox_path = env.get(CHROME_SANDBOX_ENV, CHROME_SANDBOX_PATH)
earthdok748e1352015-01-27 23:04:0835 # The above would silently disable the SUID sandbox if the env value were
36 # an empty string. We don't want to allow that. http://crbug.com/245376
37 # TODO(jln): Remove this check once it's no longer possible to disable the
38 # sandbox that way.
39 if not chrome_sandbox_path:
40 chrome_sandbox_path = CHROME_SANDBOX_PATH
41 extra_env[CHROME_SANDBOX_ENV] = chrome_sandbox_path
John Abd-El-Malek14ae0542014-10-15 17:52:3142
43 return extra_env
44
45
46def trim_cmd(cmd):
47 """Removes internal flags from cmd since they're just used to communicate from
48 the host machine to this script running on the swarm slaves."""
earthdokeeb065302015-02-04 18:18:0449 sanitizers = ['asan', 'lsan', 'msan', 'tsan']
50 internal_flags = frozenset('--%s=%d' % (name, value)
51 for name in sanitizers
52 for value in [0, 1])
John Abd-El-Malek14ae0542014-10-15 17:52:3153 return [i for i in cmd if i not in internal_flags]
[email protected]76361be452012-08-30 22:48:1454
[email protected]0a88a652012-03-09 00:34:4555
[email protected]4bf4d632012-05-31 15:50:3056def fix_python_path(cmd):
57 """Returns the fixed command line to call the right python executable."""
58 out = cmd[:]
59 if out[0] == 'python':
60 out[0] = sys.executable
61 elif out[0].endswith('.py'):
62 out.insert(0, sys.executable)
63 return out
64
65
pcc46233c22017-06-20 22:11:4166def get_sanitizer_env(cmd, asan, lsan, msan, tsan, cfi_diag):
earthdokeeb065302015-02-04 18:18:0467 """Returns the envirnoment flags needed for sanitizer tools."""
John Abd-El-Malek14ae0542014-10-15 17:52:3168
69 extra_env = {}
70
earthdokeeb065302015-02-04 18:18:0471 # Instruct GTK to use malloc while running sanitizer-instrumented tests.
earthdoke6c54a42014-10-16 18:02:3672 extra_env['G_SLICE'] = 'always-malloc'
John Abd-El-Malek14ae0542014-10-15 17:52:3173
74 extra_env['NSS_DISABLE_ARENA_FREE_LIST'] = '1'
75 extra_env['NSS_DISABLE_UNLOAD'] = '1'
76
77 # TODO(glider): remove the symbolizer path once
78 # https://code.google.com/p/address-sanitizer/issues/detail?id=134 is fixed.
Nico Weber19097312016-01-29 18:13:1579 symbolizer_path = os.path.join(ROOT_DIR,
80 'third_party', 'llvm-build', 'Release+Asserts', 'bin', 'llvm-symbolizer')
John Abd-El-Malek14ae0542014-10-15 17:52:3181
earthdokeeb065302015-02-04 18:18:0482 if lsan or tsan:
John Abd-El-Malek14ae0542014-10-15 17:52:3183 # LSan is not sandbox-compatible, so we can use online symbolization. In
84 # fact, it needs symbolization to be able to apply suppressions.
85 symbolization_options = ['symbolize=1',
86 'external_symbolizer_path=%s' % symbolizer_path]
pcc46233c22017-06-20 22:11:4187 elif (asan or msan or cfi_diag) and sys.platform not in ['win32', 'cygwin']:
timurrrr064f9522015-02-12 15:46:3288 # ASan uses a script for offline symbolization, except on Windows.
John Abd-El-Malek14ae0542014-10-15 17:52:3189 # Important note: when running ASan with leak detection enabled, we must use
90 # the LSan symbolization options above.
91 symbolization_options = ['symbolize=0']
vadimsh5cf3c442014-10-20 10:22:3892 # Set the path to llvm-symbolizer to be used by asan_symbolize.py
93 extra_env['LLVM_SYMBOLIZER_PATH'] = symbolizer_path
timurrrr7e67df92015-02-12 21:50:5294 else:
95 symbolization_options = []
John Abd-El-Malek14ae0542014-10-15 17:52:3196
Yves Gerey2e221522019-03-13 08:46:1997 # Leverage sanitizer to print stack trace on abort (e.g. assertion failure).
98 symbolization_options.append('handle_abort=1')
99
earthdokeeb065302015-02-04 18:18:04100 if asan:
101 asan_options = symbolization_options[:]
102 if lsan:
103 asan_options.append('detect_leaks=1')
John Abd-El-Malek14ae0542014-10-15 17:52:31104
timurrrr7e67df92015-02-12 21:50:52105 if asan_options:
106 extra_env['ASAN_OPTIONS'] = ' '.join(asan_options)
John Abd-El-Malek14ae0542014-10-15 17:52:31107
earthdokeeb065302015-02-04 18:18:04108 if sys.platform == 'darwin':
109 isolate_output_dir = os.path.abspath(os.path.dirname(cmd[0]))
110 # This is needed because the test binary has @executable_path embedded in
111 # it that the OS tries to resolve to the cache directory and not the
112 # mapped directory.
113 extra_env['DYLD_LIBRARY_PATH'] = str(isolate_output_dir)
114
115 if lsan:
116 if asan or msan:
117 lsan_options = []
118 else:
119 lsan_options = symbolization_options[:]
120 if sys.platform == 'linux2':
121 # Use the debug version of libstdc++ under LSan. If we don't, there will
122 # be a lot of incomplete stack traces in the reports.
123 extra_env['LD_LIBRARY_PATH'] = '/usr/lib/x86_64-linux-gnu/debug:'
124
earthdokeeb065302015-02-04 18:18:04125 extra_env['LSAN_OPTIONS'] = ' '.join(lsan_options)
126
127 if msan:
128 msan_options = symbolization_options[:]
129 if lsan:
130 msan_options.append('detect_leaks=1')
131 extra_env['MSAN_OPTIONS'] = ' '.join(msan_options)
132
133 if tsan:
134 tsan_options = symbolization_options[:]
135 extra_env['TSAN_OPTIONS'] = ' '.join(tsan_options)
John Abd-El-Malek14ae0542014-10-15 17:52:31136
pcc46233c22017-06-20 22:11:41137 # CFI uses the UBSan runtime to provide diagnostics.
138 if cfi_diag:
139 ubsan_options = symbolization_options[:] + ['print_stacktrace=1']
140 extra_env['UBSAN_OPTIONS'] = ' '.join(ubsan_options)
141
John Abd-El-Malek14ae0542014-10-15 17:52:31142 return extra_env
143
144
glider9d919342015-02-06 17:42:27145def get_sanitizer_symbolize_command(json_path=None, executable_path=None):
earthdok9bd5d582015-01-29 21:19:36146 """Construct the command to invoke offline symbolization script."""
Nico Weber19097312016-01-29 18:13:15147 script_path = os.path.join(
148 ROOT_DIR, 'tools', 'valgrind', 'asan', 'asan_symbolize.py')
earthdok9bd5d582015-01-29 21:19:36149 cmd = [sys.executable, script_path]
150 if json_path is not None:
151 cmd.append('--test-summary-json-file=%s' % json_path)
glider9d919342015-02-06 17:42:27152 if executable_path is not None:
153 cmd.append('--executable-path=%s' % executable_path)
earthdok9bd5d582015-01-29 21:19:36154 return cmd
155
156
157def get_json_path(cmd):
158 """Extract the JSON test summary path from a command line."""
159 json_path_flag = '--test-launcher-summary-output='
160 for arg in cmd:
161 if arg.startswith(json_path_flag):
162 return arg.split(json_path_flag).pop()
163 return None
164
165
166def symbolize_snippets_in_json(cmd, env):
167 """Symbolize output snippets inside the JSON test summary."""
168 json_path = get_json_path(cmd)
169 if json_path is None:
170 return
171
172 try:
glider9d919342015-02-06 17:42:27173 symbolize_command = get_sanitizer_symbolize_command(
174 json_path=json_path, executable_path=cmd[0])
earthdok9bd5d582015-01-29 21:19:36175 p = subprocess.Popen(symbolize_command, stderr=subprocess.PIPE, env=env)
176 (_, stderr) = p.communicate()
177 except OSError as e:
Chris McDonaldc3e0f26b2020-06-04 02:15:14178 print('Exception while symbolizing snippets: %s' % e, file=sys.stderr)
thakis45643b42016-01-29 21:53:42179 raise
earthdok9bd5d582015-01-29 21:19:36180
181 if p.returncode != 0:
Chris McDonaldc3e0f26b2020-06-04 02:15:14182 print("Error: failed to symbolize snippets in JSON:\n", file=sys.stderr)
183 print(stderr, file=sys.stderr)
thakis45643b42016-01-29 21:53:42184 raise subprocess.CalledProcessError(p.returncode, symbolize_command)
earthdok9bd5d582015-01-29 21:19:36185
186
Trent Aptedb1852432018-01-25 02:15:10187def run_command_with_output(argv, stdoutfile, env=None, cwd=None):
Caleb Rouleaufd511292019-02-22 18:22:00188 """Run command and stream its stdout/stderr to the console & |stdoutfile|.
189
190 Also forward_signals to obey
191 https://chromium.googlesource.com/infra/luci/luci-py/+/master/appengine/swarming/doc/Bot.md#graceful-termination_aka-the-sigterm-and-sigkill-dance
192
193 Returns:
194 integer returncode of the subprocess.
Trent Aptedb1852432018-01-25 02:15:10195 """
Sorin Jianue11c9852019-09-12 20:43:55196 print('Running %r in %r (env: %r)' % (argv, cwd, env))
Trent Aptedb1852432018-01-25 02:15:10197 assert stdoutfile
Ned Nguyen7f43ff62018-10-04 04:58:46198 with io.open(stdoutfile, 'wb') as writer, \
199 io.open(stdoutfile, 'rb', 1) as reader:
Caleb Rouleau6844df152019-09-11 01:11:59200 process = _popen(argv, env=env, cwd=cwd, stdout=writer,
201 stderr=subprocess.STDOUT)
Ben Pastenee5ece812018-06-20 22:39:34202 forward_signals([process])
Trent Aptedb1852432018-01-25 02:15:10203 while process.poll() is None:
204 sys.stdout.write(reader.read())
Caleb Rouleaufd511292019-02-22 18:22:00205 # This sleep is needed for signal propagation. See the
206 # wait_with_signals() docstring.
Trent Aptedb1852432018-01-25 02:15:10207 time.sleep(0.1)
208 # Read the remaining.
209 sys.stdout.write(reader.read())
Sorin Jianue11c9852019-09-12 20:43:55210 print('Command %r returned exit code %d' % (argv, process.returncode))
Trent Aptedb1852432018-01-25 02:15:10211 return process.returncode
212
213
Caleb Rouleaufd511292019-02-22 18:22:00214def run_command(argv, env=None, cwd=None, log=True):
215 """Run command and stream its stdout/stderr both to stdout.
216
217 Also forward_signals to obey
218 https://chromium.googlesource.com/infra/luci/luci-py/+/master/appengine/swarming/doc/Bot.md#graceful-termination_aka-the-sigterm-and-sigkill-dance
219
220 Returns:
221 integer returncode of the subprocess.
222 """
223 if log:
Sorin Jianue11c9852019-09-12 20:43:55224 print('Running %r in %r (env: %r)' % (argv, cwd, env))
Caleb Rouleau6844df152019-09-11 01:11:59225 process = _popen(argv, env=env, cwd=cwd, stderr=subprocess.STDOUT)
Caleb Rouleaufd511292019-02-22 18:22:00226 forward_signals([process])
227 return wait_with_signals(process)
228
229
Caleb Rouleau84e3e812019-05-30 23:34:50230def run_command_output_to_handle(argv, file_handle, env=None, cwd=None):
231 """Run command and stream its stdout/stderr both to |file_handle|.
232
233 Also forward_signals to obey
234 https://chromium.googlesource.com/infra/luci/luci-py/+/master/appengine/swarming/doc/Bot.md#graceful-termination_aka-the-sigterm-and-sigkill-dance
235
236 Returns:
237 integer returncode of the subprocess.
238 """
Sorin Jianue11c9852019-09-12 20:43:55239 print('Running %r in %r (env: %r)' % (argv, cwd, env))
Caleb Rouleau6844df152019-09-11 01:11:59240 process = _popen(
Caleb Rouleau84e3e812019-05-30 23:34:50241 argv, env=env, cwd=cwd, stderr=file_handle, stdout=file_handle)
242 forward_signals([process])
243 exit_code = wait_with_signals(process)
Sorin Jianue11c9852019-09-12 20:43:55244 print('Command returned exit code %d' % exit_code)
Caleb Rouleau84e3e812019-05-30 23:34:50245 return exit_code
246
247
Caleb Rouleaufd511292019-02-22 18:22:00248def wait_with_signals(process):
249 """A version of process.wait() that works cross-platform.
250
251 This version properly surfaces the SIGBREAK signal.
252
253 From reading the subprocess.py source code, it seems we need to explicitly
254 call time.sleep(). The reason is that subprocess.Popen.wait() on Windows
255 directly calls WaitForSingleObject(), but only time.sleep() properly surface
256 the SIGBREAK signal.
257
258 Refs:
259 https://github.com/python/cpython/blob/v2.7.15/Lib/subprocess.py#L692
260 https://github.com/python/cpython/blob/v2.7.15/Modules/timemodule.c#L1084
261
262 Returns:
263 returncode of the process.
264 """
265 while process.poll() is None:
266 time.sleep(0.1)
267 return process.returncode
268
269
Ben Pastenee5ece812018-06-20 22:39:34270def forward_signals(procs):
271 """Forwards unix's SIGTERM or win's CTRL_BREAK_EVENT to the given processes.
272
273 This plays nicely with swarming's timeout handling. See also
274 https://chromium.googlesource.com/infra/luci/luci-py/+/master/appengine/swarming/doc/Bot.md#graceful-termination_aka-the-sigterm-and-sigkill-dance
275
276 Args:
277 procs: A list of subprocess.Popen objects representing child processes.
278 """
279 assert all(isinstance(p, subprocess.Popen) for p in procs)
280 def _sig_handler(sig, _):
281 for p in procs:
Ilia Samsonova00835302019-04-19 17:37:59282 if p.poll() is not None:
283 continue
Andrew Grieve95a2d332018-06-28 02:22:59284 # SIGBREAK is defined only for win32.
285 if sys.platform == 'win32' and sig == signal.SIGBREAK:
Ben Pastenee5ece812018-06-20 22:39:34286 p.send_signal(signal.CTRL_BREAK_EVENT)
287 else:
288 p.send_signal(sig)
289 if sys.platform == 'win32':
290 signal.signal(signal.SIGBREAK, _sig_handler)
291 else:
292 signal.signal(signal.SIGTERM, _sig_handler)
Ilia Samsonova00835302019-04-19 17:37:59293 signal.signal(signal.SIGINT, _sig_handler)
Ben Pastenee5ece812018-06-20 22:39:34294
Haiyang Pan94c26592019-08-13 17:48:11295
Trent Aptedb1852432018-01-25 02:15:10296def run_executable(cmd, env, stdoutfile=None):
[email protected]0a88a652012-03-09 00:34:45297 """Runs an executable with:
Dirk Pranke50e557b2017-12-01 23:48:09298 - CHROME_HEADLESS set to indicate that the test is running on a
299 bot and shouldn't do anything interactive like show modal dialogs.
[email protected]3766ed1c2012-07-26 20:53:56300 - environment variable CR_SOURCE_ROOT set to the root directory.
[email protected]0a88a652012-03-09 00:34:45301 - environment variable LANGUAGE to en_US.UTF-8.
earthdok748e1352015-01-27 23:04:08302 - environment variable CHROME_DEVEL_SANDBOX set
[email protected]0a88a652012-03-09 00:34:45303 - Reuses sys.executable automatically.
304 """
Dirk Pranke50e557b2017-12-01 23:48:09305 extra_env = {
306 # Set to indicate that the executable is running non-interactively on
307 # a bot.
308 'CHROME_HEADLESS': '1',
309
310 # Many tests assume a English interface...
311 'LANG': 'en_US.UTF-8',
312 }
313
[email protected]3766ed1c2012-07-26 20:53:56314 # Used by base/base_paths_linux.cc as an override. Just make sure the default
315 # logic is used.
316 env.pop('CR_SOURCE_ROOT', None)
earthdok748e1352015-01-27 23:04:08317 extra_env.update(get_sandbox_env(env))
John Abd-El-Malek4569c422014-10-09 05:10:53318
319 # Copy logic from tools/build/scripts/slave/runtest.py.
320 asan = '--asan=1' in cmd
321 lsan = '--lsan=1' in cmd
earthdokeeb065302015-02-04 18:18:04322 msan = '--msan=1' in cmd
323 tsan = '--tsan=1' in cmd
pcc46233c22017-06-20 22:11:41324 cfi_diag = '--cfi-diag=1' in cmd
Trent Aptedb1852432018-01-25 02:15:10325 if stdoutfile or sys.platform in ['win32', 'cygwin']:
timurrrr064f9522015-02-12 15:46:32326 # Symbolization works in-process on Windows even when sandboxed.
327 use_symbolization_script = False
328 else:
Ilia Samsonov4ea016762019-12-02 22:11:13329 # If any sanitizer is enabled, we print unsymbolized stack trace
330 # that is required to run through symbolization script.
331 use_symbolization_script = (asan or msan or cfi_diag or lsan or tsan)
John Abd-El-Malek4569c422014-10-09 05:10:53332
pcc46233c22017-06-20 22:11:41333 if asan or lsan or msan or tsan or cfi_diag:
334 extra_env.update(get_sanitizer_env(cmd, asan, lsan, msan, tsan, cfi_diag))
earthdokeeb065302015-02-04 18:18:04335
Timur Iskhodzhanovfdbfd4e12015-02-05 13:50:23336 if lsan or tsan:
337 # LSan and TSan are not sandbox-friendly.
338 cmd.append('--no-sandbox')
John Abd-El-Malek14ae0542014-10-15 17:52:31339
340 cmd = trim_cmd(cmd)
John Abd-El-Malek4569c422014-10-09 05:10:53341
[email protected]8ba98352012-05-23 20:43:59342 # Ensure paths are correctly separated on windows.
343 cmd[0] = cmd[0].replace('/', os.path.sep)
[email protected]4bf4d632012-05-31 15:50:30344 cmd = fix_python_path(cmd)
John Abd-El-Malek14ae0542014-10-15 17:52:31345
Dirk Pranke2cf1a152019-04-03 01:34:38346 # We also want to print the GTEST env vars that were set by the caller,
347 # because you need them to reproduce the task properly.
348 env_to_print = extra_env.copy()
349 for env_var_name in ('GTEST_SHARD_INDEX', 'GTEST_TOTAL_SHARDS'):
Chris McDonaldc3e0f26b2020-06-04 02:15:14350 if env_var_name in env:
351 env_to_print[env_var_name] = env[env_var_name]
Dirk Pranke2cf1a152019-04-03 01:34:38352
Sorin Jianue11c9852019-09-12 20:43:55353 print('Additional test environment:\n%s\n'
John Abd-El-Malek14ae0542014-10-15 17:52:31354 'Command: %s\n' % (
355 '\n'.join(' %s=%s' %
Chris McDonaldc3e0f26b2020-06-04 02:15:14356 (k, v) for k, v in sorted(six.iteritems(env_to_print))),
Sorin Jianue11c9852019-09-12 20:43:55357 ' '.join(cmd)))
maruelf00125f82016-11-19 00:01:14358 sys.stdout.flush()
John Abd-El-Malek14ae0542014-10-15 17:52:31359 env.update(extra_env or {})
[email protected]50ec9f232012-03-16 04:18:23360 try:
Trent Aptedb1852432018-01-25 02:15:10361 if stdoutfile:
362 # Write to stdoutfile and poll to produce terminal output.
363 return run_command_with_output(cmd, env=env, stdoutfile=stdoutfile)
364 elif use_symbolization_script:
365 # See above comment regarding offline symbolization.
John Abd-El-Malek4569c422014-10-09 05:10:53366 # Need to pipe to the symbolizer script.
Caleb Rouleau6844df152019-09-11 01:11:59367 p1 = _popen(cmd, env=env, stdout=subprocess.PIPE,
368 stderr=sys.stdout)
369 p2 = _popen(
glider9d919342015-02-06 17:42:27370 get_sanitizer_symbolize_command(executable_path=cmd[0]),
371 env=env, stdin=p1.stdout)
John Abd-El-Malek4569c422014-10-09 05:10:53372 p1.stdout.close() # Allow p1 to receive a SIGPIPE if p2 exits.
Ben Pastenee5ece812018-06-20 22:39:34373 forward_signals([p1, p2])
Caleb Rouleaufd511292019-02-22 18:22:00374 wait_with_signals(p1)
375 wait_with_signals(p2)
earthdok9bd5d582015-01-29 21:19:36376 # Also feed the out-of-band JSON output to the symbolizer script.
377 symbolize_snippets_in_json(cmd, env)
vadimsh1eaeb2982014-10-20 12:28:45378 return p1.returncode
John Abd-El-Malek4569c422014-10-09 05:10:53379 else:
Caleb Rouleaufd511292019-02-22 18:22:00380 return run_command(cmd, env=env, log=False)
[email protected]50ec9f232012-03-16 04:18:23381 except OSError:
Chris McDonaldc3e0f26b2020-06-04 02:15:14382 print('Failed to start %s' % cmd, file=sys.stderr)
[email protected]50ec9f232012-03-16 04:18:23383 raise
[email protected]0a88a652012-03-09 00:34:45384
385
Caleb Rouleau6844df152019-09-11 01:11:59386def _popen(*args, **kwargs):
387 assert 'creationflags' not in kwargs
388 if sys.platform == 'win32':
389 # Necessary for signal handling. See crbug.com/733612#c6.
390 kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP
391 return subprocess.Popen(*args, **kwargs)
392
393
[email protected]0a88a652012-03-09 00:34:45394def main():
gliderb73f1872014-10-09 16:24:56395 return run_executable(sys.argv[1:], os.environ.copy())
[email protected]0a88a652012-03-09 00:34:45396
397
[email protected]ed763a72012-08-29 03:51:22398if __name__ == '__main__':
[email protected]0a88a652012-03-09 00:34:45399 sys.exit(main())