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