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.
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.
12 To use, run in a new shell (it blocks until all Swarming jobs complete):
14 tools/run-swarmed.py -t content_unittests --out-dir=out/fuch
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.
22 import multiprocessing
29 INTERNAL_ERROR_EXIT_CODE = -1000
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.
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.
43 index, args, isolated_hash = args
44 json_file = os.path.join(args.results, '%d.json' % index)
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,
51 '--dump-json', json_file,
53 if args.target_os == 'fuchsia':
58 '-d', 'cpu', args.arch,
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'
68 '.swarming_module:infra/python/cpython/${platform}:' +
70 vpython_native_pkg = (
71 '.swarming_module:infra/tools/luci/vpython-native/${platform}:' +
74 '.swarming_module:infra/tools/luci/vpython/${platform}:' +
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',
89 '--test-launcher-summary-output=${ISOLATED_OUTDIR}/output.json',
90 '--system-log-file=${ISOLATED_OUTDIR}/system_log']
92 trigger_args.append('--gtest_filter=' + args.gtest_filter)
93 elif args.target_os == 'fuchsia':
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)
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',
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'
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:
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.')
145 args = parser.parse_args()
147 if args.target_os == 'detect':
148 with open(os.path.join(args.out_dir, 'args.gn')) as f:
151 l = l.split('#')[0].strip()
153 k, v = map(str.strip, l.split('=', 1))
155 if 'target_os' in gn_args:
156 args.target_os = gn_args['target_os'].strip('"')
158 args.target_os = { 'darwin': 'mac', 'linux2': 'linux', 'win32': 'win' }[
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:
170 subprocess.check_call(
171 ['tools/mb/mb.py', 'isolate', '//' + args.out_dir, args.test_name])
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'
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]
184 if os.path.isdir(args.results):
185 shutil.rmtree(args.results)
186 os.makedirs(args.results)
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)
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)),
208 print 'Results logs collected into', os.path.abspath(args.results) + '.'
215 if __name__ == '__main__':