blob: f7e9805393d217f80c6b67f064a47cd7ad8e13e0 [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 Tattermuschc96b9eb2015-09-18 16:01:21 -0700465if args.use_docker:
466 if not args.travis:
467 print 'Seen --use_docker flag, will run tests under docker.'
468 print
469 print 'IMPORTANT: The changes you are testing need to be locally committed'
470 print 'because only the committed changes in the current branch will be'
471 print 'copied to the docker environment.'
472 time.sleep(5)
Jan Tattermuschc95eead2015-09-18 13:03:50 -0700473
474 child_argv = [ arg for arg in sys.argv if not arg == '--use_docker' ]
475 run_tests_cmd = 'tools/run_tests/run_tests.py %s' % " ".join(child_argv[1:])
476
477 # TODO(jtattermusch): revisit if we need special handling for arch here
478 # set arch command prefix in case we are working with different arch.
479 arch_env = os.getenv('arch')
480 if arch_env:
481 run_test_cmd = 'arch %s %s' % (arch_env, run_test_cmd)
482
483 env = os.environ.copy()
484 env['RUN_TESTS_COMMAND'] = run_tests_cmd
485 if args.xml_report:
486 env['XML_REPORT'] = args.xml_report
Jan Tattermusch261b58c2015-09-18 17:15:48 -0700487 if not args.travis:
488 env['TTY_FLAG'] = '-t' # enables Ctrl-C when not on Jenkins.
Jan Tattermuschc95eead2015-09-18 13:03:50 -0700489
490 subprocess.check_call(['tools/jenkins/build_docker_and_run_tests.sh'],
491 shell=True,
492 env=env)
493 sys.exit(0)
494
Nicolas Nobleddef2462015-01-06 18:08:25 -0800495# grab config
Craig Tiller738c3342015-01-12 14:28:33 -0800496run_configs = set(_CONFIGS[cfg]
497 for cfg in itertools.chain.from_iterable(
498 _CONFIGS.iterkeys() if x == 'all' else [x]
499 for x in args.config))
500build_configs = set(cfg.build_config for cfg in run_configs)
Craig Tillerf1973b02015-01-16 12:32:13 -0800501
Craig Tiller06805272015-06-11 14:46:47 -0700502if args.travis:
503 _FORCE_ENVIRON_FOR_WRAPPERS = {'GRPC_TRACE': 'surface,batch'}
504
Craig Tiller60f15e62015-05-13 09:05:17 -0700505languages = set(_LANGUAGES[l]
506 for l in itertools.chain.from_iterable(
507 _LANGUAGES.iterkeys() if x == 'all' else [x]
508 for x in args.language))
murgatroid99132ce6a2015-03-04 17:29:14 -0800509
510if len(build_configs) > 1:
511 for language in languages:
512 if not language.supports_multi_config():
513 print language, 'does not support multiple build configurations'
514 sys.exit(1)
515
Craig Tiller5058c692015-04-08 09:42:04 -0700516if platform.system() == 'Windows':
517 def make_jobspec(cfg, targets):
Craig Tillerfc3c0c42015-09-01 16:47:54 -0700518 extra_args = []
Craig Tillerb5391e12015-09-03 14:35:18 -0700519 # better do parallel compilation
520 extra_args.extend(["/m"])
521 # disable PDB generation: it's broken, and we don't need it during CI
522 extra_args.extend(["/p:GenerateDebugInformation=false", "/p:DebugInformationFormat=None"])
Craig Tiller6fd23842015-09-01 07:36:31 -0700523 return [
Craig Tiller0aeb7aa2015-09-01 12:53:30 -0700524 jobset.JobSpec(['vsprojects\\build.bat',
Craig Tiller6fd23842015-09-01 07:36:31 -0700525 'vsprojects\\%s.sln' % target,
Craig Tillerfc3c0c42015-09-01 16:47:54 -0700526 '/p:Configuration=%s' % _WINDOWS_CONFIG[cfg]] +
527 extra_args,
Craig Tillerdfc3eee2015-09-01 16:32:16 -0700528 shell=True, timeout_seconds=90*60)
Craig Tiller6fd23842015-09-01 07:36:31 -0700529 for target in targets]
Craig Tiller5058c692015-04-08 09:42:04 -0700530else:
531 def make_jobspec(cfg, targets):
Craig Tiller6fd23842015-09-01 07:36:31 -0700532 return [jobset.JobSpec([os.getenv('MAKE', 'make'),
533 '-j', '%d' % (multiprocessing.cpu_count() + 1),
534 'EXTRA_DEFINES=GRPC_TEST_SLOWDOWN_MACHINE_FACTOR=%f' %
535 args.slowdown,
536 'CONFIG=%s' % cfg] + targets,
537 timeout_seconds=30*60)]
Craig Tiller5058c692015-04-08 09:42:04 -0700538
Craig Tillerbd4e3782015-09-01 06:48:55 -0700539make_targets = list(set(itertools.chain.from_iterable(
540 l.make_targets() for l in languages)))
541build_steps = []
542if make_targets:
Craig Tiller6fd23842015-09-01 07:36:31 -0700543 make_commands = itertools.chain.from_iterable(make_jobspec(cfg, make_targets) for cfg in build_configs)
544 build_steps.extend(set(make_commands))
Craig Tiller5058c692015-04-08 09:42:04 -0700545build_steps.extend(set(
murgatroid99132ce6a2015-03-04 17:29:14 -0800546 jobset.JobSpec(cmdline, environ={'CONFIG': cfg})
547 for cfg in build_configs
Craig Tiller547db2b2015-01-30 14:08:39 -0800548 for l in languages
Craig Tiller533b1a22015-05-29 08:41:29 -0700549 for cmdline in l.build_steps()))
Craig Tillerf1973b02015-01-16 12:32:13 -0800550
Nicolas Nobleddef2462015-01-06 18:08:25 -0800551runs_per_test = args.runs_per_test
ctiller3040cb72015-01-07 12:13:17 -0800552forever = args.forever
Nicolas Nobleddef2462015-01-06 18:08:25 -0800553
Nicolas Nobleddef2462015-01-06 18:08:25 -0800554
Craig Tiller71735182015-01-15 17:07:13 -0800555class TestCache(object):
Craig Tillerb50d1662015-01-15 17:28:21 -0800556 """Cache for running tests."""
557
David Klempner25739582015-02-11 15:57:32 -0800558 def __init__(self, use_cache_results):
Craig Tiller71735182015-01-15 17:07:13 -0800559 self._last_successful_run = {}
David Klempner25739582015-02-11 15:57:32 -0800560 self._use_cache_results = use_cache_results
Craig Tiller69cd2372015-06-11 09:38:09 -0700561 self._last_save = time.time()
Craig Tiller71735182015-01-15 17:07:13 -0800562
563 def should_run(self, cmdline, bin_hash):
Craig Tiller71735182015-01-15 17:07:13 -0800564 if cmdline not in self._last_successful_run:
565 return True
566 if self._last_successful_run[cmdline] != bin_hash:
567 return True
David Klempner25739582015-02-11 15:57:32 -0800568 if not self._use_cache_results:
569 return True
Craig Tiller71735182015-01-15 17:07:13 -0800570 return False
571
572 def finished(self, cmdline, bin_hash):
Craig Tiller547db2b2015-01-30 14:08:39 -0800573 self._last_successful_run[cmdline] = bin_hash
Craig Tiller69cd2372015-06-11 09:38:09 -0700574 if time.time() - self._last_save > 1:
575 self.save()
Craig Tiller71735182015-01-15 17:07:13 -0800576
577 def dump(self):
Craig Tillerb50d1662015-01-15 17:28:21 -0800578 return [{'cmdline': k, 'hash': v}
579 for k, v in self._last_successful_run.iteritems()]
Craig Tiller71735182015-01-15 17:07:13 -0800580
581 def parse(self, exdump):
582 self._last_successful_run = dict((o['cmdline'], o['hash']) for o in exdump)
583
584 def save(self):
585 with open('.run_tests_cache', 'w') as f:
Craig Tiller261dd982015-01-16 16:41:45 -0800586 f.write(json.dumps(self.dump()))
Craig Tiller69cd2372015-06-11 09:38:09 -0700587 self._last_save = time.time()
Craig Tiller71735182015-01-15 17:07:13 -0800588
Craig Tiller1cc11db2015-01-15 22:50:50 -0800589 def maybe_load(self):
590 if os.path.exists('.run_tests_cache'):
591 with open('.run_tests_cache') as f:
Craig Tiller261dd982015-01-16 16:41:45 -0800592 self.parse(json.loads(f.read()))
Craig Tiller71735182015-01-15 17:07:13 -0800593
594
Craig Tillerf53d9c82015-08-04 14:19:43 -0700595def _start_port_server(port_server_port):
596 # check if a compatible port server is running
597 # if incompatible (version mismatch) ==> start a new one
598 # if not running ==> start a new one
599 # otherwise, leave it up
600 try:
Craig Tillerabd37fd2015-08-26 07:54:01 -0700601 version = urllib2.urlopen('http://localhost:%d/version' % port_server_port,
602 timeout=1).read()
Craig Tillerf53d9c82015-08-04 14:19:43 -0700603 running = True
604 except Exception:
605 running = False
606 if running:
607 with open('tools/run_tests/port_server.py') as f:
608 current_version = hashlib.sha1(f.read()).hexdigest()
609 running = (version == current_version)
610 if not running:
Craig Tilleref125592015-08-05 07:41:35 -0700611 urllib2.urlopen('http://localhost:%d/quit' % port_server_port).read()
612 time.sleep(1)
Craig Tillerf53d9c82015-08-04 14:19:43 -0700613 if not running:
614 port_log = open('portlog.txt', 'w')
615 port_server = subprocess.Popen(
Craig Tiller9a0c10e2015-08-06 15:47:32 -0700616 ['python', 'tools/run_tests/port_server.py', '-p', '%d' % port_server_port],
Craig Tillerf53d9c82015-08-04 14:19:43 -0700617 stderr=subprocess.STDOUT,
618 stdout=port_log)
Craig Tiller8b5f4dc2015-08-26 08:02:01 -0700619 # ensure port server is up
Craig Tillerabd37fd2015-08-26 07:54:01 -0700620 waits = 0
Craig Tillerf53d9c82015-08-04 14:19:43 -0700621 while True:
Craig Tillerabd37fd2015-08-26 07:54:01 -0700622 if waits > 10:
623 port_server.kill()
624 print "port_server failed to start"
625 sys.exit(1)
Craig Tillerf53d9c82015-08-04 14:19:43 -0700626 try:
Craig Tillerabd37fd2015-08-26 07:54:01 -0700627 urllib2.urlopen('http://localhost:%d/get' % port_server_port,
628 timeout=1).read()
Craig Tillerf53d9c82015-08-04 14:19:43 -0700629 break
630 except urllib2.URLError:
Craig Tillerabd37fd2015-08-26 07:54:01 -0700631 print "waiting for port_server"
Craig Tillerf53d9c82015-08-04 14:19:43 -0700632 time.sleep(0.5)
Craig Tillerabd37fd2015-08-26 07:54:01 -0700633 waits += 1
Craig Tillerf53d9c82015-08-04 14:19:43 -0700634 except:
635 port_server.kill()
636 raise
637
638
639def _build_and_run(
640 check_cancelled, newline_on_success, travis, cache, xml_report=None):
ctiller3040cb72015-01-07 12:13:17 -0800641 """Do one pass of building & running tests."""
murgatroid99666450e2015-01-26 13:03:31 -0800642 # build latest sequentially
Nicolas "Pixel" Noblea7df3f92015-02-26 22:07:04 +0100643 if not jobset.run(build_steps, maxjobs=1,
644 newline_on_success=newline_on_success, travis=travis):
Craig Tillerd86a3942015-01-14 12:48:54 -0800645 return 1
ctiller3040cb72015-01-07 12:13:17 -0800646
Craig Tiller234b6e72015-05-23 10:12:40 -0700647 # start antagonists
David Garcia Quintas79e389f2015-06-02 17:49:42 -0700648 antagonists = [subprocess.Popen(['tools/run_tests/antagonist.py'])
Craig Tiller234b6e72015-05-23 10:12:40 -0700649 for _ in range(0, args.antagonists)]
Craig Tillerf53d9c82015-08-04 14:19:43 -0700650 port_server_port = 9999
651 _start_port_server(port_server_port)
Craig Tiller234b6e72015-05-23 10:12:40 -0700652 try:
David Garcia Quintase90cd372015-05-31 18:15:26 -0700653 infinite_runs = runs_per_test == 0
yang-g6c1fdc62015-08-18 11:57:42 -0700654 one_run = set(
655 spec
656 for config in run_configs
657 for language in languages
658 for spec in language.test_specs(config, args.travis)
659 if re.search(args.regex, spec.shortname))
David Garcia Quintas79e389f2015-06-02 17:49:42 -0700660 # When running on travis, we want out test runs to be as similar as possible
661 # for reproducibility purposes.
662 if travis:
663 massaged_one_run = sorted(one_run, key=lambda x: x.shortname)
664 else:
665 # whereas otherwise, we want to shuffle things up to give all tests a
666 # chance to run.
667 massaged_one_run = list(one_run) # random.shuffle needs an indexable seq.
668 random.shuffle(massaged_one_run) # which it modifies in-place.
Craig Tillerf7b7c892015-06-22 14:33:25 -0700669 if infinite_runs:
670 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 -0700671 runs_sequence = (itertools.repeat(massaged_one_run) if infinite_runs
672 else itertools.repeat(massaged_one_run, runs_per_test))
David Garcia Quintase90cd372015-05-31 18:15:26 -0700673 all_runs = itertools.chain.from_iterable(runs_sequence)
Nicolas "Pixel" Noble5937b5b2015-06-26 02:04:12 +0200674
675 root = ET.Element('testsuites') if xml_report else None
676 testsuite = ET.SubElement(root, 'testsuite', id='1', package='grpc', name='tests') if xml_report else None
677
Craig Tiller234b6e72015-05-23 10:12:40 -0700678 if not jobset.run(all_runs, check_cancelled,
679 newline_on_success=newline_on_success, travis=travis,
David Garcia Quintase90cd372015-05-31 18:15:26 -0700680 infinite_runs=infinite_runs,
Craig Tillerda2220a2015-05-27 07:50:53 -0700681 maxjobs=args.jobs,
Craig Tillercd43da82015-05-29 08:41:29 -0700682 stop_on_failure=args.stop_on_failure,
Nicolas "Pixel" Noble5937b5b2015-06-26 02:04:12 +0200683 cache=cache if not xml_report else None,
Craig Tillerf53d9c82015-08-04 14:19:43 -0700684 xml_report=testsuite,
685 add_env={'GRPC_TEST_PORT_SERVER': 'localhost:%d' % port_server_port}):
Craig Tiller234b6e72015-05-23 10:12:40 -0700686 return 2
687 finally:
688 for antagonist in antagonists:
689 antagonist.kill()
Nicolas "Pixel" Noble5937b5b2015-06-26 02:04:12 +0200690 if xml_report:
691 tree = ET.ElementTree(root)
692 tree.write(xml_report, encoding='UTF-8')
Craig Tillerd86a3942015-01-14 12:48:54 -0800693
Craig Tiller69cd2372015-06-11 09:38:09 -0700694 if cache: cache.save()
695
Craig Tillerd86a3942015-01-14 12:48:54 -0800696 return 0
ctiller3040cb72015-01-07 12:13:17 -0800697
698
David Klempner25739582015-02-11 15:57:32 -0800699test_cache = TestCache(runs_per_test == 1)
Craig Tiller547db2b2015-01-30 14:08:39 -0800700test_cache.maybe_load()
Craig Tiller71735182015-01-15 17:07:13 -0800701
ctiller3040cb72015-01-07 12:13:17 -0800702if forever:
Nicolas Noble044db742015-01-14 16:57:24 -0800703 success = True
ctiller3040cb72015-01-07 12:13:17 -0800704 while True:
Craig Tiller42bc87c2015-02-23 08:50:19 -0800705 dw = watch_dirs.DirWatcher(['src', 'include', 'test', 'examples'])
ctiller3040cb72015-01-07 12:13:17 -0800706 initial_time = dw.most_recent_change()
707 have_files_changed = lambda: dw.most_recent_change() != initial_time
Nicolas Noble044db742015-01-14 16:57:24 -0800708 previous_success = success
Craig Tiller71735182015-01-15 17:07:13 -0800709 success = _build_and_run(check_cancelled=have_files_changed,
Nicolas Nobleb09078f2015-01-14 18:06:05 -0800710 newline_on_success=False,
Craig Tiller9a5a9402015-04-16 10:39:50 -0700711 travis=args.travis,
Craig Tiller71735182015-01-15 17:07:13 -0800712 cache=test_cache) == 0
Nicolas Noble044db742015-01-14 16:57:24 -0800713 if not previous_success and success:
Nicolas Nobleb09078f2015-01-14 18:06:05 -0800714 jobset.message('SUCCESS',
715 'All tests are now passing properly',
716 do_newline=True)
Nicolas Noble044db742015-01-14 16:57:24 -0800717 jobset.message('IDLE', 'No change detected')
ctiller3040cb72015-01-07 12:13:17 -0800718 while not have_files_changed():
719 time.sleep(1)
720else:
Craig Tiller71735182015-01-15 17:07:13 -0800721 result = _build_and_run(check_cancelled=lambda: False,
722 newline_on_success=args.newline_on_success,
Nicolas "Pixel" Noblea7df3f92015-02-26 22:07:04 +0100723 travis=args.travis,
Nicolas "Pixel" Noble5937b5b2015-06-26 02:04:12 +0200724 cache=test_cache,
725 xml_report=args.xml_report)
Nicolas Nobleb09078f2015-01-14 18:06:05 -0800726 if result == 0:
727 jobset.message('SUCCESS', 'All tests passed', do_newline=True)
728 else:
729 jobset.message('FAILED', 'Some tests failed', do_newline=True)
730 sys.exit(result)