blob: 5a109bd1a1a77521fdc043273f4f4a01e137f732 [file] [log] [blame]
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +02001#!/usr/bin/env python2.7
2# Copyright 2015, Google Inc.
3# All rights reserved.
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above
12# copyright notice, this list of conditions and the following disclaimer
13# in the documentation and/or other materials provided with the
14# distribution.
15# * Neither the name of Google Inc. nor the names of its
16# contributors may be used to endorse or promote products derived from
17# this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31"""Run test matrix."""
32
33import argparse
Jan Tattermuscha1906d52016-09-19 18:37:17 +020034import multiprocessing
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +020035import os
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +020036import sys
Jan Tattermusch5c79a312016-12-20 11:02:50 +010037
38import python_utils.jobset as jobset
39import python_utils.report_utils as report_utils
40from python_utils.filter_pull_request_tests import filter_tests
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +020041
42_ROOT = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '../..'))
43os.chdir(_ROOT)
44
Jan Tattermusch060eb872016-09-20 16:06:13 +020045# Set the timeout high to allow enough time for sanitizers and pre-building
46# clang docker.
Ken Payson97e69202016-10-17 09:11:49 -070047_RUNTESTS_TIMEOUT = 4*60*60
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +020048
Jan Tattermuscha1906d52016-09-19 18:37:17 +020049# Number of jobs assigned to each run_tests.py instance
Matt Kwongfef98962016-10-27 10:45:47 -070050_DEFAULT_INNER_JOBS = 2
Jan Tattermuscha1906d52016-09-19 18:37:17 +020051
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +020052
Matt Kwongfef98962016-10-27 10:45:47 -070053def _docker_jobspec(name, runtests_args=[], inner_jobs=_DEFAULT_INNER_JOBS):
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +020054 """Run a single instance of run_tests.py in a docker container"""
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +020055 test_job = jobset.JobSpec(
56 cmdline=['python', 'tools/run_tests/run_tests.py',
57 '--use_docker',
58 '-t',
Matt Kwongfef98962016-10-27 10:45:47 -070059 '-j', str(inner_jobs),
Jan Tattermuschcfcc0752016-10-09 17:02:34 +020060 '-x', 'report_%s.xml' % name,
61 '--report_suite_name', '%s' % name] + runtests_args,
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +020062 shortname='run_tests_%s' % name,
63 timeout_seconds=_RUNTESTS_TIMEOUT)
64 return test_job
65
66
Matt Kwongfef98962016-10-27 10:45:47 -070067def _workspace_jobspec(name, runtests_args=[], workspace_name=None, inner_jobs=_DEFAULT_INNER_JOBS):
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +020068 """Run a single instance of run_tests.py in a separate workspace"""
Jan Tattermuscha1906d52016-09-19 18:37:17 +020069 if not workspace_name:
70 workspace_name = 'workspace_%s' % name
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +020071 env = {'WORKSPACE_NAME': workspace_name}
72 test_job = jobset.JobSpec(
Jan Tattermusch5c79a312016-12-20 11:02:50 +010073 cmdline=['tools/run_tests/helper_scripts/run_tests_in_workspace.sh',
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +020074 '-t',
Matt Kwongfef98962016-10-27 10:45:47 -070075 '-j', str(inner_jobs),
Jan Tattermuschcfcc0752016-10-09 17:02:34 +020076 '-x', '../report_%s.xml' % name,
77 '--report_suite_name', '%s' % name] + runtests_args,
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +020078 environ=env,
79 shortname='run_tests_%s' % name,
80 timeout_seconds=_RUNTESTS_TIMEOUT)
81 return test_job
82
83
murgatroid991191b722017-02-08 11:56:52 -080084def _generate_jobs(languages, configs, platforms, iomgr_platform = 'native',
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +020085 arch=None, compiler=None,
Matt Kwongfef98962016-10-27 10:45:47 -070086 labels=[], extra_args=[],
87 inner_jobs=_DEFAULT_INNER_JOBS):
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +020088 result = []
89 for language in languages:
90 for platform in platforms:
91 for config in configs:
murgatroid991191b722017-02-08 11:56:52 -080092 name = '%s_%s_%s_%s' % (language, platform, config, iomgr_platform)
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +020093 runtests_args = ['-l', language,
94 '-c', config]
95 if arch or compiler:
96 name += '_%s_%s' % (arch, compiler)
97 runtests_args += ['--arch', arch,
98 '--compiler', compiler]
99
Jan Tattermusch6d7c6ef2016-09-22 13:40:48 +0200100 runtests_args += extra_args
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +0200101 if platform == 'linux':
Matt Kwongfef98962016-10-27 10:45:47 -0700102 job = _docker_jobspec(name=name, runtests_args=runtests_args, inner_jobs=inner_jobs)
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +0200103 else:
Matt Kwongfef98962016-10-27 10:45:47 -0700104 job = _workspace_jobspec(name=name, runtests_args=runtests_args, inner_jobs=inner_jobs)
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +0200105
106 job.labels = [platform, config, language] + labels
107 result.append(job)
108 return result
109
110
Matt Kwongfef98962016-10-27 10:45:47 -0700111def _create_test_jobs(extra_args=[], inner_jobs=_DEFAULT_INNER_JOBS):
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +0200112 test_jobs = []
113 # supported on linux only
114 test_jobs += _generate_jobs(languages=['sanity', 'php7'],
115 configs=['dbg', 'opt'],
116 platforms=['linux'],
Jan Tattermusch6d7c6ef2016-09-22 13:40:48 +0200117 labels=['basictests'],
Matt Kwongfef98962016-10-27 10:45:47 -0700118 extra_args=extra_args,
119 inner_jobs=inner_jobs)
murgatroid991687cab2016-10-11 11:42:01 -0700120
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +0200121 # supported on all platforms.
122 test_jobs += _generate_jobs(languages=['c', 'csharp', 'node', 'python'],
Jan Tattermusch6d7c6ef2016-09-22 13:40:48 +0200123 configs=['dbg', 'opt'],
124 platforms=['linux', 'macos', 'windows'],
125 labels=['basictests'],
Matt Kwongfef98962016-10-27 10:45:47 -0700126 extra_args=extra_args,
127 inner_jobs=inner_jobs)
murgatroid991687cab2016-10-11 11:42:01 -0700128
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +0200129 # supported on linux and mac.
130 test_jobs += _generate_jobs(languages=['c++', 'ruby', 'php'],
Jan Tattermusch6d7c6ef2016-09-22 13:40:48 +0200131 configs=['dbg', 'opt'],
132 platforms=['linux', 'macos'],
133 labels=['basictests'],
Matt Kwongfef98962016-10-27 10:45:47 -0700134 extra_args=extra_args,
135 inner_jobs=inner_jobs)
murgatroid991687cab2016-10-11 11:42:01 -0700136
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +0200137 # supported on mac only.
138 test_jobs += _generate_jobs(languages=['objc'],
139 configs=['dbg', 'opt'],
140 platforms=['macos'],
Jan Tattermusch6d7c6ef2016-09-22 13:40:48 +0200141 labels=['basictests'],
Matt Kwongfef98962016-10-27 10:45:47 -0700142 extra_args=extra_args,
143 inner_jobs=inner_jobs)
murgatroid991687cab2016-10-11 11:42:01 -0700144
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +0200145 # sanitizers
146 test_jobs += _generate_jobs(languages=['c'],
147 configs=['msan', 'asan', 'tsan'],
148 platforms=['linux'],
Jan Tattermusch6d7c6ef2016-09-22 13:40:48 +0200149 labels=['sanitizers'],
Matt Kwongfef98962016-10-27 10:45:47 -0700150 extra_args=extra_args,
151 inner_jobs=inner_jobs)
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +0200152 test_jobs += _generate_jobs(languages=['c++'],
153 configs=['asan', 'tsan'],
154 platforms=['linux'],
Jan Tattermusch6d7c6ef2016-09-22 13:40:48 +0200155 labels=['sanitizers'],
Matt Kwongfef98962016-10-27 10:45:47 -0700156 extra_args=extra_args,
157 inner_jobs=inner_jobs)
murgatroid991687cab2016-10-11 11:42:01 -0700158
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +0200159 return test_jobs
160
murgatroid991687cab2016-10-11 11:42:01 -0700161
Matt Kwongfef98962016-10-27 10:45:47 -0700162def _create_portability_test_jobs(extra_args=[], inner_jobs=_DEFAULT_INNER_JOBS):
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +0200163 test_jobs = []
164 # portability C x86
165 test_jobs += _generate_jobs(languages=['c'],
166 configs=['dbg'],
167 platforms=['linux'],
168 arch='x86',
169 compiler='default',
Jan Tattermusch6d7c6ef2016-09-22 13:40:48 +0200170 labels=['portability'],
Matt Kwongfef98962016-10-27 10:45:47 -0700171 extra_args=extra_args,
172 inner_jobs=inner_jobs)
murgatroid991687cab2016-10-11 11:42:01 -0700173
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +0200174 # portability C and C++ on x64
175 for compiler in ['gcc4.4', 'gcc4.6', 'gcc5.3',
176 'clang3.5', 'clang3.6', 'clang3.7']:
Matt Kwonge3beac92016-11-01 12:53:04 -0700177 test_jobs += _generate_jobs(languages=['c'],
178 configs=['dbg'],
179 platforms=['linux'],
180 arch='x64',
181 compiler=compiler,
182 labels=['portability'],
183 extra_args=extra_args,
184 inner_jobs=inner_jobs)
Jan Tattermusch8613e472016-11-22 11:15:53 +0100185
Matt Kwonge3beac92016-11-01 12:53:04 -0700186 for compiler in ['gcc4.8', 'gcc5.3',
187 'clang3.5', 'clang3.6', 'clang3.7']:
188 test_jobs += _generate_jobs(languages=['c++'],
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +0200189 configs=['dbg'],
190 platforms=['linux'],
191 arch='x64',
192 compiler=compiler,
Jan Tattermusch6d7c6ef2016-09-22 13:40:48 +0200193 labels=['portability'],
Matt Kwongfef98962016-10-27 10:45:47 -0700194 extra_args=extra_args,
195 inner_jobs=inner_jobs)
murgatroid991687cab2016-10-11 11:42:01 -0700196
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +0200197 # portability C on Windows
198 for arch in ['x86', 'x64']:
199 for compiler in ['vs2013', 'vs2015']:
200 test_jobs += _generate_jobs(languages=['c'],
201 configs=['dbg'],
202 platforms=['windows'],
203 arch=arch,
204 compiler=compiler,
Jan Tattermusch6d7c6ef2016-09-22 13:40:48 +0200205 labels=['portability'],
Matt Kwongfef98962016-10-27 10:45:47 -0700206 extra_args=extra_args,
207 inner_jobs=inner_jobs)
murgatroid991687cab2016-10-11 11:42:01 -0700208
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +0200209 test_jobs += _generate_jobs(languages=['python'],
210 configs=['dbg'],
211 platforms=['linux'],
212 arch='default',
213 compiler='python3.4',
Jan Tattermusch6d7c6ef2016-09-22 13:40:48 +0200214 labels=['portability'],
Matt Kwongfef98962016-10-27 10:45:47 -0700215 extra_args=extra_args,
216 inner_jobs=inner_jobs)
murgatroid991687cab2016-10-11 11:42:01 -0700217
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +0200218 test_jobs += _generate_jobs(languages=['csharp'],
219 configs=['dbg'],
220 platforms=['linux'],
221 arch='default',
222 compiler='coreclr',
Jan Tattermusch6d7c6ef2016-09-22 13:40:48 +0200223 labels=['portability'],
Matt Kwongfef98962016-10-27 10:45:47 -0700224 extra_args=extra_args,
225 inner_jobs=inner_jobs)
murgatroid99804c9e92016-12-05 12:19:57 -0800226
murgatroid991191b722017-02-08 11:56:52 -0800227 test_jobs += _generate_jobs(languages=['c'],
228 configs=['dbg'],
229 platforms=['linux'],
230 iomgr_platform='uv',
231 labels=['portability'],
232 extra_args=extra_args,
233 inner_jobs=inner_jobs)
234
murgatroid99804c9e92016-12-05 12:19:57 -0800235 test_jobs += _generate_jobs(languages=['node'],
236 configs=['dbg'],
237 platforms=['linux'],
238 arch='default',
239 compiler='electron1.3',
240 labels=['portability'],
241 extra_args=extra_args,
242 inner_jobs=inner_jobs)
murgatroid991191b722017-02-08 11:56:52 -0800243
244 test_jobs += _generate_jobs(languages=['node'],
245 configs=['dbg'],
246 platforms=['linux'],
247 iomgr_platform='uv',
248 labels=['portability'],
249 extra_args=extra_args,
250 inner_jobs=inner_jobs)
251
252 test_jobs += _generate_jobs(languages=['node'],
253 configs=['dbg'],
254 platforms=['linux'],
255 arch='default',
256 compiler='node4',
257 labels=['portability'],
258 extra_args=extra_args,
259 inner_jobs=inner_jobs)
260
261 test_jobs += _generate_jobs(languages=['node'],
262 configs=['dbg'],
263 platforms=['linux'],
264 arch='default',
265 compiler='node6',
266 labels=['portability'],
267 extra_args=extra_args,
268 inner_jobs=inner_jobs)
269
murgatroid991687cab2016-10-11 11:42:01 -0700270 return test_jobs
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +0200271
272
Jan Tattermusch6d7c6ef2016-09-22 13:40:48 +0200273def _allowed_labels():
274 """Returns a list of existing job labels."""
275 all_labels = set()
276 for job in _create_test_jobs() + _create_portability_test_jobs():
277 for label in job.labels:
278 all_labels.add(label)
279 return sorted(all_labels)
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +0200280
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +0200281
Jan Tattermusch68e27bf2016-12-16 14:09:03 +0100282def _runs_per_test_type(arg_str):
Jan Tattermusch6a851292016-12-20 10:20:42 +0100283 """Auxiliary function to parse the "runs_per_test" flag."""
284 try:
285 n = int(arg_str)
286 if n <= 0: raise ValueError
287 return n
288 except:
289 msg = '\'{}\' is not a positive integer'.format(arg_str)
290 raise argparse.ArgumentTypeError(msg)
Jan Tattermusch68e27bf2016-12-16 14:09:03 +0100291
292
Matt Kwong7e9bd6c2016-10-24 17:30:25 -0700293if __name__ == "__main__":
294 argp = argparse.ArgumentParser(description='Run a matrix of run_tests.py tests.')
295 argp.add_argument('-j', '--jobs',
296 default=multiprocessing.cpu_count()/_DEFAULT_INNER_JOBS,
297 type=int,
298 help='Number of concurrent run_tests.py instances.')
299 argp.add_argument('-f', '--filter',
300 choices=_allowed_labels(),
301 nargs='+',
302 default=[],
303 help='Filter targets to run by label with AND semantics.')
Jan Tattermusch6a851292016-12-20 10:20:42 +0100304 argp.add_argument('--exclude',
305 choices=_allowed_labels(),
306 nargs='+',
307 default=[],
308 help='Exclude targets with any of given labels.')
Matt Kwong7e9bd6c2016-10-24 17:30:25 -0700309 argp.add_argument('--build_only',
310 default=False,
311 action='store_const',
312 const=True,
313 help='Pass --build_only flag to run_tests.py instances.')
314 argp.add_argument('--force_default_poller', default=False, action='store_const', const=True,
315 help='Pass --force_default_poller to run_tests.py instances.')
316 argp.add_argument('--dry_run',
317 default=False,
318 action='store_const',
319 const=True,
320 help='Only print what would be run.')
321 argp.add_argument('--filter_pr_tests',
322 default=False,
323 action='store_const',
324 const=True,
Jan Tattermusch68e27bf2016-12-16 14:09:03 +0100325 help='Filters out tests irrelevant to pull request changes.')
Matt Kwong7e9bd6c2016-10-24 17:30:25 -0700326 argp.add_argument('--base_branch',
327 default='origin/master',
328 type=str,
329 help='Branch that pull request is requesting to merge into')
330 argp.add_argument('--inner_jobs',
331 default=_DEFAULT_INNER_JOBS,
332 type=int,
333 help='Number of jobs in each run_tests.py instance')
Jan Tattermusch68e27bf2016-12-16 14:09:03 +0100334 argp.add_argument('-n', '--runs_per_test', default=1, type=_runs_per_test_type,
335 help='How many times to run each tests. >1 runs implies ' +
336 'omitting passing test from the output & reports.')
Matt Kwong7e9bd6c2016-10-24 17:30:25 -0700337 args = argp.parse_args()
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +0200338
Matt Kwong7e9bd6c2016-10-24 17:30:25 -0700339 extra_args = []
340 if args.build_only:
341 extra_args.append('--build_only')
342 if args.force_default_poller:
343 extra_args.append('--force_default_poller')
Jan Tattermusch68e27bf2016-12-16 14:09:03 +0100344 if args.runs_per_test > 1:
345 extra_args.append('-n')
346 extra_args.append('%s' % args.runs_per_test)
347 extra_args.append('--quiet_success')
Matt Kwongfef98962016-10-27 10:45:47 -0700348
Matt Kwong7e9bd6c2016-10-24 17:30:25 -0700349 all_jobs = _create_test_jobs(extra_args=extra_args, inner_jobs=args.inner_jobs) + \
350 _create_portability_test_jobs(extra_args=extra_args, inner_jobs=args.inner_jobs)
Jan Tattermusch6d7c6ef2016-09-22 13:40:48 +0200351
Matt Kwong7e9bd6c2016-10-24 17:30:25 -0700352 jobs = []
353 for job in all_jobs:
354 if not args.filter or all(filter in job.labels for filter in args.filter):
Jan Tattermusch6a851292016-12-20 10:20:42 +0100355 if not any(exclude_label in job.labels for exclude_label in args.exclude):
356 jobs.append(job)
Jan Tattermusch6d7c6ef2016-09-22 13:40:48 +0200357
Matt Kwong7e9bd6c2016-10-24 17:30:25 -0700358 if not jobs:
359 jobset.message('FAILED', 'No test suites match given criteria.',
360 do_newline=True)
361 sys.exit(1)
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +0200362
Matt Kwong7e9bd6c2016-10-24 17:30:25 -0700363 print('IMPORTANT: The changes you are testing need to be locally committed')
364 print('because only the committed changes in the current branch will be')
365 print('copied to the docker environment or into subworkspaces.')
murgatroid991687cab2016-10-11 11:42:01 -0700366
Matt Kwong7e9bd6c2016-10-24 17:30:25 -0700367 skipped_jobs = []
Jan Tattermusch9c79e8d2016-09-19 14:33:18 +0200368
Matt Kwong7e9bd6c2016-10-24 17:30:25 -0700369 if args.filter_pr_tests:
370 print('Looking for irrelevant tests to skip...')
371 relevant_jobs = filter_tests(jobs, args.base_branch)
372 if len(relevant_jobs) == len(jobs):
373 print('No tests will be skipped.')
374 else:
375 print('These tests will be skipped:')
Matt Kwongaa6c94c2016-11-09 15:53:23 -0800376 skipped_jobs = list(set(jobs) - set(relevant_jobs))
377 # Sort by shortnames to make printing of skipped tests consistent
378 skipped_jobs.sort(key=lambda job: job.shortname)
Matt Kwong7e9bd6c2016-10-24 17:30:25 -0700379 for job in list(skipped_jobs):
380 print(' %s' % job.shortname)
381 jobs = relevant_jobs
382
383 print('Will run these tests:')
384 for job in jobs:
385 if args.dry_run:
386 print(' %s: "%s"' % (job.shortname, ' '.join(job.cmdline)))
387 else:
388 print(' %s' % job.shortname)
Matt Kwong5c691c62016-10-20 17:11:18 -0700389 print
390
Jan Tattermusch7b9c21a2016-09-22 14:44:27 +0200391 if args.dry_run:
Matt Kwong7e9bd6c2016-10-24 17:30:25 -0700392 print('--dry_run was used, exiting')
393 sys.exit(1)
394
395 jobset.message('START', 'Running test matrix.', do_newline=True)
396 num_failures, resultset = jobset.run(jobs,
397 newline_on_success=True,
398 travis=True,
399 maxjobs=args.jobs)
400 # Merge skipped tests into results to show skipped tests on report.xml
401 if skipped_jobs:
402 skipped_results = jobset.run(skipped_jobs,
403 skip_jobs=True)
404 resultset.update(skipped_results)
405 report_utils.render_junit_xml_report(resultset, 'report.xml',
406 suite_name='aggregate_tests')
407
408 if num_failures == 0:
409 jobset.message('SUCCESS', 'All run_tests.py instance finished successfully.',
410 do_newline=True)
Jan Tattermusch7b9c21a2016-09-22 14:44:27 +0200411 else:
Matt Kwong7e9bd6c2016-10-24 17:30:25 -0700412 jobset.message('FAILED', 'Some run_tests.py instance have failed.',
413 do_newline=True)
414 sys.exit(1)