xref: /aosp_15_r20/external/cronet/testing/test_env.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1*6777b538SAndroid Build Coastguard Worker#!/usr/bin/env python
2*6777b538SAndroid Build Coastguard Worker# Copyright 2012 The Chromium Authors
3*6777b538SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be
4*6777b538SAndroid Build Coastguard Worker# found in the LICENSE file.
5*6777b538SAndroid Build Coastguard Worker
6*6777b538SAndroid Build Coastguard Worker"""Sets environment variables needed to run a chromium unit test."""
7*6777b538SAndroid Build Coastguard Worker
8*6777b538SAndroid Build Coastguard Workerfrom __future__ import print_function
9*6777b538SAndroid Build Coastguard Workerimport io
10*6777b538SAndroid Build Coastguard Workerimport os
11*6777b538SAndroid Build Coastguard Workerimport signal
12*6777b538SAndroid Build Coastguard Workerimport subprocess
13*6777b538SAndroid Build Coastguard Workerimport sys
14*6777b538SAndroid Build Coastguard Workerimport time
15*6777b538SAndroid Build Coastguard Worker
16*6777b538SAndroid Build Coastguard Worker
17*6777b538SAndroid Build Coastguard Worker# This is hardcoded to be src/ relative to this script.
18*6777b538SAndroid Build Coastguard WorkerROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
19*6777b538SAndroid Build Coastguard Worker
20*6777b538SAndroid Build Coastguard Worker
21*6777b538SAndroid Build Coastguard Workerdef trim_cmd(cmd):
22*6777b538SAndroid Build Coastguard Worker  """Removes internal flags from cmd since they're just used to communicate from
23*6777b538SAndroid Build Coastguard Worker  the host machine to this script running on the swarm slaves."""
24*6777b538SAndroid Build Coastguard Worker  sanitizers = ['asan', 'lsan', 'msan', 'tsan', 'coverage-continuous-mode',
25*6777b538SAndroid Build Coastguard Worker                'skip-set-lpac-acls']
26*6777b538SAndroid Build Coastguard Worker  internal_flags = frozenset('--%s=%d' % (name, value)
27*6777b538SAndroid Build Coastguard Worker                             for name in sanitizers
28*6777b538SAndroid Build Coastguard Worker                             for value in [0, 1])
29*6777b538SAndroid Build Coastguard Worker  return [i for i in cmd if i not in internal_flags]
30*6777b538SAndroid Build Coastguard Worker
31*6777b538SAndroid Build Coastguard Worker
32*6777b538SAndroid Build Coastguard Workerdef fix_python_path(cmd):
33*6777b538SAndroid Build Coastguard Worker  """Returns the fixed command line to call the right python executable."""
34*6777b538SAndroid Build Coastguard Worker  out = cmd[:]
35*6777b538SAndroid Build Coastguard Worker  if out[0] == 'python':
36*6777b538SAndroid Build Coastguard Worker    out[0] = sys.executable
37*6777b538SAndroid Build Coastguard Worker  elif out[0].endswith('.py'):
38*6777b538SAndroid Build Coastguard Worker    out.insert(0, sys.executable)
39*6777b538SAndroid Build Coastguard Worker  return out
40*6777b538SAndroid Build Coastguard Worker
41*6777b538SAndroid Build Coastguard Worker
42*6777b538SAndroid Build Coastguard Workerdef get_sanitizer_env(asan, lsan, msan, tsan, cfi_diag):
43*6777b538SAndroid Build Coastguard Worker  """Returns the environment flags needed for sanitizer tools."""
44*6777b538SAndroid Build Coastguard Worker
45*6777b538SAndroid Build Coastguard Worker  extra_env = {}
46*6777b538SAndroid Build Coastguard Worker
47*6777b538SAndroid Build Coastguard Worker  # Instruct GTK to use malloc while running sanitizer-instrumented tests.
48*6777b538SAndroid Build Coastguard Worker  extra_env['G_SLICE'] = 'always-malloc'
49*6777b538SAndroid Build Coastguard Worker
50*6777b538SAndroid Build Coastguard Worker  extra_env['NSS_DISABLE_ARENA_FREE_LIST'] = '1'
51*6777b538SAndroid Build Coastguard Worker  extra_env['NSS_DISABLE_UNLOAD'] = '1'
52*6777b538SAndroid Build Coastguard Worker
53*6777b538SAndroid Build Coastguard Worker  # TODO(glider): remove the symbolizer path once
54*6777b538SAndroid Build Coastguard Worker  # https://code.google.com/p/address-sanitizer/issues/detail?id=134 is fixed.
55*6777b538SAndroid Build Coastguard Worker  symbolizer_path = os.path.join(ROOT_DIR,
56*6777b538SAndroid Build Coastguard Worker      'third_party', 'llvm-build', 'Release+Asserts', 'bin', 'llvm-symbolizer')
57*6777b538SAndroid Build Coastguard Worker
58*6777b538SAndroid Build Coastguard Worker  if lsan or tsan:
59*6777b538SAndroid Build Coastguard Worker    # LSan is not sandbox-compatible, so we can use online symbolization. In
60*6777b538SAndroid Build Coastguard Worker    # fact, it needs symbolization to be able to apply suppressions.
61*6777b538SAndroid Build Coastguard Worker    symbolization_options = ['symbolize=1',
62*6777b538SAndroid Build Coastguard Worker                             'external_symbolizer_path=%s' % symbolizer_path]
63*6777b538SAndroid Build Coastguard Worker  elif (asan or msan or cfi_diag) and sys.platform not in ['win32', 'cygwin']:
64*6777b538SAndroid Build Coastguard Worker    # ASan uses a script for offline symbolization, except on Windows.
65*6777b538SAndroid Build Coastguard Worker    # Important note: when running ASan with leak detection enabled, we must use
66*6777b538SAndroid Build Coastguard Worker    # the LSan symbolization options above.
67*6777b538SAndroid Build Coastguard Worker    symbolization_options = ['symbolize=0']
68*6777b538SAndroid Build Coastguard Worker    # Set the path to llvm-symbolizer to be used by asan_symbolize.py
69*6777b538SAndroid Build Coastguard Worker    extra_env['LLVM_SYMBOLIZER_PATH'] = symbolizer_path
70*6777b538SAndroid Build Coastguard Worker  else:
71*6777b538SAndroid Build Coastguard Worker    symbolization_options = []
72*6777b538SAndroid Build Coastguard Worker
73*6777b538SAndroid Build Coastguard Worker  # Leverage sanitizer to print stack trace on abort (e.g. assertion failure).
74*6777b538SAndroid Build Coastguard Worker  symbolization_options.append('handle_abort=1')
75*6777b538SAndroid Build Coastguard Worker
76*6777b538SAndroid Build Coastguard Worker  if asan:
77*6777b538SAndroid Build Coastguard Worker    asan_options = symbolization_options[:]
78*6777b538SAndroid Build Coastguard Worker    if lsan:
79*6777b538SAndroid Build Coastguard Worker      asan_options.append('detect_leaks=1')
80*6777b538SAndroid Build Coastguard Worker      # LSan appears to have trouble with later versions of glibc.
81*6777b538SAndroid Build Coastguard Worker      # See https://github.com/google/sanitizers/issues/1322
82*6777b538SAndroid Build Coastguard Worker      if 'linux' in sys.platform:
83*6777b538SAndroid Build Coastguard Worker        asan_options.append('intercept_tls_get_addr=0')
84*6777b538SAndroid Build Coastguard Worker
85*6777b538SAndroid Build Coastguard Worker    if asan_options:
86*6777b538SAndroid Build Coastguard Worker      extra_env['ASAN_OPTIONS'] = ' '.join(asan_options)
87*6777b538SAndroid Build Coastguard Worker
88*6777b538SAndroid Build Coastguard Worker  if lsan:
89*6777b538SAndroid Build Coastguard Worker    if asan or msan:
90*6777b538SAndroid Build Coastguard Worker      lsan_options = []
91*6777b538SAndroid Build Coastguard Worker    else:
92*6777b538SAndroid Build Coastguard Worker      lsan_options = symbolization_options[:]
93*6777b538SAndroid Build Coastguard Worker    if sys.platform == 'linux2':
94*6777b538SAndroid Build Coastguard Worker      # Use the debug version of libstdc++ under LSan. If we don't, there will
95*6777b538SAndroid Build Coastguard Worker      # be a lot of incomplete stack traces in the reports.
96*6777b538SAndroid Build Coastguard Worker      extra_env['LD_LIBRARY_PATH'] = '/usr/lib/x86_64-linux-gnu/debug:'
97*6777b538SAndroid Build Coastguard Worker
98*6777b538SAndroid Build Coastguard Worker    extra_env['LSAN_OPTIONS'] = ' '.join(lsan_options)
99*6777b538SAndroid Build Coastguard Worker
100*6777b538SAndroid Build Coastguard Worker  if msan:
101*6777b538SAndroid Build Coastguard Worker    msan_options = symbolization_options[:]
102*6777b538SAndroid Build Coastguard Worker    if lsan:
103*6777b538SAndroid Build Coastguard Worker      msan_options.append('detect_leaks=1')
104*6777b538SAndroid Build Coastguard Worker    extra_env['MSAN_OPTIONS'] = ' '.join(msan_options)
105*6777b538SAndroid Build Coastguard Worker    extra_env['VK_ICD_FILENAMES'] = ''
106*6777b538SAndroid Build Coastguard Worker    extra_env['LIBGL_DRIVERS_PATH'] = ''
107*6777b538SAndroid Build Coastguard Worker
108*6777b538SAndroid Build Coastguard Worker  if tsan:
109*6777b538SAndroid Build Coastguard Worker    tsan_options = symbolization_options[:]
110*6777b538SAndroid Build Coastguard Worker    extra_env['TSAN_OPTIONS'] = ' '.join(tsan_options)
111*6777b538SAndroid Build Coastguard Worker
112*6777b538SAndroid Build Coastguard Worker  # CFI uses the UBSan runtime to provide diagnostics.
113*6777b538SAndroid Build Coastguard Worker  if cfi_diag:
114*6777b538SAndroid Build Coastguard Worker    ubsan_options = symbolization_options[:] + ['print_stacktrace=1']
115*6777b538SAndroid Build Coastguard Worker    extra_env['UBSAN_OPTIONS'] = ' '.join(ubsan_options)
116*6777b538SAndroid Build Coastguard Worker
117*6777b538SAndroid Build Coastguard Worker  return extra_env
118*6777b538SAndroid Build Coastguard Worker
119*6777b538SAndroid Build Coastguard Workerdef get_coverage_continuous_mode_env(env):
120*6777b538SAndroid Build Coastguard Worker  """Append %c (clang code coverage continuous mode) flag to LLVM_PROFILE_FILE
121*6777b538SAndroid Build Coastguard Worker  pattern string."""
122*6777b538SAndroid Build Coastguard Worker  llvm_profile_file = env.get('LLVM_PROFILE_FILE')
123*6777b538SAndroid Build Coastguard Worker  if not llvm_profile_file:
124*6777b538SAndroid Build Coastguard Worker    return {}
125*6777b538SAndroid Build Coastguard Worker
126*6777b538SAndroid Build Coastguard Worker  # Do not insert %c into LLVM_PROFILE_FILE if it's already there as that'll
127*6777b538SAndroid Build Coastguard Worker  # cause the coverage instrumentation to write coverage data to default.profraw
128*6777b538SAndroid Build Coastguard Worker  # instead of LLVM_PROFILE_FILE.
129*6777b538SAndroid Build Coastguard Worker  if "%c" in llvm_profile_file:
130*6777b538SAndroid Build Coastguard Worker    return {
131*6777b538SAndroid Build Coastguard Worker      'LLVM_PROFILE_FILE': llvm_profile_file
132*6777b538SAndroid Build Coastguard Worker    }
133*6777b538SAndroid Build Coastguard Worker
134*6777b538SAndroid Build Coastguard Worker  dirname, basename = os.path.split(llvm_profile_file)
135*6777b538SAndroid Build Coastguard Worker  root, ext = os.path.splitext(basename)
136*6777b538SAndroid Build Coastguard Worker
137*6777b538SAndroid Build Coastguard Worker  return {
138*6777b538SAndroid Build Coastguard Worker    'LLVM_PROFILE_FILE': os.path.join(dirname, root + "%c" + ext)
139*6777b538SAndroid Build Coastguard Worker  }
140*6777b538SAndroid Build Coastguard Worker
141*6777b538SAndroid Build Coastguard Workerdef get_sanitizer_symbolize_command(json_path=None, executable_path=None):
142*6777b538SAndroid Build Coastguard Worker  """Construct the command to invoke offline symbolization script."""
143*6777b538SAndroid Build Coastguard Worker  script_path = os.path.join(
144*6777b538SAndroid Build Coastguard Worker      ROOT_DIR, 'tools', 'valgrind', 'asan', 'asan_symbolize.py')
145*6777b538SAndroid Build Coastguard Worker  cmd = [sys.executable, script_path]
146*6777b538SAndroid Build Coastguard Worker  if json_path is not None:
147*6777b538SAndroid Build Coastguard Worker    cmd.append('--test-summary-json-file=%s' % json_path)
148*6777b538SAndroid Build Coastguard Worker  if executable_path is not None:
149*6777b538SAndroid Build Coastguard Worker    cmd.append('--executable-path=%s' % executable_path)
150*6777b538SAndroid Build Coastguard Worker  return cmd
151*6777b538SAndroid Build Coastguard Worker
152*6777b538SAndroid Build Coastguard Worker
153*6777b538SAndroid Build Coastguard Workerdef get_json_path(cmd):
154*6777b538SAndroid Build Coastguard Worker  """Extract the JSON test summary path from a command line."""
155*6777b538SAndroid Build Coastguard Worker  json_path_flag = '--test-launcher-summary-output='
156*6777b538SAndroid Build Coastguard Worker  for arg in cmd:
157*6777b538SAndroid Build Coastguard Worker    if arg.startswith(json_path_flag):
158*6777b538SAndroid Build Coastguard Worker      return arg.split(json_path_flag).pop()
159*6777b538SAndroid Build Coastguard Worker  return None
160*6777b538SAndroid Build Coastguard Worker
161*6777b538SAndroid Build Coastguard Worker
162*6777b538SAndroid Build Coastguard Workerdef symbolize_snippets_in_json(cmd, env):
163*6777b538SAndroid Build Coastguard Worker  """Symbolize output snippets inside the JSON test summary."""
164*6777b538SAndroid Build Coastguard Worker  json_path = get_json_path(cmd)
165*6777b538SAndroid Build Coastguard Worker  if json_path is None:
166*6777b538SAndroid Build Coastguard Worker    return
167*6777b538SAndroid Build Coastguard Worker
168*6777b538SAndroid Build Coastguard Worker  try:
169*6777b538SAndroid Build Coastguard Worker    symbolize_command = get_sanitizer_symbolize_command(
170*6777b538SAndroid Build Coastguard Worker        json_path=json_path, executable_path=cmd[0])
171*6777b538SAndroid Build Coastguard Worker    p = subprocess.Popen(symbolize_command, stderr=subprocess.PIPE, env=env)
172*6777b538SAndroid Build Coastguard Worker    (_, stderr) = p.communicate()
173*6777b538SAndroid Build Coastguard Worker  except OSError as e:
174*6777b538SAndroid Build Coastguard Worker    print('Exception while symbolizing snippets: %s' % e, file=sys.stderr)
175*6777b538SAndroid Build Coastguard Worker    raise
176*6777b538SAndroid Build Coastguard Worker
177*6777b538SAndroid Build Coastguard Worker  if p.returncode != 0:
178*6777b538SAndroid Build Coastguard Worker    print("Error: failed to symbolize snippets in JSON:\n", file=sys.stderr)
179*6777b538SAndroid Build Coastguard Worker    print(stderr, file=sys.stderr)
180*6777b538SAndroid Build Coastguard Worker    raise subprocess.CalledProcessError(p.returncode, symbolize_command)
181*6777b538SAndroid Build Coastguard Worker
182*6777b538SAndroid Build Coastguard Worker
183*6777b538SAndroid Build Coastguard Workerdef get_escalate_sanitizer_warnings_command(json_path):
184*6777b538SAndroid Build Coastguard Worker  """Construct the command to invoke sanitizer warnings script."""
185*6777b538SAndroid Build Coastguard Worker  script_path = os.path.join(
186*6777b538SAndroid Build Coastguard Worker      ROOT_DIR, 'tools', 'memory', 'sanitizer',
187*6777b538SAndroid Build Coastguard Worker      'escalate_sanitizer_warnings.py')
188*6777b538SAndroid Build Coastguard Worker  cmd = [sys.executable, script_path]
189*6777b538SAndroid Build Coastguard Worker  cmd.append('--test-summary-json-file=%s' % json_path)
190*6777b538SAndroid Build Coastguard Worker  return cmd
191*6777b538SAndroid Build Coastguard Worker
192*6777b538SAndroid Build Coastguard Worker
193*6777b538SAndroid Build Coastguard Workerdef escalate_sanitizer_warnings_in_json(cmd, env):
194*6777b538SAndroid Build Coastguard Worker  """Escalate sanitizer warnings inside the JSON test summary."""
195*6777b538SAndroid Build Coastguard Worker  json_path = get_json_path(cmd)
196*6777b538SAndroid Build Coastguard Worker  if json_path is None:
197*6777b538SAndroid Build Coastguard Worker    print("Warning: Cannot escalate sanitizer warnings without a json summary "
198*6777b538SAndroid Build Coastguard Worker          "file:\n", file=sys.stderr)
199*6777b538SAndroid Build Coastguard Worker    return 0
200*6777b538SAndroid Build Coastguard Worker
201*6777b538SAndroid Build Coastguard Worker  try:
202*6777b538SAndroid Build Coastguard Worker    escalate_command = get_escalate_sanitizer_warnings_command(json_path)
203*6777b538SAndroid Build Coastguard Worker    p = subprocess.Popen(escalate_command, stderr=subprocess.PIPE, env=env)
204*6777b538SAndroid Build Coastguard Worker    (_, stderr) = p.communicate()
205*6777b538SAndroid Build Coastguard Worker  except OSError as e:
206*6777b538SAndroid Build Coastguard Worker    print('Exception while escalating sanitizer warnings: %s' % e,
207*6777b538SAndroid Build Coastguard Worker          file=sys.stderr)
208*6777b538SAndroid Build Coastguard Worker    raise
209*6777b538SAndroid Build Coastguard Worker
210*6777b538SAndroid Build Coastguard Worker  if p.returncode != 0:
211*6777b538SAndroid Build Coastguard Worker    print("Error: failed to escalate sanitizer warnings status in JSON:\n",
212*6777b538SAndroid Build Coastguard Worker          file=sys.stderr)
213*6777b538SAndroid Build Coastguard Worker    print(stderr, file=sys.stderr)
214*6777b538SAndroid Build Coastguard Worker  return p.returncode
215*6777b538SAndroid Build Coastguard Worker
216*6777b538SAndroid Build Coastguard Worker
217*6777b538SAndroid Build Coastguard Worker
218*6777b538SAndroid Build Coastguard Workerdef run_command_with_output(argv, stdoutfile, env=None, cwd=None):
219*6777b538SAndroid Build Coastguard Worker  """Run command and stream its stdout/stderr to the console & |stdoutfile|.
220*6777b538SAndroid Build Coastguard Worker
221*6777b538SAndroid Build Coastguard Worker  Also forward_signals to obey
222*6777b538SAndroid Build Coastguard Worker  https://chromium.googlesource.com/infra/luci/luci-py/+/main/appengine/swarming/doc/Bot.md#graceful-termination_aka-the-sigterm-and-sigkill-dance
223*6777b538SAndroid Build Coastguard Worker
224*6777b538SAndroid Build Coastguard Worker  Returns:
225*6777b538SAndroid Build Coastguard Worker    integer returncode of the subprocess.
226*6777b538SAndroid Build Coastguard Worker  """
227*6777b538SAndroid Build Coastguard Worker  print('Running %r in %r (env: %r)' % (argv, cwd, env), file=sys.stderr)
228*6777b538SAndroid Build Coastguard Worker  assert stdoutfile
229*6777b538SAndroid Build Coastguard Worker  with io.open(stdoutfile, 'wb') as writer, \
230*6777b538SAndroid Build Coastguard Worker      io.open(stdoutfile, 'rb', 1) as reader:
231*6777b538SAndroid Build Coastguard Worker    process = _popen(argv, env=env, cwd=cwd, stdout=writer,
232*6777b538SAndroid Build Coastguard Worker                     stderr=subprocess.STDOUT)
233*6777b538SAndroid Build Coastguard Worker    forward_signals([process])
234*6777b538SAndroid Build Coastguard Worker    while process.poll() is None:
235*6777b538SAndroid Build Coastguard Worker      sys.stdout.write(reader.read().decode('utf-8'))
236*6777b538SAndroid Build Coastguard Worker      # This sleep is needed for signal propagation. See the
237*6777b538SAndroid Build Coastguard Worker      # wait_with_signals() docstring.
238*6777b538SAndroid Build Coastguard Worker      time.sleep(0.1)
239*6777b538SAndroid Build Coastguard Worker    # Read the remaining.
240*6777b538SAndroid Build Coastguard Worker    sys.stdout.write(reader.read().decode('utf-8'))
241*6777b538SAndroid Build Coastguard Worker    print('Command %r returned exit code %d' % (argv, process.returncode),
242*6777b538SAndroid Build Coastguard Worker          file=sys.stderr)
243*6777b538SAndroid Build Coastguard Worker    return process.returncode
244*6777b538SAndroid Build Coastguard Worker
245*6777b538SAndroid Build Coastguard Worker
246*6777b538SAndroid Build Coastguard Workerdef run_command(argv, env=None, cwd=None, log=True):
247*6777b538SAndroid Build Coastguard Worker  """Run command and stream its stdout/stderr both to stdout.
248*6777b538SAndroid Build Coastguard Worker
249*6777b538SAndroid Build Coastguard Worker  Also forward_signals to obey
250*6777b538SAndroid Build Coastguard Worker  https://chromium.googlesource.com/infra/luci/luci-py/+/main/appengine/swarming/doc/Bot.md#graceful-termination_aka-the-sigterm-and-sigkill-dance
251*6777b538SAndroid Build Coastguard Worker
252*6777b538SAndroid Build Coastguard Worker  Returns:
253*6777b538SAndroid Build Coastguard Worker    integer returncode of the subprocess.
254*6777b538SAndroid Build Coastguard Worker  """
255*6777b538SAndroid Build Coastguard Worker  if log:
256*6777b538SAndroid Build Coastguard Worker    print('Running %r in %r (env: %r)' % (argv, cwd, env), file=sys.stderr)
257*6777b538SAndroid Build Coastguard Worker  process = _popen(argv, env=env, cwd=cwd, stderr=subprocess.STDOUT)
258*6777b538SAndroid Build Coastguard Worker  forward_signals([process])
259*6777b538SAndroid Build Coastguard Worker  exit_code = wait_with_signals(process)
260*6777b538SAndroid Build Coastguard Worker  if log:
261*6777b538SAndroid Build Coastguard Worker    print('Command returned exit code %d' % exit_code, file=sys.stderr)
262*6777b538SAndroid Build Coastguard Worker  return exit_code
263*6777b538SAndroid Build Coastguard Worker
264*6777b538SAndroid Build Coastguard Worker
265*6777b538SAndroid Build Coastguard Workerdef run_command_output_to_handle(argv, file_handle, env=None, cwd=None):
266*6777b538SAndroid Build Coastguard Worker  """Run command and stream its stdout/stderr both to |file_handle|.
267*6777b538SAndroid Build Coastguard Worker
268*6777b538SAndroid Build Coastguard Worker  Also forward_signals to obey
269*6777b538SAndroid Build Coastguard Worker  https://chromium.googlesource.com/infra/luci/luci-py/+/main/appengine/swarming/doc/Bot.md#graceful-termination_aka-the-sigterm-and-sigkill-dance
270*6777b538SAndroid Build Coastguard Worker
271*6777b538SAndroid Build Coastguard Worker  Returns:
272*6777b538SAndroid Build Coastguard Worker    integer returncode of the subprocess.
273*6777b538SAndroid Build Coastguard Worker  """
274*6777b538SAndroid Build Coastguard Worker  print('Running %r in %r (env: %r)' % (argv, cwd, env))
275*6777b538SAndroid Build Coastguard Worker  process = _popen(
276*6777b538SAndroid Build Coastguard Worker      argv, env=env, cwd=cwd, stderr=file_handle, stdout=file_handle)
277*6777b538SAndroid Build Coastguard Worker  forward_signals([process])
278*6777b538SAndroid Build Coastguard Worker  exit_code = wait_with_signals(process)
279*6777b538SAndroid Build Coastguard Worker  print('Command returned exit code %d' % exit_code)
280*6777b538SAndroid Build Coastguard Worker  return exit_code
281*6777b538SAndroid Build Coastguard Worker
282*6777b538SAndroid Build Coastguard Worker
283*6777b538SAndroid Build Coastguard Workerdef wait_with_signals(process):
284*6777b538SAndroid Build Coastguard Worker  """A version of process.wait() that works cross-platform.
285*6777b538SAndroid Build Coastguard Worker
286*6777b538SAndroid Build Coastguard Worker  This version properly surfaces the SIGBREAK signal.
287*6777b538SAndroid Build Coastguard Worker
288*6777b538SAndroid Build Coastguard Worker  From reading the subprocess.py source code, it seems we need to explicitly
289*6777b538SAndroid Build Coastguard Worker  call time.sleep(). The reason is that subprocess.Popen.wait() on Windows
290*6777b538SAndroid Build Coastguard Worker  directly calls WaitForSingleObject(), but only time.sleep() properly surface
291*6777b538SAndroid Build Coastguard Worker  the SIGBREAK signal.
292*6777b538SAndroid Build Coastguard Worker
293*6777b538SAndroid Build Coastguard Worker  Refs:
294*6777b538SAndroid Build Coastguard Worker  https://github.com/python/cpython/blob/v2.7.15/Lib/subprocess.py#L692
295*6777b538SAndroid Build Coastguard Worker  https://github.com/python/cpython/blob/v2.7.15/Modules/timemodule.c#L1084
296*6777b538SAndroid Build Coastguard Worker
297*6777b538SAndroid Build Coastguard Worker  Returns:
298*6777b538SAndroid Build Coastguard Worker    returncode of the process.
299*6777b538SAndroid Build Coastguard Worker  """
300*6777b538SAndroid Build Coastguard Worker  while process.poll() is None:
301*6777b538SAndroid Build Coastguard Worker    time.sleep(0.1)
302*6777b538SAndroid Build Coastguard Worker  return process.returncode
303*6777b538SAndroid Build Coastguard Worker
304*6777b538SAndroid Build Coastguard Worker
305*6777b538SAndroid Build Coastguard Workerdef forward_signals(procs):
306*6777b538SAndroid Build Coastguard Worker  """Forwards unix's SIGTERM or win's CTRL_BREAK_EVENT to the given processes.
307*6777b538SAndroid Build Coastguard Worker
308*6777b538SAndroid Build Coastguard Worker  This plays nicely with swarming's timeout handling. See also
309*6777b538SAndroid Build Coastguard Worker  https://chromium.googlesource.com/infra/luci/luci-py/+/main/appengine/swarming/doc/Bot.md#graceful-termination_aka-the-sigterm-and-sigkill-dance
310*6777b538SAndroid Build Coastguard Worker
311*6777b538SAndroid Build Coastguard Worker  Args:
312*6777b538SAndroid Build Coastguard Worker      procs: A list of subprocess.Popen objects representing child processes.
313*6777b538SAndroid Build Coastguard Worker  """
314*6777b538SAndroid Build Coastguard Worker  assert all(isinstance(p, subprocess.Popen) for p in procs)
315*6777b538SAndroid Build Coastguard Worker  def _sig_handler(sig, _):
316*6777b538SAndroid Build Coastguard Worker    for p in procs:
317*6777b538SAndroid Build Coastguard Worker      if p.poll() is not None:
318*6777b538SAndroid Build Coastguard Worker        continue
319*6777b538SAndroid Build Coastguard Worker      # SIGBREAK is defined only for win32.
320*6777b538SAndroid Build Coastguard Worker      # pylint: disable=no-member
321*6777b538SAndroid Build Coastguard Worker      if sys.platform == 'win32' and sig == signal.SIGBREAK:
322*6777b538SAndroid Build Coastguard Worker        p.send_signal(signal.CTRL_BREAK_EVENT)
323*6777b538SAndroid Build Coastguard Worker      else:
324*6777b538SAndroid Build Coastguard Worker        print("Forwarding signal(%d) to process %d" % (sig, p.pid))
325*6777b538SAndroid Build Coastguard Worker        p.send_signal(sig)
326*6777b538SAndroid Build Coastguard Worker      # pylint: enable=no-member
327*6777b538SAndroid Build Coastguard Worker  if sys.platform == 'win32':
328*6777b538SAndroid Build Coastguard Worker    signal.signal(signal.SIGBREAK, _sig_handler) # pylint: disable=no-member
329*6777b538SAndroid Build Coastguard Worker  else:
330*6777b538SAndroid Build Coastguard Worker    signal.signal(signal.SIGTERM, _sig_handler)
331*6777b538SAndroid Build Coastguard Worker    signal.signal(signal.SIGINT, _sig_handler)
332*6777b538SAndroid Build Coastguard Worker
333*6777b538SAndroid Build Coastguard Worker
334*6777b538SAndroid Build Coastguard Workerdef run_executable(cmd, env, stdoutfile=None, cwd=None):
335*6777b538SAndroid Build Coastguard Worker  """Runs an executable with:
336*6777b538SAndroid Build Coastguard Worker    - CHROME_HEADLESS set to indicate that the test is running on a
337*6777b538SAndroid Build Coastguard Worker      bot and shouldn't do anything interactive like show modal dialogs.
338*6777b538SAndroid Build Coastguard Worker    - environment variable CR_SOURCE_ROOT set to the root directory.
339*6777b538SAndroid Build Coastguard Worker    - environment variable LANGUAGE to en_US.UTF-8.
340*6777b538SAndroid Build Coastguard Worker    - environment variable CHROME_DEVEL_SANDBOX set
341*6777b538SAndroid Build Coastguard Worker    - Reuses sys.executable automatically.
342*6777b538SAndroid Build Coastguard Worker  """
343*6777b538SAndroid Build Coastguard Worker  extra_env = {
344*6777b538SAndroid Build Coastguard Worker      # Set to indicate that the executable is running non-interactively on
345*6777b538SAndroid Build Coastguard Worker      # a bot.
346*6777b538SAndroid Build Coastguard Worker      'CHROME_HEADLESS': '1',
347*6777b538SAndroid Build Coastguard Worker
348*6777b538SAndroid Build Coastguard Worker       # Many tests assume a English interface...
349*6777b538SAndroid Build Coastguard Worker      'LANG': 'en_US.UTF-8',
350*6777b538SAndroid Build Coastguard Worker  }
351*6777b538SAndroid Build Coastguard Worker
352*6777b538SAndroid Build Coastguard Worker  # Used by base/base_paths_linux.cc as an override. Just make sure the default
353*6777b538SAndroid Build Coastguard Worker  # logic is used.
354*6777b538SAndroid Build Coastguard Worker  env.pop('CR_SOURCE_ROOT', None)
355*6777b538SAndroid Build Coastguard Worker
356*6777b538SAndroid Build Coastguard Worker  # Copy logic from  tools/build/scripts/slave/runtest.py.
357*6777b538SAndroid Build Coastguard Worker  asan = '--asan=1' in cmd
358*6777b538SAndroid Build Coastguard Worker  lsan = '--lsan=1' in cmd
359*6777b538SAndroid Build Coastguard Worker  msan = '--msan=1' in cmd
360*6777b538SAndroid Build Coastguard Worker  tsan = '--tsan=1' in cmd
361*6777b538SAndroid Build Coastguard Worker  cfi_diag = '--cfi-diag=1' in cmd
362*6777b538SAndroid Build Coastguard Worker  # Treat sanitizer warnings as test case failures.
363*6777b538SAndroid Build Coastguard Worker  use_sanitizer_warnings_script = '--fail-san=1' in cmd
364*6777b538SAndroid Build Coastguard Worker  if stdoutfile or sys.platform in ['win32', 'cygwin']:
365*6777b538SAndroid Build Coastguard Worker    # Symbolization works in-process on Windows even when sandboxed.
366*6777b538SAndroid Build Coastguard Worker    use_symbolization_script = False
367*6777b538SAndroid Build Coastguard Worker  else:
368*6777b538SAndroid Build Coastguard Worker    # If any sanitizer is enabled, we print unsymbolized stack trace
369*6777b538SAndroid Build Coastguard Worker    # that is required to run through symbolization script.
370*6777b538SAndroid Build Coastguard Worker    use_symbolization_script = (asan or msan or cfi_diag or lsan or tsan)
371*6777b538SAndroid Build Coastguard Worker
372*6777b538SAndroid Build Coastguard Worker  if asan or lsan or msan or tsan or cfi_diag:
373*6777b538SAndroid Build Coastguard Worker    extra_env.update(get_sanitizer_env(asan, lsan, msan, tsan, cfi_diag))
374*6777b538SAndroid Build Coastguard Worker
375*6777b538SAndroid Build Coastguard Worker  if lsan or tsan:
376*6777b538SAndroid Build Coastguard Worker    # LSan and TSan are not sandbox-friendly.
377*6777b538SAndroid Build Coastguard Worker    cmd.append('--no-sandbox')
378*6777b538SAndroid Build Coastguard Worker
379*6777b538SAndroid Build Coastguard Worker  # Enable clang code coverage continuous mode.
380*6777b538SAndroid Build Coastguard Worker  if '--coverage-continuous-mode=1' in cmd:
381*6777b538SAndroid Build Coastguard Worker    extra_env.update(get_coverage_continuous_mode_env(env))
382*6777b538SAndroid Build Coastguard Worker
383*6777b538SAndroid Build Coastguard Worker  # pylint: disable=import-outside-toplevel
384*6777b538SAndroid Build Coastguard Worker  if '--skip-set-lpac-acls=1' not in cmd and sys.platform == 'win32':
385*6777b538SAndroid Build Coastguard Worker    sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
386*6777b538SAndroid Build Coastguard Worker        'scripts'))
387*6777b538SAndroid Build Coastguard Worker    from scripts import common
388*6777b538SAndroid Build Coastguard Worker    common.set_lpac_acls(ROOT_DIR, is_test_script=True)
389*6777b538SAndroid Build Coastguard Worker  # pylint: enable=import-outside-toplevel
390*6777b538SAndroid Build Coastguard Worker
391*6777b538SAndroid Build Coastguard Worker  cmd = trim_cmd(cmd)
392*6777b538SAndroid Build Coastguard Worker
393*6777b538SAndroid Build Coastguard Worker  # Ensure paths are correctly separated on windows.
394*6777b538SAndroid Build Coastguard Worker  cmd[0] = cmd[0].replace('/', os.path.sep)
395*6777b538SAndroid Build Coastguard Worker  cmd = fix_python_path(cmd)
396*6777b538SAndroid Build Coastguard Worker
397*6777b538SAndroid Build Coastguard Worker  # We also want to print the GTEST env vars that were set by the caller,
398*6777b538SAndroid Build Coastguard Worker  # because you need them to reproduce the task properly.
399*6777b538SAndroid Build Coastguard Worker  env_to_print = extra_env.copy()
400*6777b538SAndroid Build Coastguard Worker  for env_var_name in ('GTEST_SHARD_INDEX', 'GTEST_TOTAL_SHARDS'):
401*6777b538SAndroid Build Coastguard Worker    if env_var_name in env:
402*6777b538SAndroid Build Coastguard Worker      env_to_print[env_var_name] = env[env_var_name]
403*6777b538SAndroid Build Coastguard Worker
404*6777b538SAndroid Build Coastguard Worker  print('Additional test environment:\n%s\n'
405*6777b538SAndroid Build Coastguard Worker        'Command: %s\n' % (
406*6777b538SAndroid Build Coastguard Worker        '\n'.join('    %s=%s' % (k, v)
407*6777b538SAndroid Build Coastguard Worker                  for k, v in sorted(env_to_print.items())),
408*6777b538SAndroid Build Coastguard Worker        ' '.join(cmd)))
409*6777b538SAndroid Build Coastguard Worker  sys.stdout.flush()
410*6777b538SAndroid Build Coastguard Worker  env.update(extra_env or {})
411*6777b538SAndroid Build Coastguard Worker  try:
412*6777b538SAndroid Build Coastguard Worker    if stdoutfile:
413*6777b538SAndroid Build Coastguard Worker      # Write to stdoutfile and poll to produce terminal output.
414*6777b538SAndroid Build Coastguard Worker      return run_command_with_output(cmd,
415*6777b538SAndroid Build Coastguard Worker                                     env=env,
416*6777b538SAndroid Build Coastguard Worker                                     stdoutfile=stdoutfile,
417*6777b538SAndroid Build Coastguard Worker                                     cwd=cwd)
418*6777b538SAndroid Build Coastguard Worker    if use_symbolization_script:
419*6777b538SAndroid Build Coastguard Worker      # See above comment regarding offline symbolization.
420*6777b538SAndroid Build Coastguard Worker      # Need to pipe to the symbolizer script.
421*6777b538SAndroid Build Coastguard Worker      p1 = _popen(cmd, env=env, stdout=subprocess.PIPE,
422*6777b538SAndroid Build Coastguard Worker                  cwd=cwd, stderr=sys.stdout)
423*6777b538SAndroid Build Coastguard Worker      p2 = _popen(
424*6777b538SAndroid Build Coastguard Worker          get_sanitizer_symbolize_command(executable_path=cmd[0]),
425*6777b538SAndroid Build Coastguard Worker          env=env, stdin=p1.stdout)
426*6777b538SAndroid Build Coastguard Worker      p1.stdout.close()  # Allow p1 to receive a SIGPIPE if p2 exits.
427*6777b538SAndroid Build Coastguard Worker      forward_signals([p1, p2])
428*6777b538SAndroid Build Coastguard Worker      wait_with_signals(p1)
429*6777b538SAndroid Build Coastguard Worker      wait_with_signals(p2)
430*6777b538SAndroid Build Coastguard Worker      # Also feed the out-of-band JSON output to the symbolizer script.
431*6777b538SAndroid Build Coastguard Worker      symbolize_snippets_in_json(cmd, env)
432*6777b538SAndroid Build Coastguard Worker      returncode = p1.returncode
433*6777b538SAndroid Build Coastguard Worker    else:
434*6777b538SAndroid Build Coastguard Worker      returncode = run_command(cmd, env=env, cwd=cwd, log=False)
435*6777b538SAndroid Build Coastguard Worker    # Check if we should post-process sanitizer warnings.
436*6777b538SAndroid Build Coastguard Worker    if use_sanitizer_warnings_script:
437*6777b538SAndroid Build Coastguard Worker      escalate_returncode = escalate_sanitizer_warnings_in_json(cmd, env)
438*6777b538SAndroid Build Coastguard Worker      if not returncode and escalate_returncode:
439*6777b538SAndroid Build Coastguard Worker        print('Tests with sanitizer warnings led to task failure.')
440*6777b538SAndroid Build Coastguard Worker        returncode = escalate_returncode
441*6777b538SAndroid Build Coastguard Worker    return returncode
442*6777b538SAndroid Build Coastguard Worker  except OSError:
443*6777b538SAndroid Build Coastguard Worker    print('Failed to start %s' % cmd, file=sys.stderr)
444*6777b538SAndroid Build Coastguard Worker    raise
445*6777b538SAndroid Build Coastguard Worker
446*6777b538SAndroid Build Coastguard Worker
447*6777b538SAndroid Build Coastguard Workerdef _popen(*args, **kwargs):
448*6777b538SAndroid Build Coastguard Worker  assert 'creationflags' not in kwargs
449*6777b538SAndroid Build Coastguard Worker  if sys.platform == 'win32':
450*6777b538SAndroid Build Coastguard Worker    # Necessary for signal handling. See crbug.com/733612#c6.
451*6777b538SAndroid Build Coastguard Worker    kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP
452*6777b538SAndroid Build Coastguard Worker  return subprocess.Popen(*args, **kwargs)
453*6777b538SAndroid Build Coastguard Worker
454*6777b538SAndroid Build Coastguard Worker
455*6777b538SAndroid Build Coastguard Workerdef main():
456*6777b538SAndroid Build Coastguard Worker  return run_executable(sys.argv[1:], os.environ.copy())
457*6777b538SAndroid Build Coastguard Worker
458*6777b538SAndroid Build Coastguard Worker
459*6777b538SAndroid Build Coastguard Workerif __name__ == '__main__':
460*6777b538SAndroid Build Coastguard Worker  sys.exit(main())
461