xref: /aosp_15_r20/external/webrtc/tools_webrtc/gtest-parallel-wrapper.py (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1*d9f75844SAndroid Build Coastguard Worker#!/usr/bin/env vpython3
2*d9f75844SAndroid Build Coastguard Worker
3*d9f75844SAndroid Build Coastguard Worker# Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
4*d9f75844SAndroid Build Coastguard Worker#
5*d9f75844SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license
6*d9f75844SAndroid Build Coastguard Worker# that can be found in the LICENSE file in the root of the source
7*d9f75844SAndroid Build Coastguard Worker# tree. An additional intellectual property rights grant can be found
8*d9f75844SAndroid Build Coastguard Worker# in the file PATENTS.  All contributing project authors may
9*d9f75844SAndroid Build Coastguard Worker# be found in the AUTHORS file in the root of the source tree.
10*d9f75844SAndroid Build Coastguard Worker
11*d9f75844SAndroid Build Coastguard Worker# pylint: disable=invalid-name
12*d9f75844SAndroid Build Coastguard Worker"""
13*d9f75844SAndroid Build Coastguard WorkerThis script acts as an interface between the Chromium infrastructure and
14*d9f75844SAndroid Build Coastguard Workergtest-parallel, renaming options and translating environment variables into
15*d9f75844SAndroid Build Coastguard Workerflags. Developers should execute gtest-parallel directly.
16*d9f75844SAndroid Build Coastguard Worker
17*d9f75844SAndroid Build Coastguard WorkerIn particular, this translates the GTEST_SHARD_INDEX and GTEST_TOTAL_SHARDS
18*d9f75844SAndroid Build Coastguard Workerenvironment variables to the --shard_index and --shard_count flags, renames
19*d9f75844SAndroid Build Coastguard Workerthe --isolated-script-test-output flag to --dump_json_test_results,
20*d9f75844SAndroid Build Coastguard Workerand interprets e.g. --workers=2x as 2 workers per core.
21*d9f75844SAndroid Build Coastguard Worker
22*d9f75844SAndroid Build Coastguard WorkerFlags before '--' will be attempted to be understood as arguments to
23*d9f75844SAndroid Build Coastguard Workergtest-parallel. If gtest-parallel doesn't recognize the flag or the flag is
24*d9f75844SAndroid Build Coastguard Workerafter '--', the flag will be passed on to the test executable.
25*d9f75844SAndroid Build Coastguard Worker
26*d9f75844SAndroid Build Coastguard Worker--isolated-script-test-perf-output is renamed to
27*d9f75844SAndroid Build Coastguard Worker--isolated_script_test_perf_output. The Android test runner needs the flag to
28*d9f75844SAndroid Build Coastguard Workerbe in the former form, but our tests require the latter, so this is the only
29*d9f75844SAndroid Build Coastguard Workerplace we can do it.
30*d9f75844SAndroid Build Coastguard Worker
31*d9f75844SAndroid Build Coastguard WorkerIf the --store-test-artifacts flag is set, an --output_dir must be also
32*d9f75844SAndroid Build Coastguard Workerspecified.
33*d9f75844SAndroid Build Coastguard Worker
34*d9f75844SAndroid Build Coastguard WorkerThe test artifacts will then be stored in a 'test_artifacts' subdirectory of the
35*d9f75844SAndroid Build Coastguard Workeroutput dir, and will be compressed into a zip file once the test finishes
36*d9f75844SAndroid Build Coastguard Workerexecuting.
37*d9f75844SAndroid Build Coastguard Worker
38*d9f75844SAndroid Build Coastguard WorkerThis is useful when running the tests in swarming, since the output directory
39*d9f75844SAndroid Build Coastguard Workeris not known beforehand.
40*d9f75844SAndroid Build Coastguard Worker
41*d9f75844SAndroid Build Coastguard WorkerFor example:
42*d9f75844SAndroid Build Coastguard Worker
43*d9f75844SAndroid Build Coastguard Worker  gtest-parallel-wrapper.py some_test \
44*d9f75844SAndroid Build Coastguard Worker      --some_flag=some_value \
45*d9f75844SAndroid Build Coastguard Worker      --another_flag \
46*d9f75844SAndroid Build Coastguard Worker      --output_dir=SOME_OUTPUT_DIR \
47*d9f75844SAndroid Build Coastguard Worker      --store-test-artifacts
48*d9f75844SAndroid Build Coastguard Worker      --isolated-script-test-output=SOME_DIR \
49*d9f75844SAndroid Build Coastguard Worker      --isolated-script-test-perf-output=SOME_OTHER_DIR \
50*d9f75844SAndroid Build Coastguard Worker      -- \
51*d9f75844SAndroid Build Coastguard Worker      --foo=bar \
52*d9f75844SAndroid Build Coastguard Worker      --baz
53*d9f75844SAndroid Build Coastguard Worker
54*d9f75844SAndroid Build Coastguard WorkerWill be converted into:
55*d9f75844SAndroid Build Coastguard Worker
56*d9f75844SAndroid Build Coastguard Worker  vpython3 gtest-parallel \
57*d9f75844SAndroid Build Coastguard Worker      --shard_index 0 \
58*d9f75844SAndroid Build Coastguard Worker      --shard_count 1 \
59*d9f75844SAndroid Build Coastguard Worker      --output_dir=SOME_OUTPUT_DIR \
60*d9f75844SAndroid Build Coastguard Worker      --dump_json_test_results=SOME_DIR \
61*d9f75844SAndroid Build Coastguard Worker      some_test \
62*d9f75844SAndroid Build Coastguard Worker      -- \
63*d9f75844SAndroid Build Coastguard Worker      --test_artifacts_dir=SOME_OUTPUT_DIR/test_artifacts \
64*d9f75844SAndroid Build Coastguard Worker      --some_flag=some_value \
65*d9f75844SAndroid Build Coastguard Worker      --another_flag \
66*d9f75844SAndroid Build Coastguard Worker      --isolated-script-test-perf-output=SOME_OTHER_DIR \
67*d9f75844SAndroid Build Coastguard Worker      --foo=bar \
68*d9f75844SAndroid Build Coastguard Worker      --baz
69*d9f75844SAndroid Build Coastguard Worker
70*d9f75844SAndroid Build Coastguard Worker"""
71*d9f75844SAndroid Build Coastguard Worker
72*d9f75844SAndroid Build Coastguard Workerimport argparse
73*d9f75844SAndroid Build Coastguard Workerimport collections
74*d9f75844SAndroid Build Coastguard Workerimport multiprocessing
75*d9f75844SAndroid Build Coastguard Workerimport os
76*d9f75844SAndroid Build Coastguard Workerimport shutil
77*d9f75844SAndroid Build Coastguard Workerimport subprocess
78*d9f75844SAndroid Build Coastguard Workerimport sys
79*d9f75844SAndroid Build Coastguard Worker
80*d9f75844SAndroid Build Coastguard WorkerArgs = collections.namedtuple(
81*d9f75844SAndroid Build Coastguard Worker    'Args',
82*d9f75844SAndroid Build Coastguard Worker    ['gtest_parallel_args', 'test_env', 'output_dir', 'test_artifacts_dir'])
83*d9f75844SAndroid Build Coastguard Worker
84*d9f75844SAndroid Build Coastguard Worker
85*d9f75844SAndroid Build Coastguard Workerdef _CatFiles(file_list, output_file_destination):
86*d9f75844SAndroid Build Coastguard Worker  with open(output_file_destination, 'w') as output_file:
87*d9f75844SAndroid Build Coastguard Worker    for filename in file_list:
88*d9f75844SAndroid Build Coastguard Worker      with open(filename) as input_file:
89*d9f75844SAndroid Build Coastguard Worker        output_file.write(input_file.read())
90*d9f75844SAndroid Build Coastguard Worker      os.remove(filename)
91*d9f75844SAndroid Build Coastguard Worker
92*d9f75844SAndroid Build Coastguard Worker
93*d9f75844SAndroid Build Coastguard Workerdef _ParseWorkersOption(workers):
94*d9f75844SAndroid Build Coastguard Worker  """Interpret Nx syntax as N * cpu_count. Int value is left as is."""
95*d9f75844SAndroid Build Coastguard Worker  base = float(workers.rstrip('x'))
96*d9f75844SAndroid Build Coastguard Worker  if workers.endswith('x'):
97*d9f75844SAndroid Build Coastguard Worker    result = int(base * multiprocessing.cpu_count())
98*d9f75844SAndroid Build Coastguard Worker  else:
99*d9f75844SAndroid Build Coastguard Worker    result = int(base)
100*d9f75844SAndroid Build Coastguard Worker  return max(result, 1)  # Sanitize when using e.g. '0.5x'.
101*d9f75844SAndroid Build Coastguard Worker
102*d9f75844SAndroid Build Coastguard Worker
103*d9f75844SAndroid Build Coastguard Workerclass ReconstructibleArgumentGroup:
104*d9f75844SAndroid Build Coastguard Worker  """An argument group that can be converted back into a command line.
105*d9f75844SAndroid Build Coastguard Worker
106*d9f75844SAndroid Build Coastguard Worker  This acts like ArgumentParser.add_argument_group, but names of arguments added
107*d9f75844SAndroid Build Coastguard Worker  to it are also kept in a list, so that parsed options from
108*d9f75844SAndroid Build Coastguard Worker  ArgumentParser.parse_args can be reconstructed back into a command line (list
109*d9f75844SAndroid Build Coastguard Worker  of args) based on the list of wanted keys."""
110*d9f75844SAndroid Build Coastguard Worker
111*d9f75844SAndroid Build Coastguard Worker  def __init__(self, parser, *args, **kwargs):
112*d9f75844SAndroid Build Coastguard Worker    self._group = parser.add_argument_group(*args, **kwargs)
113*d9f75844SAndroid Build Coastguard Worker    self._keys = []
114*d9f75844SAndroid Build Coastguard Worker
115*d9f75844SAndroid Build Coastguard Worker  def AddArgument(self, *args, **kwargs):
116*d9f75844SAndroid Build Coastguard Worker    arg = self._group.add_argument(*args, **kwargs)
117*d9f75844SAndroid Build Coastguard Worker    self._keys.append(arg.dest)
118*d9f75844SAndroid Build Coastguard Worker
119*d9f75844SAndroid Build Coastguard Worker  def RemakeCommandLine(self, options):
120*d9f75844SAndroid Build Coastguard Worker    result = []
121*d9f75844SAndroid Build Coastguard Worker    for key in self._keys:
122*d9f75844SAndroid Build Coastguard Worker      value = getattr(options, key)
123*d9f75844SAndroid Build Coastguard Worker      if value is True:
124*d9f75844SAndroid Build Coastguard Worker        result.append('--%s' % key)
125*d9f75844SAndroid Build Coastguard Worker      elif value is not None:
126*d9f75844SAndroid Build Coastguard Worker        result.append('--%s=%s' % (key, value))
127*d9f75844SAndroid Build Coastguard Worker    return result
128*d9f75844SAndroid Build Coastguard Worker
129*d9f75844SAndroid Build Coastguard Worker
130*d9f75844SAndroid Build Coastguard Workerdef ParseArgs(argv=None):
131*d9f75844SAndroid Build Coastguard Worker  parser = argparse.ArgumentParser(argv)
132*d9f75844SAndroid Build Coastguard Worker
133*d9f75844SAndroid Build Coastguard Worker  gtest_group = ReconstructibleArgumentGroup(parser,
134*d9f75844SAndroid Build Coastguard Worker                                             'Arguments to gtest-parallel')
135*d9f75844SAndroid Build Coastguard Worker  # These options will be passed unchanged to gtest-parallel.
136*d9f75844SAndroid Build Coastguard Worker  gtest_group.AddArgument('-d', '--output_dir')
137*d9f75844SAndroid Build Coastguard Worker  gtest_group.AddArgument('-r', '--repeat')
138*d9f75844SAndroid Build Coastguard Worker  # --isolated-script-test-output is used to upload results to the flakiness
139*d9f75844SAndroid Build Coastguard Worker  # dashboard. This translation is made because gtest-parallel expects the flag
140*d9f75844SAndroid Build Coastguard Worker  # to be called --dump_json_test_results instead.
141*d9f75844SAndroid Build Coastguard Worker  gtest_group.AddArgument('--isolated-script-test-output',
142*d9f75844SAndroid Build Coastguard Worker                          dest='dump_json_test_results')
143*d9f75844SAndroid Build Coastguard Worker  gtest_group.AddArgument('--retry_failed')
144*d9f75844SAndroid Build Coastguard Worker  gtest_group.AddArgument('--gtest_color')
145*d9f75844SAndroid Build Coastguard Worker  gtest_group.AddArgument('--gtest_filter')
146*d9f75844SAndroid Build Coastguard Worker  gtest_group.AddArgument('--gtest_also_run_disabled_tests',
147*d9f75844SAndroid Build Coastguard Worker                          action='store_true',
148*d9f75844SAndroid Build Coastguard Worker                          default=None)
149*d9f75844SAndroid Build Coastguard Worker  gtest_group.AddArgument('--timeout')
150*d9f75844SAndroid Build Coastguard Worker
151*d9f75844SAndroid Build Coastguard Worker  # Syntax 'Nx' will be interpreted as N * number of cpu cores.
152*d9f75844SAndroid Build Coastguard Worker  gtest_group.AddArgument('-w', '--workers', type=_ParseWorkersOption)
153*d9f75844SAndroid Build Coastguard Worker
154*d9f75844SAndroid Build Coastguard Worker  # Needed when the test wants to store test artifacts, because it doesn't
155*d9f75844SAndroid Build Coastguard Worker  # know what will be the swarming output dir.
156*d9f75844SAndroid Build Coastguard Worker  parser.add_argument('--store-test-artifacts', action='store_true')
157*d9f75844SAndroid Build Coastguard Worker
158*d9f75844SAndroid Build Coastguard Worker  parser.add_argument('executable')
159*d9f75844SAndroid Build Coastguard Worker  parser.add_argument('executable_args', nargs='*')
160*d9f75844SAndroid Build Coastguard Worker
161*d9f75844SAndroid Build Coastguard Worker  options, unrecognized_args = parser.parse_known_args(argv)
162*d9f75844SAndroid Build Coastguard Worker
163*d9f75844SAndroid Build Coastguard Worker  executable_args = options.executable_args + unrecognized_args
164*d9f75844SAndroid Build Coastguard Worker
165*d9f75844SAndroid Build Coastguard Worker  if options.store_test_artifacts:
166*d9f75844SAndroid Build Coastguard Worker    assert options.output_dir, (
167*d9f75844SAndroid Build Coastguard Worker        '--output_dir must be specified for storing test artifacts.')
168*d9f75844SAndroid Build Coastguard Worker    test_artifacts_dir = os.path.join(options.output_dir, 'test_artifacts')
169*d9f75844SAndroid Build Coastguard Worker
170*d9f75844SAndroid Build Coastguard Worker    executable_args.insert(0, '--test_artifacts_dir=%s' % test_artifacts_dir)
171*d9f75844SAndroid Build Coastguard Worker  else:
172*d9f75844SAndroid Build Coastguard Worker    test_artifacts_dir = None
173*d9f75844SAndroid Build Coastguard Worker
174*d9f75844SAndroid Build Coastguard Worker  gtest_parallel_args = gtest_group.RemakeCommandLine(options)
175*d9f75844SAndroid Build Coastguard Worker
176*d9f75844SAndroid Build Coastguard Worker  # GTEST_SHARD_INDEX and GTEST_TOTAL_SHARDS must be removed from the
177*d9f75844SAndroid Build Coastguard Worker  # environment. Otherwise it will be picked up by the binary, causing a bug
178*d9f75844SAndroid Build Coastguard Worker  # where only tests in the first shard are executed.
179*d9f75844SAndroid Build Coastguard Worker  test_env = os.environ.copy()
180*d9f75844SAndroid Build Coastguard Worker  gtest_shard_index = test_env.pop('GTEST_SHARD_INDEX', '0')
181*d9f75844SAndroid Build Coastguard Worker  gtest_total_shards = test_env.pop('GTEST_TOTAL_SHARDS', '1')
182*d9f75844SAndroid Build Coastguard Worker
183*d9f75844SAndroid Build Coastguard Worker  gtest_parallel_args.insert(0, '--shard_index=%s' % gtest_shard_index)
184*d9f75844SAndroid Build Coastguard Worker  gtest_parallel_args.insert(1, '--shard_count=%s' % gtest_total_shards)
185*d9f75844SAndroid Build Coastguard Worker
186*d9f75844SAndroid Build Coastguard Worker  gtest_parallel_args.append(options.executable)
187*d9f75844SAndroid Build Coastguard Worker  if executable_args:
188*d9f75844SAndroid Build Coastguard Worker    gtest_parallel_args += ['--'] + executable_args
189*d9f75844SAndroid Build Coastguard Worker
190*d9f75844SAndroid Build Coastguard Worker  return Args(gtest_parallel_args, test_env, options.output_dir,
191*d9f75844SAndroid Build Coastguard Worker              test_artifacts_dir)
192*d9f75844SAndroid Build Coastguard Worker
193*d9f75844SAndroid Build Coastguard Worker
194*d9f75844SAndroid Build Coastguard Workerdef main():
195*d9f75844SAndroid Build Coastguard Worker  webrtc_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
196*d9f75844SAndroid Build Coastguard Worker  gtest_parallel_path = os.path.join(webrtc_root, 'third_party',
197*d9f75844SAndroid Build Coastguard Worker                                     'gtest-parallel', 'gtest-parallel')
198*d9f75844SAndroid Build Coastguard Worker
199*d9f75844SAndroid Build Coastguard Worker  gtest_parallel_args, test_env, output_dir, test_artifacts_dir = ParseArgs()
200*d9f75844SAndroid Build Coastguard Worker
201*d9f75844SAndroid Build Coastguard Worker  command = [
202*d9f75844SAndroid Build Coastguard Worker      sys.executable,
203*d9f75844SAndroid Build Coastguard Worker      gtest_parallel_path,
204*d9f75844SAndroid Build Coastguard Worker  ] + gtest_parallel_args
205*d9f75844SAndroid Build Coastguard Worker
206*d9f75844SAndroid Build Coastguard Worker  if output_dir and not os.path.isdir(output_dir):
207*d9f75844SAndroid Build Coastguard Worker    os.makedirs(output_dir)
208*d9f75844SAndroid Build Coastguard Worker  if test_artifacts_dir and not os.path.isdir(test_artifacts_dir):
209*d9f75844SAndroid Build Coastguard Worker    os.makedirs(test_artifacts_dir)
210*d9f75844SAndroid Build Coastguard Worker
211*d9f75844SAndroid Build Coastguard Worker  print('gtest-parallel-wrapper: Executing command %s' % ' '.join(command))
212*d9f75844SAndroid Build Coastguard Worker  sys.stdout.flush()
213*d9f75844SAndroid Build Coastguard Worker
214*d9f75844SAndroid Build Coastguard Worker  exit_code = subprocess.call(command, env=test_env, cwd=os.getcwd())
215*d9f75844SAndroid Build Coastguard Worker
216*d9f75844SAndroid Build Coastguard Worker  if output_dir:
217*d9f75844SAndroid Build Coastguard Worker    for test_status in 'passed', 'failed', 'interrupted':
218*d9f75844SAndroid Build Coastguard Worker      logs_dir = os.path.join(output_dir, 'gtest-parallel-logs', test_status)
219*d9f75844SAndroid Build Coastguard Worker      if not os.path.isdir(logs_dir):
220*d9f75844SAndroid Build Coastguard Worker        continue
221*d9f75844SAndroid Build Coastguard Worker      logs = [os.path.join(logs_dir, log) for log in os.listdir(logs_dir)]
222*d9f75844SAndroid Build Coastguard Worker      log_file = os.path.join(output_dir, '%s-tests.log' % test_status)
223*d9f75844SAndroid Build Coastguard Worker      _CatFiles(logs, log_file)
224*d9f75844SAndroid Build Coastguard Worker      os.rmdir(logs_dir)
225*d9f75844SAndroid Build Coastguard Worker
226*d9f75844SAndroid Build Coastguard Worker  if test_artifacts_dir:
227*d9f75844SAndroid Build Coastguard Worker    shutil.make_archive(test_artifacts_dir, 'zip', test_artifacts_dir)
228*d9f75844SAndroid Build Coastguard Worker    shutil.rmtree(test_artifacts_dir)
229*d9f75844SAndroid Build Coastguard Worker
230*d9f75844SAndroid Build Coastguard Worker  return exit_code
231*d9f75844SAndroid Build Coastguard Worker
232*d9f75844SAndroid Build Coastguard Worker
233*d9f75844SAndroid Build Coastguard Workerif __name__ == '__main__':
234*d9f75844SAndroid Build Coastguard Worker  sys.exit(main())
235