Upload upstream chromium 76.0.3809.146
[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/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
38   The return value is passed to a collect-style map() and consists of:
39   - The index of the job;
40   - The json file created by triggering and used to collect results;
41   - The command line arguments object.
42   """
43   index, args, isolated_hash = args
44   json_file = os.path.join(args.results, '%d.json' % index)
45   trigger_args = [
46       'tools/swarming_client/swarming.py', 'trigger',
47       '-S', 'https://chromium-swarm.appspot.com',
48       '-I', 'https://isolateserver.appspot.com',
49       '-d', 'pool', args.pool,
50       '-s', isolated_hash,
51       '--dump-json', json_file,
52   ]
53   if args.target_os == 'fuchsia':
54     trigger_args += [
55       '-d', 'os', 'Linux',
56       '-d', 'kvm', '1',
57       '-d', 'gpu', 'none',
58       '-d', 'cpu', args.arch,
59     ]
60   elif args.target_os == 'win':
61     trigger_args += [ '-d', 'os', 'Windows' ]
62   elif args.target_os == 'android':
63     # The canonical version numbers are stored in the infra repository here:
64     # build/scripts/slave/recipe_modules/swarming/api.py
65     cpython_version = 'version:2.7.14.chromium14'
66     vpython_version = 'git_revision:96f81e737868d43124b4661cf1c325296ca04944'
67     cpython_pkg = (
68         '.swarming_module:infra/python/cpython/${platform}:' +
69         cpython_version)
70     vpython_native_pkg = (
71         '.swarming_module:infra/tools/luci/vpython-native/${platform}:' +
72         vpython_version)
73     vpython_pkg = (
74         '.swarming_module:infra/tools/luci/vpython/${platform}:' +
75         vpython_version)
76     trigger_args += [
77         '-d', 'os', 'Android',
78         '-d', 'device_os', args.device_os,
79         '--cipd-package', cpython_pkg,
80         '--cipd-package', vpython_native_pkg,
81         '--cipd-package', vpython_pkg,
82         '--env-prefix', 'PATH', '.swarming_module',
83         '--env-prefix', 'PATH', '.swarming_module/bin',
84         '--env-prefix', 'VPYTHON_VIRTUALENV_ROOT',
85         '.swarming_module_cache/vpython',
86     ]
87   trigger_args += [
88       '--',
89       '--test-launcher-summary-output=${ISOLATED_OUTDIR}/output.json',
90       '--system-log-file=${ISOLATED_OUTDIR}/system_log']
91   if args.gtest_filter:
92     trigger_args.append('--gtest_filter=' + args.gtest_filter)
93   elif args.target_os == 'fuchsia':
94     filter_file = \
95         'testing/buildbot/filters/fuchsia.' + args.test_name + '.filter'
96     if os.path.isfile(filter_file):
97       trigger_args.append('--test-launcher-filter-file=../../' + filter_file)
98   with open(os.devnull, 'w') as nul:
99     subprocess.check_call(trigger_args, stdout=nul)
100   return (index, json_file, args)
101
102
103 def _Collect(spawn_result):
104   index, json_file, args = spawn_result
105   p = subprocess.Popen([
106     'tools/swarming_client/swarming.py', 'collect',
107     '-S', 'https://chromium-swarm.appspot.com',
108     '--json', json_file,
109     '--task-output-stdout=console'],
110     stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
111   stdout = p.communicate()[0]
112   if p.returncode != 0 and len(stdout) < 2**10 and 'Internal error!' in stdout:
113     exit_code = INTERNAL_ERROR_EXIT_CODE
114     file_suffix = '.INTERNAL_ERROR'
115   else:
116     exit_code = p.returncode
117     file_suffix = '' if exit_code == 0 else '.FAILED'
118   filename = '%d%s.stdout.txt' % (index, file_suffix)
119   with open(os.path.join(args.results, filename), 'w') as f:
120     f.write(stdout)
121   return exit_code
122
123
124 def main():
125   parser = argparse.ArgumentParser()
126   parser.add_argument('-C', '--out-dir', default='out/fuch',
127                       help='Build directory.')
128   parser.add_argument('--target-os', default='detect', help='gn target_os')
129   parser.add_argument('--test-name', '-t', required=True,
130                       help='Name of test to run.')
131   parser.add_argument('--arch', '-a', default='detect',
132                       help='CPU architecture of the test binary.')
133   parser.add_argument('--copies', '-n', type=int, default=1,
134                       help='Number of copies to spawn.')
135   parser.add_argument('--device-os', default='M',
136                       help='Run tests on the given version of Android.')
137   parser.add_argument('--pool', default='Chrome',
138                       help='Use the given swarming pool.')
139   parser.add_argument('--results', '-r', default='results',
140                       help='Directory in which to store results.')
141   parser.add_argument('--gtest_filter',
142                       help='Use the given gtest_filter, rather than the '
143                            'default filter file, if any.')
144
145   args = parser.parse_args()
146
147   if args.target_os == 'detect':
148     with open(os.path.join(args.out_dir, 'args.gn')) as f:
149       gn_args = {}
150       for l in f:
151         l = l.split('#')[0].strip()
152         if not l: continue
153         k, v = map(str.strip, l.split('=', 1))
154         gn_args[k] = v
155     if 'target_os' in gn_args:
156       args.target_os = gn_args['target_os'].strip('"')
157     else:
158       args.target_os = { 'darwin': 'mac', 'linux2': 'linux', 'win32': 'win' }[
159                            sys.platform]
160
161   # Determine the CPU architecture of the test binary, if not specified.
162   if args.arch == 'detect' and args.target_os == 'fuchsia':
163     executable_info = subprocess.check_output(
164         ['file', os.path.join(args.out_dir, args.test_name)])
165     if 'ARM aarch64' in executable_info:
166       args.arch = 'arm64',
167     else:
168       args.arch = 'x86-64'
169
170   subprocess.check_call(
171       ['tools/mb/mb.py', 'isolate', '//' + args.out_dir, args.test_name])
172
173   print 'If you get authentication errors, follow:'
174   print '  https://www.chromium.org/developers/testing/isolated-testing/for-swes#TOC-Login-on-the-services'
175
176   print 'Uploading to isolate server, this can take a while...'
177   archive_output = subprocess.check_output(
178       ['tools/swarming_client/isolate.py', 'archive',
179        '-I', 'https://isolateserver.appspot.com',
180        '-i', os.path.join(args.out_dir, args.test_name + '.isolate'),
181        '-s', os.path.join(args.out_dir, args.test_name + '.isolated')])
182   isolated_hash = archive_output.split()[0]
183
184   if os.path.isdir(args.results):
185     shutil.rmtree(args.results)
186   os.makedirs(args.results)
187
188   try:
189     print 'Triggering %d tasks...' % args.copies
190     pool = multiprocessing.Pool()
191     spawn_args = map(lambda i: (i, args, isolated_hash), range(args.copies))
192     spawn_results = pool.imap_unordered(_Spawn, spawn_args)
193
194     exit_codes = []
195     collect_results = pool.imap_unordered(_Collect, spawn_results)
196     for result in collect_results:
197       exit_codes.append(result)
198       successes = sum(1 for x in exit_codes if x == 0)
199       errors = sum(1 for x in exit_codes if x == INTERNAL_ERROR_EXIT_CODE)
200       failures = len(exit_codes) - successes - errors
201       clear_to_eol = '\033[K'
202       print('\r[%d/%d] collected: '
203             '%d successes, %d failures, %d bot errors...%s' % (len(exit_codes),
204                 args.copies, successes, failures, errors, clear_to_eol)),
205       sys.stdout.flush()
206
207     print
208     print 'Results logs collected into', os.path.abspath(args.results) + '.'
209   finally:
210     pool.close()
211     pool.join()
212   return 0
213
214
215 if __name__ == '__main__':
216   sys.exit(main())