blob: b5c1043e72a5e6627ca15cfa23e204a329d5c6ad [file] [log] [blame]
Jan Tattermusch320bd612015-09-15 12:44:35 -07001#!/usr/bin/env python
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 interop (cross-language) tests in parallel."""
32
33import argparse
Jan Tattermusch91ad0182015-10-01 09:22:03 -070034import dockerjob
Jan Tattermusch320bd612015-09-15 12:44:35 -070035import itertools
36import xml.etree.cElementTree as ET
37import jobset
Jan Tattermusch8266c672015-09-17 09:18:03 -070038import os
39import subprocess
40import sys
Jan Tattermusch91ad0182015-10-01 09:22:03 -070041import tempfile
Jan Tattermusch8266c672015-09-17 09:18:03 -070042import time
Jan Tattermusch91ad0182015-10-01 09:22:03 -070043import uuid
Jan Tattermusch320bd612015-09-15 12:44:35 -070044
Jan Tattermusch91ad0182015-10-01 09:22:03 -070045ROOT = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '../..'))
46os.chdir(ROOT)
47
48_DEFAULT_SERVER_PORT=8080
Jan Tattermuschf49936a2015-09-16 15:44:26 -070049
50_CLOUD_TO_PROD_BASE_ARGS = [
51 '--server_host_override=grpc-test.sandbox.google.com',
52 '--server_host=grpc-test.sandbox.google.com',
53 '--server_port=443']
54
Jan Tattermusch8266c672015-09-17 09:18:03 -070055_CLOUD_TO_CLOUD_BASE_ARGS = [
56 '--server_host_override=foo.test.google.fr']
57
Jan Tattermuschf49936a2015-09-16 15:44:26 -070058# TOOD(jtattermusch) wrapped languages use this variable for location
59# of roots.pem. We might want to use GRPC_DEFAULT_SSL_ROOTS_FILE_PATH
60# supported by C core SslCredentials instead.
61_SSL_CERT_ENV = { 'SSL_CERT_FILE':'/usr/local/share/grpc/roots.pem' }
62
63# TODO(jtatttermusch) unify usage of --enable_ssl, --use_tls and --use_tls=true
64
Jan Tattermusch8266c672015-09-17 09:18:03 -070065
Jan Tattermuschf49936a2015-09-16 15:44:26 -070066class CXXLanguage:
67
68 def __init__(self):
69 self.client_cmdline_base = ['bins/opt/interop_client']
70 self.client_cwd = None
Jan Tattermusch91ad0182015-10-01 09:22:03 -070071 self.server_cwd = None
Jan Tattermuschf49936a2015-09-16 15:44:26 -070072
73 def cloud_to_prod_args(self):
74 return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
75 ['--enable_ssl','--use_prod_roots'])
76
Jan Tattermusch8266c672015-09-17 09:18:03 -070077 def cloud_to_cloud_args(self):
78 return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
79 ['--enable_ssl'])
80
Jan Tattermuschf49936a2015-09-16 15:44:26 -070081 def cloud_to_prod_env(self):
82 return None
83
Jan Tattermusch91ad0182015-10-01 09:22:03 -070084 def server_args(self):
85 return ['bins/opt/interop_server', '--enable_ssl']
86
Jan Tattermuschf49936a2015-09-16 15:44:26 -070087 def __str__(self):
88 return 'c++'
89
90
91class CSharpLanguage:
92
93 def __init__(self):
94 self.client_cmdline_base = ['mono', 'Grpc.IntegrationTesting.Client.exe']
95 self.client_cwd = 'src/csharp/Grpc.IntegrationTesting.Client/bin/Debug'
Jan Tattermusch91ad0182015-10-01 09:22:03 -070096 self.server_cwd = 'src/csharp/Grpc.IntegrationTesting.Server/bin/Debug'
Jan Tattermuschf49936a2015-09-16 15:44:26 -070097
98 def cloud_to_prod_args(self):
99 return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
100 ['--use_tls'])
101
Jan Tattermusch8266c672015-09-17 09:18:03 -0700102 def cloud_to_cloud_args(self):
103 return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
104 ['--use_tls', '--use_test_ca'])
105
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700106 def cloud_to_prod_env(self):
107 return _SSL_CERT_ENV
108
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700109 def server_args(self):
110 return ['mono', 'Grpc.IntegrationTesting.Server.exe', '--use_tls']
111
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700112 def __str__(self):
113 return 'csharp'
114
115
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700116class JavaLanguage:
117
118 def __init__(self):
119 self.client_cmdline_base = ['./run-test-client.sh']
120 self.client_cwd = '../grpc-java'
121 self.server_cwd = '../grpc-java'
122
123 def cloud_to_prod_args(self):
124 return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
125 ['--use_tls=true'])
126
127 def cloud_to_cloud_args(self):
128 return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
129 ['--use_tls=true', '--use_test_ca=true'])
130
131 def cloud_to_prod_env(self):
132 return None
133
134 def server_args(self):
135 return ['./run-test-server.sh', '--use_tls=true']
136
137 def __str__(self):
138 return 'java'
139
140
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700141class NodeLanguage:
142
143 def __init__(self):
144 self.client_cmdline_base = ['node', 'src/node/interop/interop_client.js']
145 self.client_cwd = None
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700146 self.server_cwd = None
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700147
148 def cloud_to_prod_args(self):
149 return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
150 ['--use_tls=true'])
151
Jan Tattermusch8266c672015-09-17 09:18:03 -0700152 def cloud_to_cloud_args(self):
153 return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
154 ['--use_tls=true', '--use_test_ca=true'])
155
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700156 def cloud_to_prod_env(self):
157 return _SSL_CERT_ENV
158
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700159 def server_args(self):
160 return ['node', 'src/node/interop/interop_server.js', '--use_tls=true']
161
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700162 def __str__(self):
163 return 'node'
164
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700165
166class PHPLanguage:
167
168 def __init__(self):
169 self.client_cmdline_base = ['src/php/bin/interop_client.sh']
170 self.client_cwd = None
171
172 def cloud_to_prod_args(self):
173 return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
174 ['--use_tls'])
175
Jan Tattermusch8266c672015-09-17 09:18:03 -0700176 def cloud_to_cloud_args(self):
177 return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
178 ['--use_tls', '--use_test_ca'])
179
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700180 def cloud_to_prod_env(self):
181 return _SSL_CERT_ENV
182
183 def __str__(self):
184 return 'php'
185
186
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700187class RubyLanguage:
188
189 def __init__(self):
190 self.client_cmdline_base = ['ruby', 'src/ruby/bin/interop/interop_client.rb']
191 self.client_cwd = None
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700192 self.server_cwd = None
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700193
194 def cloud_to_prod_args(self):
195 return (self.client_cmdline_base + _CLOUD_TO_PROD_BASE_ARGS +
196 ['--use_tls'])
197
Jan Tattermusch8266c672015-09-17 09:18:03 -0700198 def cloud_to_cloud_args(self):
199 return (self.client_cmdline_base + _CLOUD_TO_CLOUD_BASE_ARGS +
200 ['--use_tls', '--use_test_ca'])
201
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700202 def cloud_to_prod_env(self):
203 return _SSL_CERT_ENV
204
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700205 def server_args(self):
206 return ['ruby', 'src/ruby/bin/interop/interop_server.rb', '--use_tls']
207
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700208 def __str__(self):
209 return 'ruby'
210
211
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700212# TODO(jtattermusch): python once we get it working
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700213_LANGUAGES = {
214 'c++' : CXXLanguage(),
215 'csharp' : CSharpLanguage(),
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700216 'java' : JavaLanguage(),
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700217 'node' : NodeLanguage(),
Jan Tattermuschf88f3e22015-09-16 17:50:45 -0700218 'php' : PHPLanguage(),
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700219 'ruby' : RubyLanguage(),
220}
Jan Tattermusch320bd612015-09-15 12:44:35 -0700221
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700222# languages supported as cloud_to_cloud servers
Jan Tattermusch8266c672015-09-17 09:18:03 -0700223# TODO(jtattermusch): enable other languages as servers as well
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700224_SERVERS = ['c++', 'node', 'csharp', 'java']
Jan Tattermusch8266c672015-09-17 09:18:03 -0700225
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700226# TODO(jtattermusch): add empty_stream once C++ starts supporting it.
227# TODO(jtattermusch): add timeout_on_sleeping_server once java starts supporting it.
Jan Tattermusch320bd612015-09-15 12:44:35 -0700228# TODO(jtattermusch): add support for auth tests.
229_TEST_CASES = ['large_unary', 'empty_unary', 'ping_pong',
230 'client_streaming', 'server_streaming',
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700231 'cancel_after_begin', 'cancel_after_first_response']
Jan Tattermusch320bd612015-09-15 12:44:35 -0700232
Jan Tattermusch8266c672015-09-17 09:18:03 -0700233
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700234def docker_run_cmdline(cmdline, image, docker_args=[], cwd=None, environ=None):
235 """Wraps given cmdline array to create 'docker run' cmdline from it."""
236 docker_cmdline = ['docker', 'run', '-i', '--rm=true']
237
238 # turn environ into -e docker args
239 if environ:
240 for k,v in environ.iteritems():
241 docker_cmdline += ['-e', '%s=%s' % (k,v)]
242
243 # set working directory
244 workdir = '/var/local/git/grpc'
245 if cwd:
246 workdir = os.path.join(workdir, cwd)
247 docker_cmdline += ['-w', workdir]
248
249 docker_cmdline += docker_args + [image] + cmdline
250 return docker_cmdline
251
252
253def bash_login_cmdline(cmdline):
254 """Creates bash -l -c cmdline from args list."""
255 # Use login shell:
256 # * rvm and nvm require it
257 # * makes error messages clearer if executables are missing
258 return ['bash', '-l', '-c', ' '.join(cmdline)]
259
260
261def cloud_to_prod_jobspec(language, test_case, docker_image=None):
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700262 """Creates jobspec for cloud-to-prod interop test"""
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700263 cmdline = bash_login_cmdline(language.cloud_to_prod_args() +
264 ['--test_case=%s' % test_case])
265 cwd = language.client_cwd
266 environ = language.cloud_to_prod_env()
267 if docker_image:
268 cmdline = docker_run_cmdline(cmdline, image=docker_image, cwd=cwd, environ=environ)
269 cwd = None
270 environ = None
271
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700272 test_job = jobset.JobSpec(
273 cmdline=cmdline,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700274 cwd=cwd,
275 environ=environ,
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700276 shortname="cloud_to_prod:%s:%s" % (language, test_case),
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700277 timeout_seconds=60,
278 flake_retries=5 if args.allow_flakes else 0,
279 timeout_retries=2 if args.allow_flakes else 0)
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700280 return test_job
281
Jan Tattermusch8266c672015-09-17 09:18:03 -0700282
283def cloud_to_cloud_jobspec(language, test_case, server_name, server_host,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700284 server_port, docker_image=None):
Jan Tattermusch8266c672015-09-17 09:18:03 -0700285 """Creates jobspec for cloud-to-cloud interop test"""
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700286 cmdline = bash_login_cmdline(language.cloud_to_cloud_args() +
287 ['--test_case=%s' % test_case,
288 '--server_host=%s' % server_host,
289 '--server_port=%s' % server_port ])
290 cwd = language.client_cwd
291 if docker_image:
292 cmdline = docker_run_cmdline(cmdline,
293 image=docker_image,
294 cwd=cwd,
295 docker_args=['--net=host'])
296 cwd = None
Jan Tattermusch8266c672015-09-17 09:18:03 -0700297 test_job = jobset.JobSpec(
298 cmdline=cmdline,
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700299 cwd=cwd,
Jan Tattermusch8266c672015-09-17 09:18:03 -0700300 shortname="cloud_to_cloud:%s:%s_server:%s" % (language, server_name,
301 test_case),
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700302 timeout_seconds=60,
303 flake_retries=5 if args.allow_flakes else 0,
304 timeout_retries=2 if args.allow_flakes else 0)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700305 return test_job
306
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700307
308def server_jobspec(language, docker_image):
309 """Create jobspec for running a server"""
310 cidfile = tempfile.mktemp()
311 cmdline = bash_login_cmdline(language.server_args() +
312 ['--port=%s' % _DEFAULT_SERVER_PORT])
313 docker_cmdline = docker_run_cmdline(cmdline,
314 image=docker_image,
315 cwd=language.server_cwd,
316 docker_args=['-p', str(_DEFAULT_SERVER_PORT),
317 '--cidfile', cidfile])
318 server_job = jobset.JobSpec(
319 cmdline=docker_cmdline,
320 shortname="interop_server:%s" % language,
321 timeout_seconds=30*60)
322 server_job.cidfile = cidfile
323 return server_job
324
325
326def build_interop_image_jobspec(language, tag=None):
327 """Creates jobspec for building interop docker image for a language"""
328 safelang = str(language).replace("+", "x")
329 if not tag:
330 tag = 'grpc_interop_%s:%s' % (safelang, uuid.uuid4())
331 env = {'INTEROP_IMAGE': tag, 'BASE_NAME': 'grpc_interop_%s' % safelang}
332 if not args.travis:
333 env['TTY_FLAG'] = '-t'
334 build_job = jobset.JobSpec(
335 cmdline=['tools/jenkins/build_interop_image.sh'],
336 environ=env,
337 shortname="build_docker_%s" % (language),
338 timeout_seconds=30*60)
339 build_job.tag = tag
340 return build_job
341
342
Jan Tattermusch320bd612015-09-15 12:44:35 -0700343argp = argparse.ArgumentParser(description='Run interop tests.')
344argp.add_argument('-l', '--language',
345 choices=['all'] + sorted(_LANGUAGES),
346 nargs='+',
Jan Tattermusch8266c672015-09-17 09:18:03 -0700347 default=['all'],
348 help='Clients to run.')
349argp.add_argument('-j', '--jobs', default=24, type=int)
350argp.add_argument('--cloud_to_prod',
351 default=False,
352 action='store_const',
353 const=True,
354 help='Run cloud_to_prod tests.')
355argp.add_argument('-s', '--server',
356 choices=['all'] + sorted(_SERVERS),
357 action='append',
358 help='Run cloud_to_cloud servers in a separate docker ' +
359 'image. Servers can only be started automatically if ' +
360 '--use_docker option is enabled.',
361 default=[])
362argp.add_argument('--override_server',
363 action='append',
364 type=lambda kv: kv.split("="),
365 help='Use servername=HOST:PORT to explicitly specify a server. E.g. csharp=localhost:50000',
366 default=[])
367argp.add_argument('-t', '--travis',
368 default=False,
369 action='store_const',
370 const=True)
371argp.add_argument('--use_docker',
372 default=False,
373 action='store_const',
374 const=True,
375 help='Run all the interop tests under docker. That provides ' +
376 'additional isolation and prevents the need to install ' +
377 'language specific prerequisites. Only available on Linux.')
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700378argp.add_argument('--allow_flakes',
379 default=False,
380 action='store_const',
381 const=True,
382 help="Allow flaky tests to show as passing (re-runs failed tests up to five times)")
Jan Tattermusch320bd612015-09-15 12:44:35 -0700383args = argp.parse_args()
384
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700385servers = set(s for s in itertools.chain.from_iterable(_SERVERS
Jan Tattermusch8266c672015-09-17 09:18:03 -0700386 if x == 'all' else [x]
387 for x in args.server))
388
389if args.use_docker:
390 if not args.travis:
391 print 'Seen --use_docker flag, will run interop tests under docker.'
392 print
393 print 'IMPORTANT: The changes you are testing need to be locally committed'
394 print 'because only the committed changes in the current branch will be'
395 print 'copied to the docker environment.'
396 time.sleep(5)
397
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700398if not args.use_docker and servers:
399 print "Running interop servers is only supported with --use_docker option enabled."
400 sys.exit(1)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700401
Jan Tattermuschf49936a2015-09-16 15:44:26 -0700402languages = set(_LANGUAGES[l]
403 for l in itertools.chain.from_iterable(
404 _LANGUAGES.iterkeys() if x == 'all' else [x]
405 for x in args.language))
Jan Tattermusch320bd612015-09-15 12:44:35 -0700406
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700407docker_images={}
408if args.use_docker:
409 # languages for which to build docker images
410 languages_to_build = set(_LANGUAGES[k] for k in set([str(l) for l in languages] +
411 [s for s in servers]))
Jan Tattermusch8266c672015-09-17 09:18:03 -0700412
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700413 build_jobs = []
414 for l in languages_to_build:
415 job = build_interop_image_jobspec(l)
416 docker_images[str(l)] = job.tag
417 build_jobs.append(job)
Jan Tattermusch8266c672015-09-17 09:18:03 -0700418
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700419 if build_jobs:
420 jobset.message('START', 'Building interop docker images.', do_newline=True)
421 if jobset.run(build_jobs, newline_on_success=True, maxjobs=args.jobs):
422 jobset.message('SUCCESS', 'All docker images built successfully.', do_newline=True)
423 else:
424 jobset.message('FAILED', 'Failed to build interop docker images.', do_newline=True)
425 for image in docker_images.itervalues():
426 dockerjob.remove_image(image, skip_nonexistent=True)
427 exit(1);
Jan Tattermusch8266c672015-09-17 09:18:03 -0700428
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700429# Start interop servers.
430server_jobs={}
431server_addresses={}
432try:
433 for s in servers:
434 lang = str(s)
435 spec = server_jobspec(_LANGUAGES[lang], docker_images.get(lang))
436 job = dockerjob.DockerJob(spec)
437 server_jobs[lang] = job
438 server_addresses[lang] = ('localhost', job.mapped_port(_DEFAULT_SERVER_PORT))
Jan Tattermusch8266c672015-09-17 09:18:03 -0700439
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700440 jobs = []
441 if args.cloud_to_prod:
442 for language in languages:
443 for test_case in _TEST_CASES:
444 test_job = cloud_to_prod_jobspec(language, test_case,
445 docker_image=docker_images.get(str(language)))
446 jobs.append(test_job)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700447
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700448 for server in args.override_server:
449 server_name = server[0]
450 (server_host, server_port) = server[1].split(':')
451 server_addresses[server_name] = (server_host, server_port)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700452
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700453 for server_name, server_address in server_addresses.iteritems():
454 (server_host, server_port) = server_address
455 for language in languages:
456 for test_case in _TEST_CASES:
457 test_job = cloud_to_cloud_jobspec(language,
458 test_case,
459 server_name,
460 server_host,
461 server_port,
462 docker_image=docker_images.get(str(language)))
463 jobs.append(test_job)
Jan Tattermusch320bd612015-09-15 12:44:35 -0700464
Jan Tattermusch91ad0182015-10-01 09:22:03 -0700465 if not jobs:
466 print "No jobs to run."
467 for image in docker_images.itervalues():
468 dockerjob.remove_image(image, skip_nonexistent=True)
469 sys.exit(1)
470
471 root = ET.Element('testsuites')
472 testsuite = ET.SubElement(root, 'testsuite', id='1', package='grpc', name='tests')
473
474 if jobset.run(jobs, newline_on_success=True, maxjobs=args.jobs, xml_report=testsuite):
475 jobset.message('SUCCESS', 'All tests passed', do_newline=True)
476 else:
477 jobset.message('FAILED', 'Some tests failed', do_newline=True)
478
479 tree = ET.ElementTree(root)
480 tree.write('report.xml', encoding='UTF-8')
481
482finally:
483 # Check if servers are still running.
484 for server, job in server_jobs.iteritems():
485 if not job.is_running():
486 print 'Server "%s" has exited prematurely.' % server
487
488 dockerjob.finish_jobs([j for j in server_jobs.itervalues()])
489
490 for image in docker_images.itervalues():
491 print 'Removing docker image %s' % image
492 dockerjob.remove_image(image)