blob: 4774a317053b90ef563bfab3a8cc3b5ad0f77ea0 [file] [log] [blame]
Nicolas Noblef3585732015-03-15 20:05:24 -07001#!/usr/bin/env python
Craig Tillerc2c79212015-02-16 12:00:01 -08002# 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
Nicolas Nobleddef2462015-01-06 18:08:25 -080031"""Run tests in parallel."""
32
33import argparse
34import glob
Craig Tillerf53d9c82015-08-04 14:19:43 -070035import hashlib
Nicolas Nobleddef2462015-01-06 18:08:25 -080036import itertools
Craig Tiller261dd982015-01-16 16:41:45 -080037import json
Nicolas Nobleddef2462015-01-06 18:08:25 -080038import multiprocessing
Craig Tiller1cc11db2015-01-15 22:50:50 -080039import os
David Garcia Quintas79e389f2015-06-02 17:49:42 -070040import platform
41import random
Craig Tillerfe406ec2015-02-24 13:55:12 -080042import re
David Garcia Quintas79e389f2015-06-02 17:49:42 -070043import subprocess
Nicolas Nobleddef2462015-01-06 18:08:25 -080044import sys
ctiller3040cb72015-01-07 12:13:17 -080045import time
Nicolas "Pixel" Noble5937b5b2015-06-26 02:04:12 +020046import xml.etree.cElementTree as ET
Craig Tillerf53d9c82015-08-04 14:19:43 -070047import urllib2
Nicolas Nobleddef2462015-01-06 18:08:25 -080048
49import jobset
ctiller3040cb72015-01-07 12:13:17 -080050import watch_dirs
Nicolas Nobleddef2462015-01-06 18:08:25 -080051
Craig Tiller2cc2b842015-02-27 11:38:31 -080052ROOT = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '../..'))
53os.chdir(ROOT)
54
55
Craig Tiller06805272015-06-11 14:46:47 -070056_FORCE_ENVIRON_FOR_WRAPPERS = {}
57
58
Craig Tillerd50993d2015-08-05 08:04:36 -070059def platform_string():
60 if platform.system() == 'Windows':
61 return 'windows'
62 elif platform.system() == 'Darwin':
63 return 'mac'
64 elif platform.system() == 'Linux':
65 return 'linux'
66 else:
67 return 'posix'
68
69
Craig Tiller738c3342015-01-12 14:28:33 -080070# SimpleConfig: just compile with CONFIG=config, and run the binary to test
71class SimpleConfig(object):
Craig Tillerb50d1662015-01-15 17:28:21 -080072
Craig Tillerb2ea0b92015-08-26 13:06:53 -070073 def __init__(self, config, environ=None, timeout_seconds=5*60):
murgatroid99132ce6a2015-03-04 17:29:14 -080074 if environ is None:
75 environ = {}
Craig Tiller738c3342015-01-12 14:28:33 -080076 self.build_config = config
Craig Tillerc7449162015-01-16 14:42:10 -080077 self.allow_hashing = (config != 'gcov')
Craig Tiller547db2b2015-01-30 14:08:39 -080078 self.environ = environ
murgatroid99132ce6a2015-03-04 17:29:14 -080079 self.environ['CONFIG'] = config
Craig Tillerb2ea0b92015-08-26 13:06:53 -070080 self.timeout_seconds = timeout_seconds
Craig Tiller738c3342015-01-12 14:28:33 -080081
Craig Tiller4fc90032015-05-21 10:39:52 -070082 def job_spec(self, cmdline, hash_targets, shortname=None, environ={}):
Craig Tiller49f61322015-03-03 13:02:11 -080083 """Construct a jobset.JobSpec for a test under this config
84
85 Args:
86 cmdline: a list of strings specifying the command line the test
87 would like to run
88 hash_targets: either None (don't do caching of test results), or
89 a list of strings specifying files to include in a
90 binary hash to check if a test has changed
91 -- if used, all artifacts needed to run the test must
92 be listed
93 """
Craig Tiller4fc90032015-05-21 10:39:52 -070094 actual_environ = self.environ.copy()
95 for k, v in environ.iteritems():
96 actual_environ[k] = v
Craig Tiller49f61322015-03-03 13:02:11 -080097 return jobset.JobSpec(cmdline=cmdline,
Jan Tattermusch9a7d30c2015-04-23 16:12:55 -070098 shortname=shortname,
Craig Tiller4fc90032015-05-21 10:39:52 -070099 environ=actual_environ,
Craig Tillerb2ea0b92015-08-26 13:06:53 -0700100 timeout_seconds=self.timeout_seconds,
Craig Tiller547db2b2015-01-30 14:08:39 -0800101 hash_targets=hash_targets
102 if self.allow_hashing else None)
Craig Tiller738c3342015-01-12 14:28:33 -0800103
104
105# ValgrindConfig: compile with some CONFIG=config, but use valgrind to run
106class ValgrindConfig(object):
Craig Tillerb50d1662015-01-15 17:28:21 -0800107
murgatroid99132ce6a2015-03-04 17:29:14 -0800108 def __init__(self, config, tool, args=None):
109 if args is None:
110 args = []
Craig Tiller738c3342015-01-12 14:28:33 -0800111 self.build_config = config
Craig Tiller2aa4d642015-01-14 15:59:44 -0800112 self.tool = tool
Craig Tiller1a305b12015-02-18 13:37:06 -0800113 self.args = args
Craig Tillerc7449162015-01-16 14:42:10 -0800114 self.allow_hashing = False
Craig Tiller738c3342015-01-12 14:28:33 -0800115
Craig Tiller49f61322015-03-03 13:02:11 -0800116 def job_spec(self, cmdline, hash_targets):
Craig Tiller1a305b12015-02-18 13:37:06 -0800117 return jobset.JobSpec(cmdline=['valgrind', '--tool=%s' % self.tool] +
Craig Tiller49f61322015-03-03 13:02:11 -0800118 self.args + cmdline,
Craig Tiller71ec6cb2015-06-03 00:51:11 -0700119 shortname='valgrind %s' % cmdline[0],
Craig Tiller1a305b12015-02-18 13:37:06 -0800120 hash_targets=None)
Craig Tiller738c3342015-01-12 14:28:33 -0800121
122
Craig Tillerc7449162015-01-16 14:42:10 -0800123class CLanguage(object):
124
Craig Tillere9c959d2015-01-18 10:23:26 -0800125 def __init__(self, make_target, test_lang):
Craig Tillerc7449162015-01-16 14:42:10 -0800126 self.make_target = make_target
Craig Tillerd50993d2015-08-05 08:04:36 -0700127 self.platform = platform_string()
Craig Tiller711bbe62015-08-19 12:35:16 -0700128 self.test_lang = test_lang
Craig Tillerc7449162015-01-16 14:42:10 -0800129
Nicolas "Pixel" Noble9db7c3b2015-02-27 06:03:00 +0100130 def test_specs(self, config, travis):
Craig Tiller547db2b2015-01-30 14:08:39 -0800131 out = []
Craig Tiller711bbe62015-08-19 12:35:16 -0700132 with open('tools/run_tests/tests.json') as f:
133 js = json.load(f)
134 platforms_str = 'ci_platforms' if travis else 'platforms'
135 binaries = [tgt
136 for tgt in js
137 if tgt['language'] == self.test_lang and
138 config.build_config not in tgt['exclude_configs'] and
139 platform_string() in tgt[platforms_str]]
140 for target in binaries:
Nicolas "Pixel" Noble9db7c3b2015-02-27 06:03:00 +0100141 if travis and target['flaky']:
142 continue
Nicolas Noblee1445362015-05-11 17:40:26 -0700143 if self.platform == 'windows':
Craig Tillerf4182602015-09-01 12:23:16 -0700144 binary = 'vsprojects/%s/%s.exe' % (
145 _WINDOWS_CONFIG[config.build_config], target['name'])
Nicolas Noblee1445362015-05-11 17:40:26 -0700146 else:
147 binary = 'bins/%s/%s' % (config.build_config, target['name'])
yang-g6c1fdc62015-08-18 11:57:42 -0700148 if os.path.isfile(binary):
149 out.append(config.job_spec([binary], [binary]))
150 else:
151 print "\nWARNING: binary not found, skipping", binary
Nicolas Noblee1445362015-05-11 17:40:26 -0700152 return sorted(out)
Craig Tillerc7449162015-01-16 14:42:10 -0800153
154 def make_targets(self):
Craig Tiller7bb3efd2015-09-01 08:04:03 -0700155 if platform_string() == 'windows':
156 # don't build tools on windows just yet
157 return ['buildtests_%s' % self.make_target]
Craig Tiller7552f0f2015-06-19 17:46:20 -0700158 return ['buildtests_%s' % self.make_target, 'tools_%s' % self.make_target]
Craig Tillerc7449162015-01-16 14:42:10 -0800159
160 def build_steps(self):
161 return []
162
murgatroid99132ce6a2015-03-04 17:29:14 -0800163 def supports_multi_config(self):
164 return True
165
166 def __str__(self):
167 return self.make_target
168
Craig Tiller99775822015-01-30 13:07:16 -0800169
murgatroid992c8d5162015-01-26 10:41:21 -0800170class NodeLanguage(object):
171
Nicolas "Pixel" Noble9db7c3b2015-02-27 06:03:00 +0100172 def test_specs(self, config, travis):
Craig Tiller4fc90032015-05-21 10:39:52 -0700173 return [config.job_spec(['tools/run_tests/run_node.sh'], None,
Craig Tiller06805272015-06-11 14:46:47 -0700174 environ=_FORCE_ENVIRON_FOR_WRAPPERS)]
murgatroid992c8d5162015-01-26 10:41:21 -0800175
176 def make_targets(self):
Craig Tilleraf7cf542015-05-22 10:07:34 -0700177 return ['static_c', 'shared_c']
murgatroid992c8d5162015-01-26 10:41:21 -0800178
179 def build_steps(self):
180 return [['tools/run_tests/build_node.sh']]
Craig Tillerc7449162015-01-16 14:42:10 -0800181
murgatroid99132ce6a2015-03-04 17:29:14 -0800182 def supports_multi_config(self):
183 return False
184
185 def __str__(self):
186 return 'node'
187
Craig Tiller99775822015-01-30 13:07:16 -0800188
Craig Tillerc7449162015-01-16 14:42:10 -0800189class PhpLanguage(object):
190
Nicolas "Pixel" Noble9db7c3b2015-02-27 06:03:00 +0100191 def test_specs(self, config, travis):
Craig Tiller4fc90032015-05-21 10:39:52 -0700192 return [config.job_spec(['src/php/bin/run_tests.sh'], None,
Craig Tiller06805272015-06-11 14:46:47 -0700193 environ=_FORCE_ENVIRON_FOR_WRAPPERS)]
Craig Tillerc7449162015-01-16 14:42:10 -0800194
195 def make_targets(self):
Craig Tilleraf7cf542015-05-22 10:07:34 -0700196 return ['static_c', 'shared_c']
Craig Tillerc7449162015-01-16 14:42:10 -0800197
198 def build_steps(self):
199 return [['tools/run_tests/build_php.sh']]
200
murgatroid99132ce6a2015-03-04 17:29:14 -0800201 def supports_multi_config(self):
202 return False
203
204 def __str__(self):
205 return 'php'
206
Craig Tillerc7449162015-01-16 14:42:10 -0800207
Nathaniel Manista840615e2015-01-22 20:31:47 +0000208class PythonLanguage(object):
209
Craig Tiller49f61322015-03-03 13:02:11 -0800210 def __init__(self):
Masood Malekghassemi2b841622015-07-28 17:39:02 -0700211 self._build_python_versions = ['2.7']
Masood Malekghassemie5f70022015-06-29 09:20:26 -0700212 self._has_python_versions = []
Craig Tiller49f61322015-03-03 13:02:11 -0800213
Nicolas "Pixel" Noble9db7c3b2015-02-27 06:03:00 +0100214 def test_specs(self, config, travis):
Masood Malekghassemi2b841622015-07-28 17:39:02 -0700215 environment = dict(_FORCE_ENVIRON_FOR_WRAPPERS)
216 environment['PYVER'] = '2.7'
217 return [config.job_spec(
218 ['tools/run_tests/run_python.sh'],
219 None,
220 environ=environment,
221 shortname='py.test',
222 )]
Nathaniel Manista840615e2015-01-22 20:31:47 +0000223
224 def make_targets(self):
Craig Tilleraf7cf542015-05-22 10:07:34 -0700225 return ['static_c', 'grpc_python_plugin', 'shared_c']
Nathaniel Manista840615e2015-01-22 20:31:47 +0000226
227 def build_steps(self):
Masood Malekghassemie5f70022015-06-29 09:20:26 -0700228 commands = []
229 for python_version in self._build_python_versions:
230 try:
231 with open(os.devnull, 'w') as output:
232 subprocess.check_call(['which', 'python' + python_version],
233 stdout=output, stderr=output)
234 commands.append(['tools/run_tests/build_python.sh', python_version])
235 self._has_python_versions.append(python_version)
236 except:
237 jobset.message('WARNING', 'Missing Python ' + python_version,
238 do_newline=True)
239 return commands
Nathaniel Manista840615e2015-01-22 20:31:47 +0000240
murgatroid99132ce6a2015-03-04 17:29:14 -0800241 def supports_multi_config(self):
242 return False
243
244 def __str__(self):
245 return 'python'
246
Craig Tillerd625d812015-04-08 15:52:35 -0700247
murgatroid996a4c4fa2015-02-27 12:08:57 -0800248class RubyLanguage(object):
249
250 def test_specs(self, config, travis):
Craig Tiller4fc90032015-05-21 10:39:52 -0700251 return [config.job_spec(['tools/run_tests/run_ruby.sh'], None,
Craig Tiller06805272015-06-11 14:46:47 -0700252 environ=_FORCE_ENVIRON_FOR_WRAPPERS)]
murgatroid996a4c4fa2015-02-27 12:08:57 -0800253
254 def make_targets(self):
murgatroid99a43c14f2015-07-30 13:31:23 -0700255 return ['static_c']
murgatroid996a4c4fa2015-02-27 12:08:57 -0800256
257 def build_steps(self):
258 return [['tools/run_tests/build_ruby.sh']]
259
murgatroid99132ce6a2015-03-04 17:29:14 -0800260 def supports_multi_config(self):
261 return False
262
263 def __str__(self):
264 return 'ruby'
265
Craig Tillerd625d812015-04-08 15:52:35 -0700266
Jan Tattermusch1970a5b2015-03-03 15:17:25 -0800267class CSharpLanguage(object):
Jan Tattermuschb00aa672015-06-01 15:48:03 -0700268 def __init__(self):
Craig Tillerd50993d2015-08-05 08:04:36 -0700269 self.platform = platform_string()
Jan Tattermuschb00aa672015-06-01 15:48:03 -0700270
Jan Tattermusch1970a5b2015-03-03 15:17:25 -0800271 def test_specs(self, config, travis):
Jan Tattermusch9a7d30c2015-04-23 16:12:55 -0700272 assemblies = ['Grpc.Core.Tests',
273 'Grpc.Examples.Tests',
Jan Tattermusch9d67d8d2015-08-01 20:39:16 -0700274 'Grpc.HealthCheck.Tests',
Jan Tattermusch9a7d30c2015-04-23 16:12:55 -0700275 'Grpc.IntegrationTesting']
Jan Tattermuschb00aa672015-06-01 15:48:03 -0700276 if self.platform == 'windows':
277 cmd = 'tools\\run_tests\\run_csharp.bat'
278 else:
279 cmd = 'tools/run_tests/run_csharp.sh'
280 return [config.job_spec([cmd, assembly],
Craig Tiller4fc90032015-05-21 10:39:52 -0700281 None, shortname=assembly,
Craig Tiller06805272015-06-11 14:46:47 -0700282 environ=_FORCE_ENVIRON_FOR_WRAPPERS)
Craig Tillerd50993d2015-08-05 08:04:36 -0700283 for assembly in assemblies]
Jan Tattermusch1970a5b2015-03-03 15:17:25 -0800284
285 def make_targets(self):
Jan Tattermuschb00aa672015-06-01 15:48:03 -0700286 # For Windows, this target doesn't really build anything,
287 # everything is build by buildall script later.
Craig Tillerd5904822015-08-31 21:30:58 -0700288 if self.platform == 'windows':
289 return []
290 else:
291 return ['grpc_csharp_ext']
Jan Tattermusch1970a5b2015-03-03 15:17:25 -0800292
293 def build_steps(self):
Jan Tattermuschb00aa672015-06-01 15:48:03 -0700294 if self.platform == 'windows':
295 return [['src\\csharp\\buildall.bat']]
296 else:
297 return [['tools/run_tests/build_csharp.sh']]
Nathaniel Manista840615e2015-01-22 20:31:47 +0000298
murgatroid99132ce6a2015-03-04 17:29:14 -0800299 def supports_multi_config(self):
300 return False
301
302 def __str__(self):
303 return 'csharp'
304
Craig Tillerd625d812015-04-08 15:52:35 -0700305
Jorge Canizalesa0b3bfa2015-07-30 19:25:52 -0700306class ObjCLanguage(object):
307
308 def test_specs(self, config, travis):
309 return [config.job_spec(['src/objective-c/tests/run_tests.sh'], None,
310 environ=_FORCE_ENVIRON_FOR_WRAPPERS)]
311
312 def make_targets(self):
Jorge Canizalesd0b32e92015-07-30 23:08:43 -0700313 return ['grpc_objective_c_plugin', 'interop_server']
Jorge Canizalesa0b3bfa2015-07-30 19:25:52 -0700314
315 def build_steps(self):
Jorge Canizalesd0b32e92015-07-30 23:08:43 -0700316 return [['src/objective-c/tests/build_tests.sh']]
Jorge Canizalesa0b3bfa2015-07-30 19:25:52 -0700317
318 def supports_multi_config(self):
319 return False
320
321 def __str__(self):
322 return 'objc'
323
324
Nicolas "Pixel" Noble9f728642015-03-24 18:50:30 +0100325class Sanity(object):
326
327 def test_specs(self, config, travis):
Craig Tillerf75fc122015-06-25 06:58:00 -0700328 return [config.job_spec('tools/run_tests/run_sanity.sh', None),
329 config.job_spec('tools/run_tests/check_sources_and_headers.py', None)]
Nicolas "Pixel" Noble9f728642015-03-24 18:50:30 +0100330
331 def make_targets(self):
332 return ['run_dep_checks']
333
334 def build_steps(self):
335 return []
336
337 def supports_multi_config(self):
338 return False
339
340 def __str__(self):
341 return 'sanity'
342
Nicolas "Pixel" Noblee55cd7f2015-04-14 17:59:13 +0200343
Nicolas "Pixel" Noblefd2b0932015-03-26 00:26:29 +0100344class Build(object):
345
346 def test_specs(self, config, travis):
347 return []
348
349 def make_targets(self):
Nicolas "Pixel" Noblec23827b2015-04-23 06:17:55 +0200350 return ['static']
Nicolas "Pixel" Noblefd2b0932015-03-26 00:26:29 +0100351
352 def build_steps(self):
353 return []
354
355 def supports_multi_config(self):
356 return True
357
358 def __str__(self):
359 return self.make_target
360
361
Craig Tiller738c3342015-01-12 14:28:33 -0800362# different configurations we can run under
363_CONFIGS = {
Craig Tillerb50d1662015-01-15 17:28:21 -0800364 'dbg': SimpleConfig('dbg'),
365 'opt': SimpleConfig('opt'),
Craig Tillerb2ea0b92015-08-26 13:06:53 -0700366 'tsan': SimpleConfig('tsan', timeout_seconds=10*60, environ={
Craig Tiller1ada6ad2015-07-16 16:19:14 -0700367 'TSAN_OPTIONS': 'suppressions=tools/tsan_suppressions.txt:halt_on_error=1:second_deadlock_stack=1'}),
Craig Tiller4a71ce22015-08-26 15:47:55 -0700368 'msan': SimpleConfig('msan', timeout_seconds=7*60),
Craig Tiller96bd5f62015-02-13 09:04:13 -0800369 'ubsan': SimpleConfig('ubsan'),
Craig Tillerb2ea0b92015-08-26 13:06:53 -0700370 'asan': SimpleConfig('asan', timeout_seconds=7*60, environ={
Craig Tillerd4b13622015-05-29 09:10:10 -0700371 'ASAN_OPTIONS': 'detect_leaks=1:color=always:suppressions=tools/tsan_suppressions.txt',
372 'LSAN_OPTIONS': 'report_objects=1'}),
Craig Tiller810725c2015-05-12 09:44:41 -0700373 'asan-noleaks': SimpleConfig('asan', environ={
374 'ASAN_OPTIONS': 'detect_leaks=0:color=always:suppressions=tools/tsan_suppressions.txt'}),
Craig Tillerb50d1662015-01-15 17:28:21 -0800375 'gcov': SimpleConfig('gcov'),
Craig Tiller1a305b12015-02-18 13:37:06 -0800376 'memcheck': ValgrindConfig('valgrind', 'memcheck', ['--leak-check=full']),
Craig Tillerb50d1662015-01-15 17:28:21 -0800377 'helgrind': ValgrindConfig('dbg', 'helgrind')
378 }
Craig Tiller738c3342015-01-12 14:28:33 -0800379
380
Nicolas "Pixel" Noble1fb5e822015-03-16 06:20:37 +0100381_DEFAULT = ['opt']
Craig Tillerc7449162015-01-16 14:42:10 -0800382_LANGUAGES = {
Craig Tillere9c959d2015-01-18 10:23:26 -0800383 'c++': CLanguage('cxx', 'c++'),
384 'c': CLanguage('c', 'c'),
murgatroid992c8d5162015-01-26 10:41:21 -0800385 'node': NodeLanguage(),
Nathaniel Manista840615e2015-01-22 20:31:47 +0000386 'php': PhpLanguage(),
387 'python': PythonLanguage(),
Jan Tattermusch1970a5b2015-03-03 15:17:25 -0800388 'ruby': RubyLanguage(),
Nicolas "Pixel" Noble9f728642015-03-24 18:50:30 +0100389 'csharp': CSharpLanguage(),
Jorge Canizalesa0b3bfa2015-07-30 19:25:52 -0700390 'objc' : ObjCLanguage(),
Nicolas "Pixel" Noble9f728642015-03-24 18:50:30 +0100391 'sanity': Sanity(),
Nicolas "Pixel" Noblefd2b0932015-03-26 00:26:29 +0100392 'build': Build(),
Craig Tillereb272bc2015-01-30 13:13:14 -0800393 }
Nicolas Nobleddef2462015-01-06 18:08:25 -0800394
Craig Tiller7bb3efd2015-09-01 08:04:03 -0700395_WINDOWS_CONFIG = {
396 'dbg': 'Debug',
397 'opt': 'Release',
398 }
399
David Garcia Quintase90cd372015-05-31 18:15:26 -0700400
401def runs_per_test_type(arg_str):
402 """Auxilary function to parse the "runs_per_test" flag.
403
404 Returns:
405 A positive integer or 0, the latter indicating an infinite number of
406 runs.
407
408 Raises:
409 argparse.ArgumentTypeError: Upon invalid input.
410 """
411 if arg_str == 'inf':
412 return 0
413 try:
414 n = int(arg_str)
415 if n <= 0: raise ValueError
Craig Tiller50e53e22015-06-01 20:18:21 -0700416 return n
David Garcia Quintase90cd372015-05-31 18:15:26 -0700417 except:
418 msg = "'{}' isn't a positive integer or 'inf'".format(arg_str)
419 raise argparse.ArgumentTypeError(msg)
Jan Tattermuschc95eead2015-09-18 13:03:50 -0700420
421# parse command line
422argp = argparse.ArgumentParser(description='Run grpc tests.')
423argp.add_argument('-c', '--config',
424 choices=['all'] + sorted(_CONFIGS.keys()),
425 nargs='+',
426 default=_DEFAULT)
David Garcia Quintase90cd372015-05-31 18:15:26 -0700427argp.add_argument('-n', '--runs_per_test', default=1, type=runs_per_test_type,
428 help='A positive integer or "inf". If "inf", all tests will run in an '
429 'infinite loop. Especially useful in combination with "-f"')
Craig Tillerfe406ec2015-02-24 13:55:12 -0800430argp.add_argument('-r', '--regex', default='.*', type=str)
Craig Tiller83762ac2015-05-22 14:04:06 -0700431argp.add_argument('-j', '--jobs', default=2 * multiprocessing.cpu_count(), type=int)
Craig Tiller8451e872015-02-27 09:25:51 -0800432argp.add_argument('-s', '--slowdown', default=1.0, type=float)
ctiller3040cb72015-01-07 12:13:17 -0800433argp.add_argument('-f', '--forever',
434 default=False,
435 action='store_const',
436 const=True)
Nicolas "Pixel" Noblea7df3f92015-02-26 22:07:04 +0100437argp.add_argument('-t', '--travis',
438 default=False,
439 action='store_const',
440 const=True)
Nicolas Noble044db742015-01-14 16:57:24 -0800441argp.add_argument('--newline_on_success',
442 default=False,
443 action='store_const',
444 const=True)
Craig Tiller686fb262015-01-15 07:39:09 -0800445argp.add_argument('-l', '--language',
Craig Tiller60f15e62015-05-13 09:05:17 -0700446 choices=['all'] + sorted(_LANGUAGES.keys()),
Craig Tiller686fb262015-01-15 07:39:09 -0800447 nargs='+',
Craig Tiller60f15e62015-05-13 09:05:17 -0700448 default=['all'])
Craig Tillercd43da82015-05-29 08:41:29 -0700449argp.add_argument('-S', '--stop_on_failure',
450 default=False,
451 action='store_const',
452 const=True)
Jan Tattermuschc95eead2015-09-18 13:03:50 -0700453argp.add_argument('--use_docker',
454 default=False,
455 action='store_const',
456 const=True,
457 help="Run all the tests under docker. That provides " +
458 "additional isolation and prevents the need to installs " +
459 "language specific prerequisites. Only available on Linux.")
Craig Tiller234b6e72015-05-23 10:12:40 -0700460argp.add_argument('-a', '--antagonists', default=0, type=int)
Nicolas "Pixel" Noble5937b5b2015-06-26 02:04:12 +0200461argp.add_argument('-x', '--xml_report', default=None, type=str,
462 help='Generates a JUnit-compatible XML report')
Nicolas Nobleddef2462015-01-06 18:08:25 -0800463args = argp.parse_args()
464
Jan Tattermuschc95eead2015-09-18 13:03:50 -0700465if args.use_docker and not args.travis:
466 print 'Seen --use_docker flag, will run tests under docker.'
467 print
468 print 'IMPORTANT: The changes you are testing need to be locally committed'
469 print 'because only the committed changes in the current branch will be'
470 print 'copied to the docker environment.'
471 time.sleep(5)
472
473 child_argv = [ arg for arg in sys.argv if not arg == '--use_docker' ]
474 run_tests_cmd = 'tools/run_tests/run_tests.py %s' % " ".join(child_argv[1:])
475
476 # TODO(jtattermusch): revisit if we need special handling for arch here
477 # set arch command prefix in case we are working with different arch.
478 arch_env = os.getenv('arch')
479 if arch_env:
480 run_test_cmd = 'arch %s %s' % (arch_env, run_test_cmd)
481
482 env = os.environ.copy()
483 env['RUN_TESTS_COMMAND'] = run_tests_cmd
484 if args.xml_report:
485 env['XML_REPORT'] = args.xml_report
486
487 subprocess.check_call(['tools/jenkins/build_docker_and_run_tests.sh'],
488 shell=True,
489 env=env)
490 sys.exit(0)
491
Nicolas Nobleddef2462015-01-06 18:08:25 -0800492# grab config
Craig Tiller738c3342015-01-12 14:28:33 -0800493run_configs = set(_CONFIGS[cfg]
494 for cfg in itertools.chain.from_iterable(
495 _CONFIGS.iterkeys() if x == 'all' else [x]
496 for x in args.config))
497build_configs = set(cfg.build_config for cfg in run_configs)
Craig Tillerf1973b02015-01-16 12:32:13 -0800498
Craig Tiller06805272015-06-11 14:46:47 -0700499if args.travis:
500 _FORCE_ENVIRON_FOR_WRAPPERS = {'GRPC_TRACE': 'surface,batch'}
501
Craig Tiller60f15e62015-05-13 09:05:17 -0700502languages = set(_LANGUAGES[l]
503 for l in itertools.chain.from_iterable(
504 _LANGUAGES.iterkeys() if x == 'all' else [x]
505 for x in args.language))
murgatroid99132ce6a2015-03-04 17:29:14 -0800506
507if len(build_configs) > 1:
508 for language in languages:
509 if not language.supports_multi_config():
510 print language, 'does not support multiple build configurations'
511 sys.exit(1)
512
Craig Tiller5058c692015-04-08 09:42:04 -0700513if platform.system() == 'Windows':
514 def make_jobspec(cfg, targets):
Craig Tillerfc3c0c42015-09-01 16:47:54 -0700515 extra_args = []
Craig Tillerb5391e12015-09-03 14:35:18 -0700516 # better do parallel compilation
517 extra_args.extend(["/m"])
518 # disable PDB generation: it's broken, and we don't need it during CI
519 extra_args.extend(["/p:GenerateDebugInformation=false", "/p:DebugInformationFormat=None"])
Craig Tiller6fd23842015-09-01 07:36:31 -0700520 return [
Craig Tiller0aeb7aa2015-09-01 12:53:30 -0700521 jobset.JobSpec(['vsprojects\\build.bat',
Craig Tiller6fd23842015-09-01 07:36:31 -0700522 'vsprojects\\%s.sln' % target,
Craig Tillerfc3c0c42015-09-01 16:47:54 -0700523 '/p:Configuration=%s' % _WINDOWS_CONFIG[cfg]] +
524 extra_args,
Craig Tillerdfc3eee2015-09-01 16:32:16 -0700525 shell=True, timeout_seconds=90*60)
Craig Tiller6fd23842015-09-01 07:36:31 -0700526 for target in targets]
Craig Tiller5058c692015-04-08 09:42:04 -0700527else:
528 def make_jobspec(cfg, targets):
Craig Tiller6fd23842015-09-01 07:36:31 -0700529 return [jobset.JobSpec([os.getenv('MAKE', 'make'),
530 '-j', '%d' % (multiprocessing.cpu_count() + 1),
531 'EXTRA_DEFINES=GRPC_TEST_SLOWDOWN_MACHINE_FACTOR=%f' %
532 args.slowdown,
533 'CONFIG=%s' % cfg] + targets,
534 timeout_seconds=30*60)]
Craig Tiller5058c692015-04-08 09:42:04 -0700535
Craig Tillerbd4e3782015-09-01 06:48:55 -0700536make_targets = list(set(itertools.chain.from_iterable(
537 l.make_targets() for l in languages)))
538build_steps = []
539if make_targets:
Craig Tiller6fd23842015-09-01 07:36:31 -0700540 make_commands = itertools.chain.from_iterable(make_jobspec(cfg, make_targets) for cfg in build_configs)
541 build_steps.extend(set(make_commands))
Craig Tiller5058c692015-04-08 09:42:04 -0700542build_steps.extend(set(
murgatroid99132ce6a2015-03-04 17:29:14 -0800543 jobset.JobSpec(cmdline, environ={'CONFIG': cfg})
544 for cfg in build_configs
Craig Tiller547db2b2015-01-30 14:08:39 -0800545 for l in languages
Craig Tiller533b1a22015-05-29 08:41:29 -0700546 for cmdline in l.build_steps()))
Craig Tillerf1973b02015-01-16 12:32:13 -0800547
Nicolas Nobleddef2462015-01-06 18:08:25 -0800548runs_per_test = args.runs_per_test
ctiller3040cb72015-01-07 12:13:17 -0800549forever = args.forever
Nicolas Nobleddef2462015-01-06 18:08:25 -0800550
Nicolas Nobleddef2462015-01-06 18:08:25 -0800551
Craig Tiller71735182015-01-15 17:07:13 -0800552class TestCache(object):
Craig Tillerb50d1662015-01-15 17:28:21 -0800553 """Cache for running tests."""
554
David Klempner25739582015-02-11 15:57:32 -0800555 def __init__(self, use_cache_results):
Craig Tiller71735182015-01-15 17:07:13 -0800556 self._last_successful_run = {}
David Klempner25739582015-02-11 15:57:32 -0800557 self._use_cache_results = use_cache_results
Craig Tiller69cd2372015-06-11 09:38:09 -0700558 self._last_save = time.time()
Craig Tiller71735182015-01-15 17:07:13 -0800559
560 def should_run(self, cmdline, bin_hash):
Craig Tiller71735182015-01-15 17:07:13 -0800561 if cmdline not in self._last_successful_run:
562 return True
563 if self._last_successful_run[cmdline] != bin_hash:
564 return True
David Klempner25739582015-02-11 15:57:32 -0800565 if not self._use_cache_results:
566 return True
Craig Tiller71735182015-01-15 17:07:13 -0800567 return False
568
569 def finished(self, cmdline, bin_hash):
Craig Tiller547db2b2015-01-30 14:08:39 -0800570 self._last_successful_run[cmdline] = bin_hash
Craig Tiller69cd2372015-06-11 09:38:09 -0700571 if time.time() - self._last_save > 1:
572 self.save()
Craig Tiller71735182015-01-15 17:07:13 -0800573
574 def dump(self):
Craig Tillerb50d1662015-01-15 17:28:21 -0800575 return [{'cmdline': k, 'hash': v}
576 for k, v in self._last_successful_run.iteritems()]
Craig Tiller71735182015-01-15 17:07:13 -0800577
578 def parse(self, exdump):
579 self._last_successful_run = dict((o['cmdline'], o['hash']) for o in exdump)
580
581 def save(self):
582 with open('.run_tests_cache', 'w') as f:
Craig Tiller261dd982015-01-16 16:41:45 -0800583 f.write(json.dumps(self.dump()))
Craig Tiller69cd2372015-06-11 09:38:09 -0700584 self._last_save = time.time()
Craig Tiller71735182015-01-15 17:07:13 -0800585
Craig Tiller1cc11db2015-01-15 22:50:50 -0800586 def maybe_load(self):
587 if os.path.exists('.run_tests_cache'):
588 with open('.run_tests_cache') as f:
Craig Tiller261dd982015-01-16 16:41:45 -0800589 self.parse(json.loads(f.read()))
Craig Tiller71735182015-01-15 17:07:13 -0800590
591
Craig Tillerf53d9c82015-08-04 14:19:43 -0700592def _start_port_server(port_server_port):
593 # check if a compatible port server is running
594 # if incompatible (version mismatch) ==> start a new one
595 # if not running ==> start a new one
596 # otherwise, leave it up
597 try:
Craig Tillerabd37fd2015-08-26 07:54:01 -0700598 version = urllib2.urlopen('http://localhost:%d/version' % port_server_port,
599 timeout=1).read()
Craig Tillerf53d9c82015-08-04 14:19:43 -0700600 running = True
601 except Exception:
602 running = False
603 if running:
604 with open('tools/run_tests/port_server.py') as f:
605 current_version = hashlib.sha1(f.read()).hexdigest()
606 running = (version == current_version)
607 if not running:
Craig Tilleref125592015-08-05 07:41:35 -0700608 urllib2.urlopen('http://localhost:%d/quit' % port_server_port).read()
609 time.sleep(1)
Craig Tillerf53d9c82015-08-04 14:19:43 -0700610 if not running:
611 port_log = open('portlog.txt', 'w')
612 port_server = subprocess.Popen(
Craig Tiller9a0c10e2015-08-06 15:47:32 -0700613 ['python', 'tools/run_tests/port_server.py', '-p', '%d' % port_server_port],
Craig Tillerf53d9c82015-08-04 14:19:43 -0700614 stderr=subprocess.STDOUT,
615 stdout=port_log)
Craig Tiller8b5f4dc2015-08-26 08:02:01 -0700616 # ensure port server is up
Craig Tillerabd37fd2015-08-26 07:54:01 -0700617 waits = 0
Craig Tillerf53d9c82015-08-04 14:19:43 -0700618 while True:
Craig Tillerabd37fd2015-08-26 07:54:01 -0700619 if waits > 10:
620 port_server.kill()
621 print "port_server failed to start"
622 sys.exit(1)
Craig Tillerf53d9c82015-08-04 14:19:43 -0700623 try:
Craig Tillerabd37fd2015-08-26 07:54:01 -0700624 urllib2.urlopen('http://localhost:%d/get' % port_server_port,
625 timeout=1).read()
Craig Tillerf53d9c82015-08-04 14:19:43 -0700626 break
627 except urllib2.URLError:
Craig Tillerabd37fd2015-08-26 07:54:01 -0700628 print "waiting for port_server"
Craig Tillerf53d9c82015-08-04 14:19:43 -0700629 time.sleep(0.5)
Craig Tillerabd37fd2015-08-26 07:54:01 -0700630 waits += 1
Craig Tillerf53d9c82015-08-04 14:19:43 -0700631 except:
632 port_server.kill()
633 raise
634
635
636def _build_and_run(
637 check_cancelled, newline_on_success, travis, cache, xml_report=None):
ctiller3040cb72015-01-07 12:13:17 -0800638 """Do one pass of building & running tests."""
murgatroid99666450e2015-01-26 13:03:31 -0800639 # build latest sequentially
Nicolas "Pixel" Noblea7df3f92015-02-26 22:07:04 +0100640 if not jobset.run(build_steps, maxjobs=1,
641 newline_on_success=newline_on_success, travis=travis):
Craig Tillerd86a3942015-01-14 12:48:54 -0800642 return 1
ctiller3040cb72015-01-07 12:13:17 -0800643
Craig Tiller234b6e72015-05-23 10:12:40 -0700644 # start antagonists
David Garcia Quintas79e389f2015-06-02 17:49:42 -0700645 antagonists = [subprocess.Popen(['tools/run_tests/antagonist.py'])
Craig Tiller234b6e72015-05-23 10:12:40 -0700646 for _ in range(0, args.antagonists)]
Craig Tillerf53d9c82015-08-04 14:19:43 -0700647 port_server_port = 9999
648 _start_port_server(port_server_port)
Craig Tiller234b6e72015-05-23 10:12:40 -0700649 try:
David Garcia Quintase90cd372015-05-31 18:15:26 -0700650 infinite_runs = runs_per_test == 0
yang-g6c1fdc62015-08-18 11:57:42 -0700651 one_run = set(
652 spec
653 for config in run_configs
654 for language in languages
655 for spec in language.test_specs(config, args.travis)
656 if re.search(args.regex, spec.shortname))
David Garcia Quintas79e389f2015-06-02 17:49:42 -0700657 # When running on travis, we want out test runs to be as similar as possible
658 # for reproducibility purposes.
659 if travis:
660 massaged_one_run = sorted(one_run, key=lambda x: x.shortname)
661 else:
662 # whereas otherwise, we want to shuffle things up to give all tests a
663 # chance to run.
664 massaged_one_run = list(one_run) # random.shuffle needs an indexable seq.
665 random.shuffle(massaged_one_run) # which it modifies in-place.
Craig Tillerf7b7c892015-06-22 14:33:25 -0700666 if infinite_runs:
667 assert len(massaged_one_run) > 0, 'Must have at least one test for a -n inf run'
David Garcia Quintas79e389f2015-06-02 17:49:42 -0700668 runs_sequence = (itertools.repeat(massaged_one_run) if infinite_runs
669 else itertools.repeat(massaged_one_run, runs_per_test))
David Garcia Quintase90cd372015-05-31 18:15:26 -0700670 all_runs = itertools.chain.from_iterable(runs_sequence)
Nicolas "Pixel" Noble5937b5b2015-06-26 02:04:12 +0200671
672 root = ET.Element('testsuites') if xml_report else None
673 testsuite = ET.SubElement(root, 'testsuite', id='1', package='grpc', name='tests') if xml_report else None
674
Craig Tiller234b6e72015-05-23 10:12:40 -0700675 if not jobset.run(all_runs, check_cancelled,
676 newline_on_success=newline_on_success, travis=travis,
David Garcia Quintase90cd372015-05-31 18:15:26 -0700677 infinite_runs=infinite_runs,
Craig Tillerda2220a2015-05-27 07:50:53 -0700678 maxjobs=args.jobs,
Craig Tillercd43da82015-05-29 08:41:29 -0700679 stop_on_failure=args.stop_on_failure,
Nicolas "Pixel" Noble5937b5b2015-06-26 02:04:12 +0200680 cache=cache if not xml_report else None,
Craig Tillerf53d9c82015-08-04 14:19:43 -0700681 xml_report=testsuite,
682 add_env={'GRPC_TEST_PORT_SERVER': 'localhost:%d' % port_server_port}):
Craig Tiller234b6e72015-05-23 10:12:40 -0700683 return 2
684 finally:
685 for antagonist in antagonists:
686 antagonist.kill()
Nicolas "Pixel" Noble5937b5b2015-06-26 02:04:12 +0200687 if xml_report:
688 tree = ET.ElementTree(root)
689 tree.write(xml_report, encoding='UTF-8')
Craig Tillerd86a3942015-01-14 12:48:54 -0800690
Craig Tiller69cd2372015-06-11 09:38:09 -0700691 if cache: cache.save()
692
Craig Tillerd86a3942015-01-14 12:48:54 -0800693 return 0
ctiller3040cb72015-01-07 12:13:17 -0800694
695
David Klempner25739582015-02-11 15:57:32 -0800696test_cache = TestCache(runs_per_test == 1)
Craig Tiller547db2b2015-01-30 14:08:39 -0800697test_cache.maybe_load()
Craig Tiller71735182015-01-15 17:07:13 -0800698
ctiller3040cb72015-01-07 12:13:17 -0800699if forever:
Nicolas Noble044db742015-01-14 16:57:24 -0800700 success = True
ctiller3040cb72015-01-07 12:13:17 -0800701 while True:
Craig Tiller42bc87c2015-02-23 08:50:19 -0800702 dw = watch_dirs.DirWatcher(['src', 'include', 'test', 'examples'])
ctiller3040cb72015-01-07 12:13:17 -0800703 initial_time = dw.most_recent_change()
704 have_files_changed = lambda: dw.most_recent_change() != initial_time
Nicolas Noble044db742015-01-14 16:57:24 -0800705 previous_success = success
Craig Tiller71735182015-01-15 17:07:13 -0800706 success = _build_and_run(check_cancelled=have_files_changed,
Nicolas Nobleb09078f2015-01-14 18:06:05 -0800707 newline_on_success=False,
Craig Tiller9a5a9402015-04-16 10:39:50 -0700708 travis=args.travis,
Craig Tiller71735182015-01-15 17:07:13 -0800709 cache=test_cache) == 0
Nicolas Noble044db742015-01-14 16:57:24 -0800710 if not previous_success and success:
Nicolas Nobleb09078f2015-01-14 18:06:05 -0800711 jobset.message('SUCCESS',
712 'All tests are now passing properly',
713 do_newline=True)
Nicolas Noble044db742015-01-14 16:57:24 -0800714 jobset.message('IDLE', 'No change detected')
ctiller3040cb72015-01-07 12:13:17 -0800715 while not have_files_changed():
716 time.sleep(1)
717else:
Craig Tiller71735182015-01-15 17:07:13 -0800718 result = _build_and_run(check_cancelled=lambda: False,
719 newline_on_success=args.newline_on_success,
Nicolas "Pixel" Noblea7df3f92015-02-26 22:07:04 +0100720 travis=args.travis,
Nicolas "Pixel" Noble5937b5b2015-06-26 02:04:12 +0200721 cache=test_cache,
722 xml_report=args.xml_report)
Nicolas Nobleb09078f2015-01-14 18:06:05 -0800723 if result == 0:
724 jobset.message('SUCCESS', 'All tests passed', do_newline=True)
725 else:
726 jobset.message('FAILED', 'Some tests failed', do_newline=True)
727 sys.exit(result)