Upload upstream chromium 67.0.3396
[platform/framework/web/chromium-efl.git] / tools / run-swarmed.py
1 #!/usr/bin/env python
2
3 # Copyright 2017 The Chromium Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file.
6
7 """Runs a Fuchsia gtest-based test on Swarming, optionally many times,
8 collecting the output of the runs into a directory. Useful for flake checking,
9 and faster than using trybots by avoiding repeated bot_update, compile, archive,
10 etc. and allowing greater parallelism.
11
12 To use, run in a new shell (it blocks until all Swarming jobs complete):
13
14   tools/fuchsia/run-swarmed.py -t content_unittests --out-dir=out/fuch
15
16 The logs of the runs will be stored in results/ (or specify a results directory
17 with --results=some_dir). You can then do something like `grep -L SUCCESS
18 results/*` to find the tests that failed or otherwise process the log files.
19 """
20
21 import argparse
22 import multiprocessing
23 import os
24 import shutil
25 import subprocess
26 import sys
27
28
29 INTERNAL_ERROR_EXIT_CODE = -1000
30
31
32 def _Spawn(args):
33   """Triggers a swarming job. The arguments passed are:
34   - The index of the job;
35   - The command line arguments object;
36   - The hash of the isolate job used to trigger;
37   - Value of --gtest_filter arg, or empty if none.
38
39   The return value is passed to a collect-style map() and consists of:
40   - The index of the job;
41   - The json file created by triggering and used to collect results;
42   - The command line arguments object.
43   """
44   index, args, isolated_hash, gtest_filter = args
45   json_file = os.path.join(args.results, '%d.json' % index)
46   trigger_args = [
47       'tools/swarming_client/swarming.py', 'trigger',
48       '-S', 'https://chromium-swarm.appspot.com',
49       '-I', 'https://isolateserver.appspot.com',
50       '-d', 'pool', 'Chrome',
51       '-s', isolated_hash,
52       '--dump-json', json_file,
53   ]
54   if args.target_os == 'fuchsia':
55     trigger_args += [
56       '-d', 'os', 'Linux',
57       '-d', 'kvm', '1',
58       '-d', 'gpu', 'none',
59       '-d', 'cpu', args.arch,
60     ]
61   elif args.target_os == 'win':
62     trigger_args += [ '-d', 'os', 'Windows' ]
63   trigger_args += [
64       '--',
65       '--test-launcher-summary-output=${ISOLATED_OUTDIR}/output.json']
66   if gtest_filter:
67     trigger_args.append('--gtest_filter=' + gtest_filter)
68   elif args.target_os == 'fuchsia':
69     filter_file = \
70         'testing/buildbot/filters/fuchsia.' + args.test_name + '.filter'
71     if os.path.isfile(filter_file):
72       trigger_args.append('--test-launcher-filter-file=../../' + filter_file)
73   with open(os.devnull, 'w') as nul:
74     subprocess.check_call(trigger_args, stdout=nul)
75   return (index, json_file, args)
76
77
78 def _Collect(spawn_result):
79   index, json_file, args = spawn_result
80   p = subprocess.Popen([
81     'tools/swarming_client/swarming.py', 'collect',
82     '-S', 'https://chromium-swarm.appspot.com',
83     '--json', json_file,
84     '--task-output-stdout=console'],
85     stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
86   stdout = p.communicate()[0]
87   if p.returncode != 0 and len(stdout) < 2**10 and 'Internal error!' in stdout:
88     exit_code = INTERNAL_ERROR_EXIT_CODE
89     file_suffix = '.INTERNAL_ERROR'
90   else:
91     exit_code = p.returncode
92     file_suffix = '' if exit_code == 0 else '.FAILED'
93   filename = '%d%s.stdout.txt' % (index, file_suffix)
94   with open(os.path.join(args.results, filename), 'w') as f:
95     f.write(stdout)
96   return exit_code
97
98
99 def main():
100   parser = argparse.ArgumentParser()
101   parser.add_argument('-C', '--out-dir', default='out/fuch',
102                       help='Build directory.')
103   parser.add_argument('--target-os', default='detect', help='gn target_os')
104   parser.add_argument('--test-name', '-t', required=True,
105                       help='Name of test to run.')
106   parser.add_argument('--arch', '-a', default='detect',
107                       help='CPU architecture of the test binary.')
108   parser.add_argument('--copies', '-n', type=int, default=1,
109                       help='Number of copies to spawn.')
110   parser.add_argument('--results', '-r', default='results',
111                       help='Directory in which to store results.')
112   parser.add_argument('--gtest_filter',
113                       help='Use the given gtest_filter, rather than the '
114                            'default filter file, if any.')
115
116   args = parser.parse_args()
117
118   if args.target_os == 'detect':
119     with open(os.path.join(args.out_dir, 'args.gn')) as f:
120       gn_args = {}
121       for l in f:
122         l = l.split('#')[0].strip()
123         if not l: continue
124         k, v = map(str.strip, l.split('=', 1))
125         gn_args[k] = v
126     if 'target_os' in gn_args:
127       args.target_os = gn_args['target_os'].strip('"')
128     else:
129       args.target_os = { 'darwin': 'mac', 'linux2': 'linux', 'win32': 'win' }[
130                            sys.platform]
131
132   # Determine the CPU architecture of the test binary, if not specified.
133   if args.arch == 'detect' and args.target_os == 'fuchsia':
134     executable_info = subprocess.check_output(
135         ['file', os.path.join(args.out_dir, args.test_name)])
136     if 'ARM aarch64' in executable_info:
137       args.arch = 'arm64',
138     else:
139       args.arch = 'x86-64'
140
141   subprocess.check_call(
142       ['tools/mb/mb.py', 'isolate', '//' + args.out_dir, args.test_name])
143
144   print 'If you get authentication errors, follow:'
145   print '  https://www.chromium.org/developers/testing/isolated-testing/for-swes#TOC-Login-on-the-services'
146
147   print 'Uploading to isolate server, this can take a while...'
148   archive_output = subprocess.check_output(
149       ['tools/swarming_client/isolate.py', 'archive',
150        '-I', 'https://isolateserver.appspot.com',
151        '-i', os.path.join(args.out_dir, args.test_name + '.isolate'),
152        '-s', os.path.join(args.out_dir, args.test_name + '.isolated')])
153   isolated_hash = archive_output.split()[0]
154
155   if os.path.isdir(args.results):
156     shutil.rmtree(args.results)
157   os.makedirs(args.results)
158
159   try:
160     print 'Triggering %d tasks...' % args.copies
161     pool = multiprocessing.Pool()
162     spawn_args = map(lambda i: (i, args, isolated_hash, args.gtest_filter),
163                      range(args.copies))
164     spawn_results = pool.imap_unordered(_Spawn, spawn_args)
165
166     exit_codes = []
167     collect_results = pool.imap_unordered(_Collect, spawn_results)
168     for result in collect_results:
169       exit_codes.append(result)
170       successes = sum(1 for x in exit_codes if x == 0)
171       errors = sum(1 for x in exit_codes if x == INTERNAL_ERROR_EXIT_CODE)
172       failures = len(exit_codes) - successes - errors
173       clear_to_eol = '\033[K'
174       print('\r[%d/%d] collected: '
175             '%d successes, %d failures, %d bot errors...%s' % (len(exit_codes),
176                 args.copies, successes, failures, errors, clear_to_eol)),
177       sys.stdout.flush()
178
179     print
180     print 'Results logs collected into', os.path.abspath(args.results) + '.'
181   finally:
182     pool.close()
183     pool.join()
184   return 0
185
186
187 if __name__ == '__main__':
188   sys.exit(main())