blob: 488af3b4da70cbc4cbe16b4ccdb4413550f9831d [file] [log] [blame]
Nathaniel Manistaae4fbcd2015-09-23 16:29:44 +00001#!/usr/bin/env python2.7
Craig Tiller40839772016-01-05 12:34:49 -08002# Copyright 2015-2016, Google Inc.
Craig Tillerc2c79212015-02-16 12:00:01 -08003# 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
Craig Tiller9279ac22016-01-20 17:05:23 -080034import ast
Nicolas Nobleddef2462015-01-06 18:08:25 -080035import glob
Craig Tillerf53d9c82015-08-04 14:19:43 -070036import hashlib
Nicolas Nobleddef2462015-01-06 18:08:25 -080037import itertools
Craig Tiller261dd982015-01-16 16:41:45 -080038import json
Nicolas Nobleddef2462015-01-06 18:08:25 -080039import multiprocessing
Craig Tiller1cc11db2015-01-15 22:50:50 -080040import os
David Garcia Quintas79e389f2015-06-02 17:49:42 -070041import platform
42import random
Craig Tillerfe406ec2015-02-24 13:55:12 -080043import re
Craig Tiller82875232015-09-25 13:57:34 -070044import socket
David Garcia Quintas79e389f2015-06-02 17:49:42 -070045import subprocess
Nicolas Nobleddef2462015-01-06 18:08:25 -080046import sys
Craig Tillerf0a293e2015-10-12 10:05:50 -070047import tempfile
48import traceback
ctiller3040cb72015-01-07 12:13:17 -080049import time
Craig Tillerf53d9c82015-08-04 14:19:43 -070050import urllib2
Jan Tattermusch03c01062015-12-11 14:28:56 -080051import uuid
Nicolas Nobleddef2462015-01-06 18:08:25 -080052
53import jobset
Adele Zhoua30f8292015-11-02 13:15:46 -080054import report_utils
ctiller3040cb72015-01-07 12:13:17 -080055import watch_dirs
Nicolas Nobleddef2462015-01-06 18:08:25 -080056
Craig Tillerb361b4e2016-01-06 11:44:17 -080057
Craig Tiller2cc2b842015-02-27 11:38:31 -080058ROOT = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '../..'))
59os.chdir(ROOT)
60
61
Craig Tiller06805272015-06-11 14:46:47 -070062_FORCE_ENVIRON_FOR_WRAPPERS = {}
63
64
Craig Tillerd50993d2015-08-05 08:04:36 -070065def platform_string():
Nicolas "Pixel" Noblef72d7b52015-12-03 03:07:43 +010066 return jobset.platform_string()
Craig Tillerd50993d2015-08-05 08:04:36 -070067
68
Craig Tiller738c3342015-01-12 14:28:33 -080069# SimpleConfig: just compile with CONFIG=config, and run the binary to test
Craig Tillera0f85172016-01-20 15:56:06 -080070class Config(object):
Craig Tillerb50d1662015-01-15 17:28:21 -080071
Craig Tillera0f85172016-01-20 15:56:06 -080072 def __init__(self, config, environ=None, timeout_multiplier=1, tool_prefix=[]):
murgatroid99132ce6a2015-03-04 17:29:14 -080073 if environ is None:
74 environ = {}
Craig Tiller738c3342015-01-12 14:28:33 -080075 self.build_config = config
Craig Tillerc7449162015-01-16 14:42:10 -080076 self.allow_hashing = (config != 'gcov')
Craig Tiller547db2b2015-01-30 14:08:39 -080077 self.environ = environ
murgatroid99132ce6a2015-03-04 17:29:14 -080078 self.environ['CONFIG'] = config
Craig Tillera0f85172016-01-20 15:56:06 -080079 self.tool_prefix = tool_prefix
Masood Malekghassemi26ea9e22015-10-09 15:19:17 -070080 self.timeout_multiplier = timeout_multiplier
Craig Tiller738c3342015-01-12 14:28:33 -080081
Masood Malekghassemi26ea9e22015-10-09 15:19:17 -070082 def job_spec(self, cmdline, hash_targets, timeout_seconds=5*60,
Craig Tiller56c6b6a2016-01-20 08:27:37 -080083 shortname=None, environ={}, cpu_cost=1.0):
Craig Tiller49f61322015-03-03 13:02:11 -080084 """Construct a jobset.JobSpec for a test under this config
85
86 Args:
87 cmdline: a list of strings specifying the command line the test
88 would like to run
89 hash_targets: either None (don't do caching of test results), or
90 a list of strings specifying files to include in a
91 binary hash to check if a test has changed
92 -- if used, all artifacts needed to run the test must
93 be listed
94 """
Craig Tiller4fc90032015-05-21 10:39:52 -070095 actual_environ = self.environ.copy()
96 for k, v in environ.iteritems():
97 actual_environ[k] = v
Craig Tillera0f85172016-01-20 15:56:06 -080098 return jobset.JobSpec(cmdline=self.tool_prefix + cmdline,
Jan Tattermusch9a7d30c2015-04-23 16:12:55 -070099 shortname=shortname,
Craig Tiller4fc90032015-05-21 10:39:52 -0700100 environ=actual_environ,
Craig Tiller56c6b6a2016-01-20 08:27:37 -0800101 cpu_cost=cpu_cost,
Craig Tiller94d04a52016-01-20 10:58:23 -0800102 timeout_seconds=(self.timeout_multiplier * timeout_seconds if timeout_seconds else None),
Craig Tiller547db2b2015-01-30 14:08:39 -0800103 hash_targets=hash_targets
Craig Tillerd4509a12015-09-28 09:18:40 -0700104 if self.allow_hashing else None,
Craig Tiller35505de2015-10-08 13:31:33 -0700105 flake_retries=5 if args.allow_flakes else 0,
106 timeout_retries=3 if args.allow_flakes else 0)
Craig Tiller738c3342015-01-12 14:28:33 -0800107
108
murgatroid99cf08daf2015-09-21 15:33:16 -0700109def get_c_tests(travis, test_lang) :
110 out = []
111 platforms_str = 'ci_platforms' if travis else 'platforms'
112 with open('tools/run_tests/tests.json') as f:
murgatroid9989899b12015-09-22 09:14:48 -0700113 js = json.load(f)
murgatroid99a3e244f2015-09-22 11:25:53 -0700114 return [tgt
115 for tgt in js
116 if tgt['language'] == test_lang and
117 platform_string() in tgt[platforms_str] and
118 not (travis and tgt['flaky'])]
murgatroid99cf08daf2015-09-21 15:33:16 -0700119
murgatroid99fafeeb32015-09-22 09:13:03 -0700120
Craig Tillerc7449162015-01-16 14:42:10 -0800121class CLanguage(object):
122
Craig Tillere9c959d2015-01-18 10:23:26 -0800123 def __init__(self, make_target, test_lang):
Craig Tillerc7449162015-01-16 14:42:10 -0800124 self.make_target = make_target
Craig Tillerd50993d2015-08-05 08:04:36 -0700125 self.platform = platform_string()
Craig Tiller711bbe62015-08-19 12:35:16 -0700126 self.test_lang = test_lang
Craig Tillerc7449162015-01-16 14:42:10 -0800127
Craig Tiller883064c2015-11-04 10:06:10 -0800128 def test_specs(self, config, args):
Craig Tiller547db2b2015-01-30 14:08:39 -0800129 out = []
Craig Tiller883064c2015-11-04 10:06:10 -0800130 binaries = get_c_tests(args.travis, self.test_lang)
Craig Tiller711bbe62015-08-19 12:35:16 -0700131 for target in binaries:
murgatroid99a3e244f2015-09-22 11:25:53 -0700132 if config.build_config in target['exclude_configs']:
murgatroid99fafeeb32015-09-22 09:13:03 -0700133 continue
Nicolas Noblee1445362015-05-11 17:40:26 -0700134 if self.platform == 'windows':
Craig Tillerf4182602015-09-01 12:23:16 -0700135 binary = 'vsprojects/%s/%s.exe' % (
136 _WINDOWS_CONFIG[config.build_config], target['name'])
Nicolas Noblee1445362015-05-11 17:40:26 -0700137 else:
138 binary = 'bins/%s/%s' % (config.build_config, target['name'])
yang-g6c1fdc62015-08-18 11:57:42 -0700139 if os.path.isfile(binary):
Craig Tiller0fe5ee72015-12-22 12:50:36 -0800140 cmdline = [binary] + target['args']
141 out.append(config.job_spec(cmdline, [binary],
142 shortname=' '.join(cmdline),
Craig Tiller56c6b6a2016-01-20 08:27:37 -0800143 cpu_cost=target['cpu_cost'],
Craig Tillercc0535d2015-12-08 15:14:47 -0800144 environ={'GRPC_DEFAULT_SSL_ROOTS_FILE_PATH':
145 os.path.abspath(os.path.dirname(
Craig Tillered2164d2015-12-08 22:03:36 -0800146 sys.argv[0]) + '/../../src/core/tsi/test_creds/ca.pem')}))
Craig Tiller883064c2015-11-04 10:06:10 -0800147 elif args.regex == '.*' or platform_string() == 'windows':
Adele Zhoue4c35612015-10-16 15:34:23 -0700148 print '\nWARNING: binary not found, skipping', binary
Nicolas Noblee1445362015-05-11 17:40:26 -0700149 return sorted(out)
Craig Tillerc7449162015-01-16 14:42:10 -0800150
Craig Tiller883064c2015-11-04 10:06:10 -0800151 def make_targets(self, test_regex):
152 if platform_string() != 'windows' and test_regex != '.*':
153 # use the regex to minimize the number of things to build
154 return [target['name']
155 for target in get_c_tests(False, self.test_lang)
156 if re.search(test_regex, target['name'])]
Craig Tiller7bb3efd2015-09-01 08:04:03 -0700157 if platform_string() == 'windows':
158 # don't build tools on windows just yet
159 return ['buildtests_%s' % self.make_target]
Craig Tiller7552f0f2015-06-19 17:46:20 -0700160 return ['buildtests_%s' % self.make_target, 'tools_%s' % self.make_target]
Craig Tillerc7449162015-01-16 14:42:10 -0800161
Jan Tattermuschc895fe02016-01-20 09:13:09 -0800162 def make_options(self):
163 return []
164
murgatroid99256d3df2015-09-21 16:58:02 -0700165 def pre_build_steps(self):
Jan Tattermusch874aec02015-10-07 19:26:19 -0700166 if self.platform == 'windows':
167 return [['tools\\run_tests\\pre_build_c.bat']]
168 else:
169 return []
murgatroid99256d3df2015-09-21 16:58:02 -0700170
Craig Tillerc7449162015-01-16 14:42:10 -0800171 def build_steps(self):
172 return []
173
Nicolas "Pixel" Noble3fcd3bf2015-10-10 02:30:38 +0200174 def post_tests_steps(self):
175 if self.platform == 'windows':
176 return []
177 else:
178 return [['tools/run_tests/post_tests_c.sh']]
179
murgatroid99a3e244f2015-09-22 11:25:53 -0700180 def makefile_name(self):
181 return 'Makefile'
182
murgatroid99132ce6a2015-03-04 17:29:14 -0800183 def supports_multi_config(self):
184 return True
185
186 def __str__(self):
187 return self.make_target
188
Craig Tillercc0535d2015-12-08 15:14:47 -0800189
murgatroid992c8d5162015-01-26 10:41:21 -0800190class NodeLanguage(object):
191
Craig Tiller883064c2015-11-04 10:06:10 -0800192 def test_specs(self, config, args):
Craig Tiller4fc90032015-05-21 10:39:52 -0700193 return [config.job_spec(['tools/run_tests/run_node.sh'], None,
Craig Tiller06805272015-06-11 14:46:47 -0700194 environ=_FORCE_ENVIRON_FOR_WRAPPERS)]
murgatroid992c8d5162015-01-26 10:41:21 -0800195
murgatroid99256d3df2015-09-21 16:58:02 -0700196 def pre_build_steps(self):
murgatroid99ae369de2015-10-09 15:40:48 -0700197 # Default to 1 week cache expiration
murgatroid9993758952015-10-14 11:51:05 -0700198 return [['tools/run_tests/pre_build_node.sh']]
murgatroid99256d3df2015-09-21 16:58:02 -0700199
Craig Tiller883064c2015-11-04 10:06:10 -0800200 def make_targets(self, test_regex):
murgatroid99db5b1602015-10-01 13:20:11 -0700201 return []
murgatroid992c8d5162015-01-26 10:41:21 -0800202
Jan Tattermuschc895fe02016-01-20 09:13:09 -0800203 def make_options(self):
204 return []
205
murgatroid992c8d5162015-01-26 10:41:21 -0800206 def build_steps(self):
207 return [['tools/run_tests/build_node.sh']]
Craig Tillerc7449162015-01-16 14:42:10 -0800208
Nicolas "Pixel" Noble3fcd3bf2015-10-10 02:30:38 +0200209 def post_tests_steps(self):
210 return []
211
murgatroid99a3e244f2015-09-22 11:25:53 -0700212 def makefile_name(self):
213 return 'Makefile'
214
murgatroid99132ce6a2015-03-04 17:29:14 -0800215 def supports_multi_config(self):
216 return False
217
218 def __str__(self):
219 return 'node'
220
Craig Tiller99775822015-01-30 13:07:16 -0800221
Craig Tillerc7449162015-01-16 14:42:10 -0800222class PhpLanguage(object):
223
Craig Tiller883064c2015-11-04 10:06:10 -0800224 def test_specs(self, config, args):
Craig Tiller4fc90032015-05-21 10:39:52 -0700225 return [config.job_spec(['src/php/bin/run_tests.sh'], None,
Craig Tiller06805272015-06-11 14:46:47 -0700226 environ=_FORCE_ENVIRON_FOR_WRAPPERS)]
Craig Tillerc7449162015-01-16 14:42:10 -0800227
murgatroid99256d3df2015-09-21 16:58:02 -0700228 def pre_build_steps(self):
229 return []
230
Craig Tiller883064c2015-11-04 10:06:10 -0800231 def make_targets(self, test_regex):
Craig Tilleraf7cf542015-05-22 10:07:34 -0700232 return ['static_c', 'shared_c']
Craig Tillerc7449162015-01-16 14:42:10 -0800233
Jan Tattermuschc895fe02016-01-20 09:13:09 -0800234 def make_options(self):
235 return []
236
Craig Tillerc7449162015-01-16 14:42:10 -0800237 def build_steps(self):
238 return [['tools/run_tests/build_php.sh']]
239
Nicolas "Pixel" Noble3fcd3bf2015-10-10 02:30:38 +0200240 def post_tests_steps(self):
Stanley Cheunga6b95482016-01-13 16:10:48 -0800241 return [['tools/run_tests/post_tests_php.sh']]
Nicolas "Pixel" Noble3fcd3bf2015-10-10 02:30:38 +0200242
murgatroid99a3e244f2015-09-22 11:25:53 -0700243 def makefile_name(self):
244 return 'Makefile'
245
murgatroid99132ce6a2015-03-04 17:29:14 -0800246 def supports_multi_config(self):
247 return False
248
249 def __str__(self):
250 return 'php'
251
Craig Tillerc7449162015-01-16 14:42:10 -0800252
Nathaniel Manista840615e2015-01-22 20:31:47 +0000253class PythonLanguage(object):
254
Craig Tiller49f61322015-03-03 13:02:11 -0800255 def __init__(self):
Masood Malekghassemi2b841622015-07-28 17:39:02 -0700256 self._build_python_versions = ['2.7']
Masood Malekghassemie5f70022015-06-29 09:20:26 -0700257 self._has_python_versions = []
Craig Tiller49f61322015-03-03 13:02:11 -0800258
Craig Tiller883064c2015-11-04 10:06:10 -0800259 def test_specs(self, config, args):
Masood Malekghassemi2b841622015-07-28 17:39:02 -0700260 environment = dict(_FORCE_ENVIRON_FOR_WRAPPERS)
261 environment['PYVER'] = '2.7'
262 return [config.job_spec(
263 ['tools/run_tests/run_python.sh'],
264 None,
265 environ=environment,
266 shortname='py.test',
Masood Malekghassemi26ea9e22015-10-09 15:19:17 -0700267 timeout_seconds=15*60
Masood Malekghassemi2b841622015-07-28 17:39:02 -0700268 )]
Nathaniel Manista840615e2015-01-22 20:31:47 +0000269
murgatroid99256d3df2015-09-21 16:58:02 -0700270 def pre_build_steps(self):
271 return []
272
Craig Tiller883064c2015-11-04 10:06:10 -0800273 def make_targets(self, test_regex):
Craig Tilleraf7cf542015-05-22 10:07:34 -0700274 return ['static_c', 'grpc_python_plugin', 'shared_c']
Nathaniel Manista840615e2015-01-22 20:31:47 +0000275
Jan Tattermuschc895fe02016-01-20 09:13:09 -0800276 def make_options(self):
277 return []
278
Nathaniel Manista840615e2015-01-22 20:31:47 +0000279 def build_steps(self):
Masood Malekghassemie5f70022015-06-29 09:20:26 -0700280 commands = []
281 for python_version in self._build_python_versions:
282 try:
283 with open(os.devnull, 'w') as output:
284 subprocess.check_call(['which', 'python' + python_version],
285 stdout=output, stderr=output)
286 commands.append(['tools/run_tests/build_python.sh', python_version])
287 self._has_python_versions.append(python_version)
288 except:
289 jobset.message('WARNING', 'Missing Python ' + python_version,
290 do_newline=True)
291 return commands
Nathaniel Manista840615e2015-01-22 20:31:47 +0000292
Nicolas "Pixel" Noble3fcd3bf2015-10-10 02:30:38 +0200293 def post_tests_steps(self):
294 return []
295
murgatroid99a3e244f2015-09-22 11:25:53 -0700296 def makefile_name(self):
297 return 'Makefile'
298
murgatroid99132ce6a2015-03-04 17:29:14 -0800299 def supports_multi_config(self):
300 return False
301
302 def __str__(self):
303 return 'python'
304
Craig Tillerd625d812015-04-08 15:52:35 -0700305
murgatroid996a4c4fa2015-02-27 12:08:57 -0800306class RubyLanguage(object):
307
Craig Tiller883064c2015-11-04 10:06:10 -0800308 def test_specs(self, config, args):
Craig Tiller4fc90032015-05-21 10:39:52 -0700309 return [config.job_spec(['tools/run_tests/run_ruby.sh'], None,
Craig Tiller06805272015-06-11 14:46:47 -0700310 environ=_FORCE_ENVIRON_FOR_WRAPPERS)]
murgatroid996a4c4fa2015-02-27 12:08:57 -0800311
murgatroid99256d3df2015-09-21 16:58:02 -0700312 def pre_build_steps(self):
Nicolas "Pixel" Noblebcf988f2015-10-08 03:00:42 +0200313 return [['tools/run_tests/pre_build_ruby.sh']]
murgatroid99256d3df2015-09-21 16:58:02 -0700314
Craig Tiller883064c2015-11-04 10:06:10 -0800315 def make_targets(self, test_regex):
murgatroid99a43c14f2015-07-30 13:31:23 -0700316 return ['static_c']
murgatroid996a4c4fa2015-02-27 12:08:57 -0800317
Jan Tattermuschc895fe02016-01-20 09:13:09 -0800318 def make_options(self):
319 return []
320
murgatroid996a4c4fa2015-02-27 12:08:57 -0800321 def build_steps(self):
322 return [['tools/run_tests/build_ruby.sh']]
323
Nicolas "Pixel" Noble3fcd3bf2015-10-10 02:30:38 +0200324 def post_tests_steps(self):
Nicolas "Pixel" Noble7ef1e532015-12-02 00:55:33 +0100325 return [['tools/run_tests/post_tests_ruby.sh']]
Nicolas "Pixel" Noble3fcd3bf2015-10-10 02:30:38 +0200326
murgatroid99a3e244f2015-09-22 11:25:53 -0700327 def makefile_name(self):
328 return 'Makefile'
329
murgatroid99132ce6a2015-03-04 17:29:14 -0800330 def supports_multi_config(self):
331 return False
332
333 def __str__(self):
334 return 'ruby'
335
Craig Tillerd625d812015-04-08 15:52:35 -0700336
Jan Tattermusch1970a5b2015-03-03 15:17:25 -0800337class CSharpLanguage(object):
Jan Tattermuschb00aa672015-06-01 15:48:03 -0700338 def __init__(self):
Craig Tillerd50993d2015-08-05 08:04:36 -0700339 self.platform = platform_string()
Jan Tattermuschb00aa672015-06-01 15:48:03 -0700340
Craig Tiller883064c2015-11-04 10:06:10 -0800341 def test_specs(self, config, args):
Jan Tattermusch03c01062015-12-11 14:28:56 -0800342 with open('src/csharp/tests.json') as f:
343 tests_json = json.load(f)
344 assemblies = tests_json['assemblies']
345 tests = tests_json['tests']
346
347 msbuild_config = _WINDOWS_CONFIG[config.build_config]
348 assembly_files = ['%s/bin/%s/%s.dll' % (a, msbuild_config, a)
349 for a in assemblies]
350
351 extra_args = ['-labels'] + assembly_files
352
Jan Tattermuschb00aa672015-06-01 15:48:03 -0700353 if self.platform == 'windows':
Jan Tattermusch03c01062015-12-11 14:28:56 -0800354 script_name = 'tools\\run_tests\\run_csharp.bat'
355 extra_args += ['-domain=None']
Jan Tattermuschb00aa672015-06-01 15:48:03 -0700356 else:
Jan Tattermusch03c01062015-12-11 14:28:56 -0800357 script_name = 'tools/run_tests/run_csharp.sh'
Jan Tattermuschbf3b1532015-10-26 10:24:42 -0700358
Jan Tattermuschbdf4b2e2015-10-28 08:22:34 -0700359 if config.build_config == 'gcov':
360 # On Windows, we only collect C# code coverage.
361 # On Linux, we only collect coverage for native extension.
362 # For code coverage all tests need to run as one suite.
Jan Tattermusch03c01062015-12-11 14:28:56 -0800363 return [config.job_spec([script_name] + extra_args, None,
364 shortname='csharp.coverage',
365 environ=_FORCE_ENVIRON_FOR_WRAPPERS)]
Jan Tattermusch61c3a832015-10-27 17:54:50 -0700366 else:
Jan Tattermusch03c01062015-12-11 14:28:56 -0800367 specs = []
368 for test in tests:
369 cmdline = [script_name, '-run=%s' % test] + extra_args
370 if self.platform == 'windows':
371 # use different output directory for each test to prevent
372 # TestResult.xml clash between parallel test runs.
373 cmdline += ['-work=test-result/%s' % uuid.uuid4()]
374 specs.append(config.job_spec(cmdline, None,
375 shortname='csharp.%s' % test,
376 environ=_FORCE_ENVIRON_FOR_WRAPPERS))
377 return specs
Jan Tattermusch1970a5b2015-03-03 15:17:25 -0800378
murgatroid99256d3df2015-09-21 16:58:02 -0700379 def pre_build_steps(self):
Jan Tattermusch48423fc2015-10-07 18:59:16 -0700380 if self.platform == 'windows':
Jan Tattermusch874aec02015-10-07 19:26:19 -0700381 return [['tools\\run_tests\\pre_build_csharp.bat']]
Jan Tattermusch48423fc2015-10-07 18:59:16 -0700382 else:
383 return [['tools/run_tests/pre_build_csharp.sh']]
murgatroid99256d3df2015-09-21 16:58:02 -0700384
Craig Tiller883064c2015-11-04 10:06:10 -0800385 def make_targets(self, test_regex):
Jan Tattermuschb00aa672015-06-01 15:48:03 -0700386 # For Windows, this target doesn't really build anything,
387 # everything is build by buildall script later.
Craig Tillerd5904822015-08-31 21:30:58 -0700388 if self.platform == 'windows':
389 return []
390 else:
391 return ['grpc_csharp_ext']
Jan Tattermusch1970a5b2015-03-03 15:17:25 -0800392
Jan Tattermuschc895fe02016-01-20 09:13:09 -0800393 def make_options(self):
394 if self.platform == 'mac':
395 # On Mac, official distribution of mono is 32bit.
396 return ['CFLAGS=-arch i386', 'LDFLAGS=-arch i386']
397 else:
398 return []
399
Jan Tattermusch1970a5b2015-03-03 15:17:25 -0800400 def build_steps(self):
Jan Tattermuschb00aa672015-06-01 15:48:03 -0700401 if self.platform == 'windows':
402 return [['src\\csharp\\buildall.bat']]
403 else:
404 return [['tools/run_tests/build_csharp.sh']]
Nathaniel Manista840615e2015-01-22 20:31:47 +0000405
Nicolas "Pixel" Noble3fcd3bf2015-10-10 02:30:38 +0200406 def post_tests_steps(self):
407 return []
408
murgatroid99a3e244f2015-09-22 11:25:53 -0700409 def makefile_name(self):
410 return 'Makefile'
411
murgatroid99132ce6a2015-03-04 17:29:14 -0800412 def supports_multi_config(self):
413 return False
414
415 def __str__(self):
416 return 'csharp'
417
Craig Tillerd625d812015-04-08 15:52:35 -0700418
Jorge Canizalesa0b3bfa2015-07-30 19:25:52 -0700419class ObjCLanguage(object):
420
Craig Tiller883064c2015-11-04 10:06:10 -0800421 def test_specs(self, config, args):
Jorge Canizalesa0b3bfa2015-07-30 19:25:52 -0700422 return [config.job_spec(['src/objective-c/tests/run_tests.sh'], None,
423 environ=_FORCE_ENVIRON_FOR_WRAPPERS)]
424
murgatroid99256d3df2015-09-21 16:58:02 -0700425 def pre_build_steps(self):
426 return []
427
Craig Tiller883064c2015-11-04 10:06:10 -0800428 def make_targets(self, test_regex):
Jorge Canizalesd0b32e92015-07-30 23:08:43 -0700429 return ['grpc_objective_c_plugin', 'interop_server']
Jorge Canizalesa0b3bfa2015-07-30 19:25:52 -0700430
Jan Tattermuschc895fe02016-01-20 09:13:09 -0800431 def make_options(self):
432 return []
433
Jorge Canizalesa0b3bfa2015-07-30 19:25:52 -0700434 def build_steps(self):
Jorge Canizalesd0b32e92015-07-30 23:08:43 -0700435 return [['src/objective-c/tests/build_tests.sh']]
Jorge Canizalesa0b3bfa2015-07-30 19:25:52 -0700436
Nicolas "Pixel" Noble3fcd3bf2015-10-10 02:30:38 +0200437 def post_tests_steps(self):
438 return []
439
murgatroid99a3e244f2015-09-22 11:25:53 -0700440 def makefile_name(self):
441 return 'Makefile'
442
Jorge Canizalesa0b3bfa2015-07-30 19:25:52 -0700443 def supports_multi_config(self):
444 return False
445
446 def __str__(self):
447 return 'objc'
448
449
Nicolas "Pixel" Noble9f728642015-03-24 18:50:30 +0100450class Sanity(object):
451
Craig Tiller883064c2015-11-04 10:06:10 -0800452 def test_specs(self, config, args):
Craig Tiller94d04a52016-01-20 10:58:23 -0800453 import yaml
454 with open('tools/run_tests/sanity_tests.yaml', 'r') as f:
Craig Tiller528fd1e2016-01-21 15:27:37 -0800455 return [config.job_spec(cmd['script'].split(), None, timeout_seconds=None, environ={'TEST': 'true'}, cpu_cost=cmd.get('cpu_cost', 1))
Craig Tiller94d04a52016-01-20 10:58:23 -0800456 for cmd in yaml.load(f)]
Nicolas "Pixel" Noble9f728642015-03-24 18:50:30 +0100457
murgatroid99256d3df2015-09-21 16:58:02 -0700458 def pre_build_steps(self):
459 return []
460
Craig Tiller883064c2015-11-04 10:06:10 -0800461 def make_targets(self, test_regex):
Nicolas "Pixel" Noble9f728642015-03-24 18:50:30 +0100462 return ['run_dep_checks']
463
Jan Tattermuschc895fe02016-01-20 09:13:09 -0800464 def make_options(self):
465 return []
466
Nicolas "Pixel" Noble9f728642015-03-24 18:50:30 +0100467 def build_steps(self):
468 return []
469
Nicolas "Pixel" Noble87879b32015-10-12 23:28:53 +0200470 def post_tests_steps(self):
471 return []
472
murgatroid99a3e244f2015-09-22 11:25:53 -0700473 def makefile_name(self):
474 return 'Makefile'
475
Nicolas "Pixel" Noble9f728642015-03-24 18:50:30 +0100476 def supports_multi_config(self):
477 return False
478
479 def __str__(self):
480 return 'sanity'
481
Nicolas "Pixel" Noblee55cd7f2015-04-14 17:59:13 +0200482
Nicolas "Pixel" Noblefd2b0932015-03-26 00:26:29 +0100483class Build(object):
484
Craig Tiller883064c2015-11-04 10:06:10 -0800485 def test_specs(self, config, args):
Nicolas "Pixel" Noblefd2b0932015-03-26 00:26:29 +0100486 return []
487
murgatroid99256d3df2015-09-21 16:58:02 -0700488 def pre_build_steps(self):
489 return []
490
Craig Tiller883064c2015-11-04 10:06:10 -0800491 def make_targets(self, test_regex):
Nicolas "Pixel" Noblec23827b2015-04-23 06:17:55 +0200492 return ['static']
Nicolas "Pixel" Noblefd2b0932015-03-26 00:26:29 +0100493
Jan Tattermuschc895fe02016-01-20 09:13:09 -0800494 def make_options(self):
495 return []
496
Nicolas "Pixel" Noblefd2b0932015-03-26 00:26:29 +0100497 def build_steps(self):
498 return []
499
Nicolas "Pixel" Noblefe300452015-10-27 23:05:10 +0100500 def post_tests_steps(self):
501 return []
502
murgatroid99a3e244f2015-09-22 11:25:53 -0700503 def makefile_name(self):
504 return 'Makefile'
505
Nicolas "Pixel" Noblefd2b0932015-03-26 00:26:29 +0100506 def supports_multi_config(self):
507 return True
508
509 def __str__(self):
510 return self.make_target
511
512
Craig Tiller738c3342015-01-12 14:28:33 -0800513# different configurations we can run under
Craig Tillera0f85172016-01-20 15:56:06 -0800514with open('tools/run_tests/configs.json') as f:
Craig Tiller1dce9062016-01-20 17:01:56 -0800515 _CONFIGS = dict((cfg['config'], Config(**cfg)) for cfg in ast.literal_eval(f.read()))
Craig Tiller738c3342015-01-12 14:28:33 -0800516
517
Nicolas "Pixel" Noble1fb5e822015-03-16 06:20:37 +0100518_DEFAULT = ['opt']
Craig Tillerc7449162015-01-16 14:42:10 -0800519_LANGUAGES = {
Craig Tillere9c959d2015-01-18 10:23:26 -0800520 'c++': CLanguage('cxx', 'c++'),
521 'c': CLanguage('c', 'c'),
murgatroid992c8d5162015-01-26 10:41:21 -0800522 'node': NodeLanguage(),
Nathaniel Manista840615e2015-01-22 20:31:47 +0000523 'php': PhpLanguage(),
524 'python': PythonLanguage(),
Jan Tattermusch1970a5b2015-03-03 15:17:25 -0800525 'ruby': RubyLanguage(),
Nicolas "Pixel" Noble9f728642015-03-24 18:50:30 +0100526 'csharp': CSharpLanguage(),
Jorge Canizalesa0b3bfa2015-07-30 19:25:52 -0700527 'objc' : ObjCLanguage(),
Nicolas "Pixel" Noble9f728642015-03-24 18:50:30 +0100528 'sanity': Sanity(),
Nicolas "Pixel" Noblefd2b0932015-03-26 00:26:29 +0100529 'build': Build(),
Craig Tillereb272bc2015-01-30 13:13:14 -0800530 }
Nicolas Nobleddef2462015-01-06 18:08:25 -0800531
Craig Tiller7bb3efd2015-09-01 08:04:03 -0700532_WINDOWS_CONFIG = {
533 'dbg': 'Debug',
534 'opt': 'Release',
Jan Tattermusche4a69182015-12-15 09:53:01 -0800535 'gcov': 'Debug',
Craig Tiller7bb3efd2015-09-01 08:04:03 -0700536 }
537
David Garcia Quintase90cd372015-05-31 18:15:26 -0700538
Jan Tattermusch2dd156e2015-12-04 18:26:17 -0800539def _windows_arch_option(arch):
540 """Returns msbuild cmdline option for selected architecture."""
Jan Tattermusch9be594f2016-01-25 18:08:47 -0800541 if arch == 'default' or arch == 'x86':
Jan Tattermusch2dd156e2015-12-04 18:26:17 -0800542 return '/p:Platform=Win32'
Jan Tattermusch9be594f2016-01-25 18:08:47 -0800543 elif arch == 'x64':
Jan Tattermusch2dd156e2015-12-04 18:26:17 -0800544 return '/p:Platform=x64'
545 else:
Jan Tattermusch9be594f2016-01-25 18:08:47 -0800546 print 'Architecture %s not supported.' % arch
Jan Tattermusch2dd156e2015-12-04 18:26:17 -0800547 sys.exit(1)
Jan Tattermuschf08018a2016-01-26 08:22:09 -0800548
549
550def _check_arch_option(arch):
551 """Checks that architecture option is valid."""
552 if platform_string() == 'windows':
553 _windows_arch_option(arch)
554 elif platform_string() == 'linux':
555 # On linux, we need to be running under docker with the right architecture.
Jan Tattermusch07fb0422016-01-26 10:46:56 -0800556 runtime_arch = platform.architecture()[0]
Jan Tattermuschf08018a2016-01-26 08:22:09 -0800557 if arch == 'default':
558 return
559 elif runtime_arch == '64bit' and arch == 'x64':
560 return
561 elif runtime_arch == '32bit' and arch == 'x86':
562 return
563 else:
564 print 'Architecture %s does not match current runtime architecture.' % arch
565 sys.exit(1)
566 else:
567 if args.arch != 'default':
568 print 'Architecture %s not supported on current platform.' % args.arch
569 sys.exit(1)
Jan Tattermusch2dd156e2015-12-04 18:26:17 -0800570
Jan Tattermusch4dc9e722016-01-25 17:00:54 -0800571
Jan Tattermusch2dd156e2015-12-04 18:26:17 -0800572def _windows_build_bat(compiler):
573 """Returns name of build.bat for selected compiler."""
574 if compiler == 'default' or compiler == 'vs2013':
575 return 'vsprojects\\build_vs2013.bat'
576 elif compiler == 'vs2015':
577 return 'vsprojects\\build_vs2015.bat'
578 elif compiler == 'vs2010':
579 return 'vsprojects\\build_vs2010.bat'
580 else:
581 print 'Compiler %s not supported.' % compiler
582 sys.exit(1)
Jan Tattermusch4dc9e722016-01-25 17:00:54 -0800583
584
Jan Tattermusch2dd156e2015-12-04 18:26:17 -0800585def _windows_toolset_option(compiler):
586 """Returns msbuild PlatformToolset for selected compiler."""
587 if compiler == 'default' or compiler == 'vs2013':
588 return '/p:PlatformToolset=v120'
589 elif compiler == 'vs2015':
590 return '/p:PlatformToolset=v140'
591 elif compiler == 'vs2010':
592 return '/p:PlatformToolset=v100'
593 else:
594 print 'Compiler %s not supported.' % compiler
595 sys.exit(1)
Jan Tattermusch4dc9e722016-01-25 17:00:54 -0800596
597
Jan Tattermusch9be594f2016-01-25 18:08:47 -0800598def _get_dockerfile_dir(arch):
Jan Tattermusch4dc9e722016-01-25 17:00:54 -0800599 """Returns dockerfile to use"""
Jan Tattermusch9be594f2016-01-25 18:08:47 -0800600 if arch == 'default' or arch == 'x64':
Jan Tattermusch5371d732016-01-25 21:40:04 -0800601 return 'tools/dockerfile/grpc_jenkins_slave_x64'
Jan Tattermusch9be594f2016-01-25 18:08:47 -0800602 elif arch == 'x86':
Jan Tattermusch5371d732016-01-25 21:40:04 -0800603 return 'tools/dockerfile/grpc_jenkins_slave_x86'
Jan Tattermusch4dc9e722016-01-25 17:00:54 -0800604 else:
Jan Tattermusch9be594f2016-01-25 18:08:47 -0800605 print 'Architecture %s not supported with current settings.' % arch
606 sys.exit(1)
Jan Tattermusch2dd156e2015-12-04 18:26:17 -0800607
David Garcia Quintase90cd372015-05-31 18:15:26 -0700608def runs_per_test_type(arg_str):
609 """Auxilary function to parse the "runs_per_test" flag.
610
611 Returns:
612 A positive integer or 0, the latter indicating an infinite number of
613 runs.
614
615 Raises:
616 argparse.ArgumentTypeError: Upon invalid input.
617 """
618 if arg_str == 'inf':
619 return 0
620 try:
621 n = int(arg_str)
622 if n <= 0: raise ValueError
Craig Tiller50e53e22015-06-01 20:18:21 -0700623 return n
David Garcia Quintase90cd372015-05-31 18:15:26 -0700624 except:
Adele Zhoue4c35612015-10-16 15:34:23 -0700625 msg = '\'{}\' is not a positive integer or \'inf\''.format(arg_str)
David Garcia Quintase90cd372015-05-31 18:15:26 -0700626 raise argparse.ArgumentTypeError(msg)
Jan Tattermuschc95eead2015-09-18 13:03:50 -0700627
628# parse command line
629argp = argparse.ArgumentParser(description='Run grpc tests.')
630argp.add_argument('-c', '--config',
631 choices=['all'] + sorted(_CONFIGS.keys()),
632 nargs='+',
633 default=_DEFAULT)
David Garcia Quintase90cd372015-05-31 18:15:26 -0700634argp.add_argument('-n', '--runs_per_test', default=1, type=runs_per_test_type,
635 help='A positive integer or "inf". If "inf", all tests will run in an '
636 'infinite loop. Especially useful in combination with "-f"')
Craig Tillerfe406ec2015-02-24 13:55:12 -0800637argp.add_argument('-r', '--regex', default='.*', type=str)
Craig Tiller5f735a62016-01-20 09:31:15 -0800638argp.add_argument('-j', '--jobs', default=multiprocessing.cpu_count(), type=int)
Craig Tiller8451e872015-02-27 09:25:51 -0800639argp.add_argument('-s', '--slowdown', default=1.0, type=float)
ctiller3040cb72015-01-07 12:13:17 -0800640argp.add_argument('-f', '--forever',
641 default=False,
642 action='store_const',
643 const=True)
Nicolas "Pixel" Noblea7df3f92015-02-26 22:07:04 +0100644argp.add_argument('-t', '--travis',
645 default=False,
646 action='store_const',
647 const=True)
Nicolas Noble044db742015-01-14 16:57:24 -0800648argp.add_argument('--newline_on_success',
649 default=False,
650 action='store_const',
651 const=True)
Craig Tiller686fb262015-01-15 07:39:09 -0800652argp.add_argument('-l', '--language',
Craig Tiller60f15e62015-05-13 09:05:17 -0700653 choices=['all'] + sorted(_LANGUAGES.keys()),
Craig Tiller686fb262015-01-15 07:39:09 -0800654 nargs='+',
Craig Tiller60f15e62015-05-13 09:05:17 -0700655 default=['all'])
Craig Tillercd43da82015-05-29 08:41:29 -0700656argp.add_argument('-S', '--stop_on_failure',
657 default=False,
658 action='store_const',
659 const=True)
Jan Tattermuschc95eead2015-09-18 13:03:50 -0700660argp.add_argument('--use_docker',
661 default=False,
662 action='store_const',
663 const=True,
Adele Zhoue4c35612015-10-16 15:34:23 -0700664 help='Run all the tests under docker. That provides ' +
665 'additional isolation and prevents the need to install ' +
666 'language specific prerequisites. Only available on Linux.')
Craig Tillerd4509a12015-09-28 09:18:40 -0700667argp.add_argument('--allow_flakes',
668 default=False,
669 action='store_const',
670 const=True,
Adele Zhoue4c35612015-10-16 15:34:23 -0700671 help='Allow flaky tests to show as passing (re-runs failed tests up to five times)')
Jan Tattermusch2dd156e2015-12-04 18:26:17 -0800672argp.add_argument('--arch',
Jan Tattermusch9be594f2016-01-25 18:08:47 -0800673 choices=['default', 'x86', 'x64'],
Jan Tattermusch2dd156e2015-12-04 18:26:17 -0800674 default='default',
675 help='Selects architecture to target. For some platforms "default" is the only supported choice.')
676argp.add_argument('--compiler',
677 choices=['default', 'vs2010', 'vs2013', 'vs2015'],
678 default='default',
679 help='Selects compiler to use. For some platforms "default" is the only supported choice.')
680argp.add_argument('--build_only',
681 default=False,
682 action='store_const',
683 const=True,
684 help='Perform all the build steps but dont run any tests.')
Craig Tiller5f735a62016-01-20 09:31:15 -0800685argp.add_argument('--measure_cpu_costs', default=False, action='store_const', const=True,
686 help='Measure the cpu costs of tests')
Craig Tiller1676f912016-01-05 10:49:44 -0800687argp.add_argument('--update_submodules', default=[], nargs='*',
688 help='Update some submodules before building. If any are updated, also run generate_projects. ' +
689 'Submodules are specified as SUBMODULE_NAME:BRANCH; if BRANCH is omitted, master is assumed.')
Craig Tiller234b6e72015-05-23 10:12:40 -0700690argp.add_argument('-a', '--antagonists', default=0, type=int)
Nicolas "Pixel" Noble5937b5b2015-06-26 02:04:12 +0200691argp.add_argument('-x', '--xml_report', default=None, type=str,
692 help='Generates a JUnit-compatible XML report')
Nicolas Nobleddef2462015-01-06 18:08:25 -0800693args = argp.parse_args()
694
Craig Tiller5f735a62016-01-20 09:31:15 -0800695jobset.measure_cpu_costs = args.measure_cpu_costs
696
Craig Tiller1676f912016-01-05 10:49:44 -0800697# update submodules if necessary
Craig Tillerb361b4e2016-01-06 11:44:17 -0800698need_to_regenerate_projects = False
699for spec in args.update_submodules:
700 spec = spec.split(':', 1)
701 if len(spec) == 1:
702 submodule = spec[0]
703 branch = 'master'
704 elif len(spec) == 2:
705 submodule = spec[0]
706 branch = spec[1]
707 cwd = 'third_party/%s' % submodule
708 def git(cmd, cwd=cwd):
709 print 'in %s: git %s' % (cwd, cmd)
710 subprocess.check_call('git %s' % cmd, cwd=cwd, shell=True)
711 git('fetch')
712 git('checkout %s' % branch)
713 git('pull origin %s' % branch)
714 if os.path.exists('src/%s/gen_build_yaml.py' % submodule):
715 need_to_regenerate_projects = True
716if need_to_regenerate_projects:
717 if jobset.platform_string() == 'linux':
718 subprocess.check_call('tools/buildgen/generate_projects.sh', shell=True)
719 else:
720 print 'WARNING: may need to regenerate projects, but since we are not on'
721 print ' Linux this step is being skipped. Compilation MAY fail.'
Craig Tiller1676f912016-01-05 10:49:44 -0800722
723
Nicolas Nobleddef2462015-01-06 18:08:25 -0800724# grab config
Craig Tiller738c3342015-01-12 14:28:33 -0800725run_configs = set(_CONFIGS[cfg]
726 for cfg in itertools.chain.from_iterable(
727 _CONFIGS.iterkeys() if x == 'all' else [x]
728 for x in args.config))
729build_configs = set(cfg.build_config for cfg in run_configs)
Craig Tillerf1973b02015-01-16 12:32:13 -0800730
Craig Tiller06805272015-06-11 14:46:47 -0700731if args.travis:
murgatroid99d3b5b7f2015-10-06 17:02:03 -0700732 _FORCE_ENVIRON_FOR_WRAPPERS = {'GRPC_TRACE': 'api'}
Craig Tiller06805272015-06-11 14:46:47 -0700733
Adele Zhou6b9527c2015-11-20 15:56:35 -0800734if 'all' in args.language:
Craig Tiller1676f912016-01-05 10:49:44 -0800735 lang_list = _LANGUAGES.keys()
Adele Zhou6b9527c2015-11-20 15:56:35 -0800736else:
737 lang_list = args.language
Craig Tiller16900662016-01-07 19:30:54 -0800738# We don't support code coverage on some languages
739if 'gcov' in args.config:
740 for bad in ['objc', 'sanity', 'build']:
741 if bad in lang_list:
742 lang_list.remove(bad)
Adele Zhou6b9527c2015-11-20 15:56:35 -0800743
744languages = set(_LANGUAGES[l] for l in lang_list)
murgatroid99132ce6a2015-03-04 17:29:14 -0800745
746if len(build_configs) > 1:
747 for language in languages:
748 if not language.supports_multi_config():
749 print language, 'does not support multiple build configurations'
750 sys.exit(1)
751
Jan Tattermuschc895fe02016-01-20 09:13:09 -0800752language_make_options=[]
753if any(language.make_options() for language in languages):
754 if len(languages) != 1:
755 print 'languages with custom make options cannot be built simultaneously with other languages'
756 sys.exit(1)
757 else:
758 language_make_options = next(iter(languages)).make_options()
759
Jan Tattermusch4dc9e722016-01-25 17:00:54 -0800760if len(languages) != 1 or len(build_configs) != 1:
761 print 'Multi-language and multi-config testing is not supported.'
762 sys.exit(1)
763
764if args.use_docker:
765 if not args.travis:
766 print 'Seen --use_docker flag, will run tests under docker.'
767 print
768 print 'IMPORTANT: The changes you are testing need to be locally committed'
769 print 'because only the committed changes in the current branch will be'
770 print 'copied to the docker environment.'
771 time.sleep(5)
772
773 child_argv = [ arg for arg in sys.argv if not arg == '--use_docker' ]
774 run_tests_cmd = 'tools/run_tests/run_tests.py %s' % ' '.join(child_argv[1:])
775
Jan Tattermusch4dc9e722016-01-25 17:00:54 -0800776 env = os.environ.copy()
777 env['RUN_TESTS_COMMAND'] = run_tests_cmd
Jan Tattermusch9be594f2016-01-25 18:08:47 -0800778 env['DOCKERFILE_DIR'] = _get_dockerfile_dir(args.arch)
Jan Tattermusch4dc9e722016-01-25 17:00:54 -0800779 env['DOCKER_RUN_SCRIPT'] = 'tools/jenkins/docker_run_tests.sh'
780 if args.xml_report:
781 env['XML_REPORT'] = args.xml_report
782 if not args.travis:
783 env['TTY_FLAG'] = '-t' # enables Ctrl-C when not on Jenkins.
784
785 subprocess.check_call(['tools/jenkins/build_docker_and_run_tests.sh'],
786 shell=True,
787 env=env)
788 sys.exit(0)
Jan Tattermuschf08018a2016-01-26 08:22:09 -0800789
790if platform_string() != 'windows' and args.compiler != 'default':
791 print 'Compiler %s not supported on current platform.' % args.compiler
792 sys.exit(1)
793
794_check_arch_option(args.arch)
Jan Tattermusch4dc9e722016-01-25 17:00:54 -0800795
Jan Tattermuschfba65302016-01-25 18:21:14 -0800796def make_jobspec(cfg, targets, makefile='Makefile'):
797 if platform_string() == 'windows':
Craig Tillerfc3c0c42015-09-01 16:47:54 -0700798 extra_args = []
Craig Tillerb5391e12015-09-03 14:35:18 -0700799 # better do parallel compilation
Jan Tattermusch47eeb2b2015-10-07 14:09:18 -0700800 # empirically /m:2 gives the best performance/price and should prevent
801 # overloading the windows workers.
Adele Zhoue4c35612015-10-16 15:34:23 -0700802 extra_args.extend(['/m:2'])
Craig Tillerb5391e12015-09-03 14:35:18 -0700803 # disable PDB generation: it's broken, and we don't need it during CI
Adele Zhoue4c35612015-10-16 15:34:23 -0700804 extra_args.extend(['/p:Jenkins=true'])
Craig Tiller6fd23842015-09-01 07:36:31 -0700805 return [
Jan Tattermusch2dd156e2015-12-04 18:26:17 -0800806 jobset.JobSpec([_windows_build_bat(args.compiler),
murgatroid99cf08daf2015-09-21 15:33:16 -0700807 'vsprojects\\%s.sln' % target,
Jan Tattermusch2dd156e2015-12-04 18:26:17 -0800808 '/p:Configuration=%s' % _WINDOWS_CONFIG[cfg],
809 _windows_toolset_option(args.compiler),
810 _windows_arch_option(args.arch)] +
Jan Tattermuschc895fe02016-01-20 09:13:09 -0800811 extra_args +
812 language_make_options,
Craig Tiller590105a2016-01-19 13:03:46 -0800813 shell=True, timeout_seconds=None)
Craig Tiller6fd23842015-09-01 07:36:31 -0700814 for target in targets]
Jan Tattermuschfba65302016-01-25 18:21:14 -0800815 else:
murgatroid998ae409f2015-10-26 16:39:00 -0700816 if targets:
817 return [jobset.JobSpec([os.getenv('MAKE', 'make'),
818 '-f', makefile,
Craig Tillerdd6f7ed2016-01-21 12:54:42 -0800819 '-j', '%d' % args.jobs,
Craig Tiller71a86042016-01-15 14:59:58 -0800820 'EXTRA_DEFINES=GRPC_TEST_SLOWDOWN_MACHINE_FACTOR=%f' % args.slowdown,
821 'CONFIG=%s' % cfg] +
Jan Tattermuschc895fe02016-01-20 09:13:09 -0800822 language_make_options +
Craig Tiller71a86042016-01-15 14:59:58 -0800823 ([] if not args.travis else ['JENKINS_BUILD=1']) +
824 targets,
Craig Tiller590105a2016-01-19 13:03:46 -0800825 timeout_seconds=None)]
murgatroid998ae409f2015-10-26 16:39:00 -0700826 else:
827 return []
Jan Tattermuschfba65302016-01-25 18:21:14 -0800828
murgatroid99a3e244f2015-09-22 11:25:53 -0700829make_targets = {}
830for l in languages:
831 makefile = l.makefile_name()
832 make_targets[makefile] = make_targets.get(makefile, set()).union(
Craig Tiller883064c2015-11-04 10:06:10 -0800833 set(l.make_targets(args.regex)))
Craig Tiller5058c692015-04-08 09:42:04 -0700834
Jan Tattermusche4a69182015-12-15 09:53:01 -0800835def build_step_environ(cfg):
836 environ = {'CONFIG': cfg}
Jan Tattermusch68016a12015-12-16 15:31:33 -0800837 msbuild_cfg = _WINDOWS_CONFIG.get(cfg)
Jan Tattermusche4a69182015-12-15 09:53:01 -0800838 if msbuild_cfg:
839 environ['MSBUILD_CONFIG'] = msbuild_cfg
840 return environ
841
murgatroid99fddac962015-09-22 09:20:11 -0700842build_steps = list(set(
Jan Tattermusche4a69182015-12-15 09:53:01 -0800843 jobset.JobSpec(cmdline, environ=build_step_environ(cfg), flake_retries=5)
murgatroid99256d3df2015-09-21 16:58:02 -0700844 for cfg in build_configs
845 for l in languages
846 for cmdline in l.pre_build_steps()))
Craig Tillerbd4e3782015-09-01 06:48:55 -0700847if make_targets:
murgatroid99a3e244f2015-09-22 11:25:53 -0700848 make_commands = itertools.chain.from_iterable(make_jobspec(cfg, list(targets), makefile) for cfg in build_configs for (makefile, targets) in make_targets.iteritems())
Craig Tiller6fd23842015-09-01 07:36:31 -0700849 build_steps.extend(set(make_commands))
Craig Tiller5058c692015-04-08 09:42:04 -0700850build_steps.extend(set(
Craig Tiller590105a2016-01-19 13:03:46 -0800851 jobset.JobSpec(cmdline, environ=build_step_environ(cfg), timeout_seconds=None)
murgatroid99132ce6a2015-03-04 17:29:14 -0800852 for cfg in build_configs
Craig Tiller547db2b2015-01-30 14:08:39 -0800853 for l in languages
Craig Tiller533b1a22015-05-29 08:41:29 -0700854 for cmdline in l.build_steps()))
Craig Tillerf1973b02015-01-16 12:32:13 -0800855
Nicolas "Pixel" Noble3fcd3bf2015-10-10 02:30:38 +0200856post_tests_steps = list(set(
Jan Tattermusche4a69182015-12-15 09:53:01 -0800857 jobset.JobSpec(cmdline, environ=build_step_environ(cfg))
Nicolas "Pixel" Noble3fcd3bf2015-10-10 02:30:38 +0200858 for cfg in build_configs
859 for l in languages
860 for cmdline in l.post_tests_steps()))
Nicolas Nobleddef2462015-01-06 18:08:25 -0800861runs_per_test = args.runs_per_test
ctiller3040cb72015-01-07 12:13:17 -0800862forever = args.forever
Nicolas Nobleddef2462015-01-06 18:08:25 -0800863
Nicolas Nobleddef2462015-01-06 18:08:25 -0800864
Craig Tiller71735182015-01-15 17:07:13 -0800865class TestCache(object):
Craig Tillerb50d1662015-01-15 17:28:21 -0800866 """Cache for running tests."""
867
David Klempner25739582015-02-11 15:57:32 -0800868 def __init__(self, use_cache_results):
Craig Tiller71735182015-01-15 17:07:13 -0800869 self._last_successful_run = {}
David Klempner25739582015-02-11 15:57:32 -0800870 self._use_cache_results = use_cache_results
Craig Tiller69cd2372015-06-11 09:38:09 -0700871 self._last_save = time.time()
Craig Tiller71735182015-01-15 17:07:13 -0800872
873 def should_run(self, cmdline, bin_hash):
Craig Tiller71735182015-01-15 17:07:13 -0800874 if cmdline not in self._last_successful_run:
875 return True
876 if self._last_successful_run[cmdline] != bin_hash:
877 return True
David Klempner25739582015-02-11 15:57:32 -0800878 if not self._use_cache_results:
879 return True
Craig Tiller71735182015-01-15 17:07:13 -0800880 return False
881
882 def finished(self, cmdline, bin_hash):
Craig Tiller547db2b2015-01-30 14:08:39 -0800883 self._last_successful_run[cmdline] = bin_hash
Craig Tiller69cd2372015-06-11 09:38:09 -0700884 if time.time() - self._last_save > 1:
885 self.save()
Craig Tiller71735182015-01-15 17:07:13 -0800886
887 def dump(self):
Craig Tillerb50d1662015-01-15 17:28:21 -0800888 return [{'cmdline': k, 'hash': v}
889 for k, v in self._last_successful_run.iteritems()]
Craig Tiller71735182015-01-15 17:07:13 -0800890
891 def parse(self, exdump):
892 self._last_successful_run = dict((o['cmdline'], o['hash']) for o in exdump)
893
894 def save(self):
895 with open('.run_tests_cache', 'w') as f:
Craig Tiller261dd982015-01-16 16:41:45 -0800896 f.write(json.dumps(self.dump()))
Craig Tiller69cd2372015-06-11 09:38:09 -0700897 self._last_save = time.time()
Craig Tiller71735182015-01-15 17:07:13 -0800898
Craig Tiller1cc11db2015-01-15 22:50:50 -0800899 def maybe_load(self):
900 if os.path.exists('.run_tests_cache'):
901 with open('.run_tests_cache') as f:
Craig Tiller261dd982015-01-16 16:41:45 -0800902 self.parse(json.loads(f.read()))
Craig Tiller71735182015-01-15 17:07:13 -0800903
904
Craig Tillerf53d9c82015-08-04 14:19:43 -0700905def _start_port_server(port_server_port):
906 # check if a compatible port server is running
907 # if incompatible (version mismatch) ==> start a new one
908 # if not running ==> start a new one
909 # otherwise, leave it up
910 try:
Craig Tillerfe4939f2015-10-06 12:55:36 -0700911 version = int(urllib2.urlopen(
912 'http://localhost:%d/version_number' % port_server_port,
913 timeout=1).read())
914 print 'detected port server running version %d' % version
Craig Tillerf53d9c82015-08-04 14:19:43 -0700915 running = True
Craig Tillerfe4939f2015-10-06 12:55:36 -0700916 except Exception as e:
Craig Tiller0d6499e2015-10-05 14:00:47 -0700917 print 'failed to detect port server: %s' % sys.exc_info()[0]
Craig Tillerfe4939f2015-10-06 12:55:36 -0700918 print e.strerror
Craig Tillerf53d9c82015-08-04 14:19:43 -0700919 running = False
920 if running:
Craig Tillerfe4939f2015-10-06 12:55:36 -0700921 current_version = int(subprocess.check_output(
Jan Tattermusch3bd08272015-11-04 19:24:37 -0800922 [sys.executable, os.path.abspath('tools/run_tests/port_server.py'),
923 'dump_version']))
Craig Tillerfe4939f2015-10-06 12:55:36 -0700924 print 'my port server is version %d' % current_version
925 running = (version >= current_version)
926 if not running:
927 print 'port_server version mismatch: killing the old one'
928 urllib2.urlopen('http://localhost:%d/quitquitquit' % port_server_port).read()
929 time.sleep(1)
Craig Tillerf53d9c82015-08-04 14:19:43 -0700930 if not running:
Craig Tillerf0a293e2015-10-12 10:05:50 -0700931 fd, logfile = tempfile.mkstemp()
932 os.close(fd)
933 print 'starting port_server, with log file %s' % logfile
Jan Tattermusch3bd08272015-11-04 19:24:37 -0800934 args = [sys.executable, os.path.abspath('tools/run_tests/port_server.py'),
935 '-p', '%d' % port_server_port, '-l', logfile]
Craig Tiller367d41d2015-10-12 13:00:22 -0700936 env = dict(os.environ)
937 env['BUILD_ID'] = 'pleaseDontKillMeJenkins'
Nicolas "Pixel" Noblef72d7b52015-12-03 03:07:43 +0100938 if platform_string() == 'windows':
Jan Tattermusch3bd08272015-11-04 19:24:37 -0800939 # Working directory of port server needs to be outside of Jenkins
940 # workspace to prevent file lock issues.
941 tempdir = tempfile.mkdtemp()
Craig Tillerd2c39712015-10-12 11:08:49 -0700942 port_server = subprocess.Popen(
Craig Tiller367d41d2015-10-12 13:00:22 -0700943 args,
944 env=env,
Jan Tattermusch3bd08272015-11-04 19:24:37 -0800945 cwd=tempdir,
Craig Tiller367d41d2015-10-12 13:00:22 -0700946 creationflags = 0x00000008, # detached process
947 close_fds=True)
Craig Tillerd2c39712015-10-12 11:08:49 -0700948 else:
949 port_server = subprocess.Popen(
950 args,
Craig Tiller367d41d2015-10-12 13:00:22 -0700951 env=env,
Craig Tillerd2c39712015-10-12 11:08:49 -0700952 preexec_fn=os.setsid,
953 close_fds=True)
Craig Tillerf0a293e2015-10-12 10:05:50 -0700954 time.sleep(1)
Craig Tiller8b5f4dc2015-08-26 08:02:01 -0700955 # ensure port server is up
Craig Tillerabd37fd2015-08-26 07:54:01 -0700956 waits = 0
Craig Tillerf53d9c82015-08-04 14:19:43 -0700957 while True:
Craig Tillerabd37fd2015-08-26 07:54:01 -0700958 if waits > 10:
Craig Tillerf0a293e2015-10-12 10:05:50 -0700959 print 'killing port server due to excessive start up waits'
Craig Tillerabd37fd2015-08-26 07:54:01 -0700960 port_server.kill()
Craig Tillera2f38b02015-09-24 11:19:17 -0700961 if port_server.poll() is not None:
Craig Tiller0d6499e2015-10-05 14:00:47 -0700962 print 'port_server failed to start'
Craig Tillerf0a293e2015-10-12 10:05:50 -0700963 # try one final time: maybe another build managed to start one
964 time.sleep(1)
965 try:
966 urllib2.urlopen('http://localhost:%d/get' % port_server_port,
967 timeout=1).read()
968 print 'last ditch attempt to contact port server succeeded'
969 break
970 except:
971 traceback.print_exc();
972 port_log = open(logfile, 'r').read()
973 print port_log
974 sys.exit(1)
Craig Tillerf53d9c82015-08-04 14:19:43 -0700975 try:
Craig Tillerabd37fd2015-08-26 07:54:01 -0700976 urllib2.urlopen('http://localhost:%d/get' % port_server_port,
977 timeout=1).read()
Craig Tillerf0a293e2015-10-12 10:05:50 -0700978 print 'port server is up and ready'
Craig Tillerf53d9c82015-08-04 14:19:43 -0700979 break
Craig Tiller31fdaa42015-09-25 13:09:59 -0700980 except socket.timeout:
Craig Tiller0d6499e2015-10-05 14:00:47 -0700981 print 'waiting for port_server: timeout'
Craig Tillerf0a293e2015-10-12 10:05:50 -0700982 traceback.print_exc();
983 time.sleep(1)
Craig Tiller31fdaa42015-09-25 13:09:59 -0700984 waits += 1
Craig Tillerf53d9c82015-08-04 14:19:43 -0700985 except urllib2.URLError:
Craig Tiller0d6499e2015-10-05 14:00:47 -0700986 print 'waiting for port_server: urlerror'
Craig Tillerf0a293e2015-10-12 10:05:50 -0700987 traceback.print_exc();
988 time.sleep(1)
Craig Tillerabd37fd2015-08-26 07:54:01 -0700989 waits += 1
Craig Tillerf53d9c82015-08-04 14:19:43 -0700990 except:
Craig Tillerf0a293e2015-10-12 10:05:50 -0700991 traceback.print_exc();
Craig Tillerf53d9c82015-08-04 14:19:43 -0700992 port_server.kill()
993 raise
994
995
Adele Zhoud5fffa52015-10-23 15:51:42 -0700996def _calculate_num_runs_failures(list_of_results):
997 """Caculate number of runs and failures for a particular test.
998
999 Args:
1000 list_of_results: (List) of JobResult object.
1001 Returns:
1002 A tuple of total number of runs and failures.
1003 """
1004 num_runs = len(list_of_results) # By default, there is 1 run per JobResult.
1005 num_failures = 0
1006 for jobresult in list_of_results:
1007 if jobresult.retries > 0:
1008 num_runs += jobresult.retries
1009 if jobresult.num_failures > 0:
1010 num_failures += jobresult.num_failures
1011 return num_runs, num_failures
1012
Adele Zhou6b9527c2015-11-20 15:56:35 -08001013
Craig Tillereb9de8b2016-01-08 08:57:41 -08001014# _build_and_run results
1015class BuildAndRunError(object):
1016
1017 BUILD = object()
1018 TEST = object()
1019 POST_TEST = object()
1020
1021
1022# returns a list of things that failed (or an empty list on success)
Craig Tillerf53d9c82015-08-04 14:19:43 -07001023def _build_and_run(
Jan Tattermusch2dd156e2015-12-04 18:26:17 -08001024 check_cancelled, newline_on_success, cache, xml_report=None, build_only=False):
ctiller3040cb72015-01-07 12:13:17 -08001025 """Do one pass of building & running tests."""
murgatroid99666450e2015-01-26 13:03:31 -08001026 # build latest sequentially
Adele Zhoue4c35612015-10-16 15:34:23 -07001027 num_failures, _ = jobset.run(
1028 build_steps, maxjobs=1, stop_on_failure=True,
Craig Tiller883064c2015-11-04 10:06:10 -08001029 newline_on_success=newline_on_success, travis=args.travis)
Adele Zhoue4c35612015-10-16 15:34:23 -07001030 if num_failures:
Craig Tillereb9de8b2016-01-08 08:57:41 -08001031 return [BuildAndRunError.BUILD]
Craig Tillerb361b4e2016-01-06 11:44:17 -08001032
Jan Tattermusch2dd156e2015-12-04 18:26:17 -08001033 if build_only:
Craig Tillereb9de8b2016-01-08 08:57:41 -08001034 return []
ctiller3040cb72015-01-07 12:13:17 -08001035
Craig Tiller234b6e72015-05-23 10:12:40 -07001036 # start antagonists
David Garcia Quintas79e389f2015-06-02 17:49:42 -07001037 antagonists = [subprocess.Popen(['tools/run_tests/antagonist.py'])
Craig Tiller234b6e72015-05-23 10:12:40 -07001038 for _ in range(0, args.antagonists)]
Craig Tillerfe4939f2015-10-06 12:55:36 -07001039 port_server_port = 32767
Craig Tillerf53d9c82015-08-04 14:19:43 -07001040 _start_port_server(port_server_port)
Adele Zhou7cf72112015-11-04 11:18:43 -08001041 resultset = None
Adele Zhou803af152015-11-30 15:16:16 -08001042 num_test_failures = 0
Craig Tiller234b6e72015-05-23 10:12:40 -07001043 try:
David Garcia Quintase90cd372015-05-31 18:15:26 -07001044 infinite_runs = runs_per_test == 0
yang-g6c1fdc62015-08-18 11:57:42 -07001045 one_run = set(
1046 spec
1047 for config in run_configs
1048 for language in languages
Craig Tiller883064c2015-11-04 10:06:10 -08001049 for spec in language.test_specs(config, args)
yang-g6c1fdc62015-08-18 11:57:42 -07001050 if re.search(args.regex, spec.shortname))
David Garcia Quintas79e389f2015-06-02 17:49:42 -07001051 # When running on travis, we want out test runs to be as similar as possible
1052 # for reproducibility purposes.
Craig Tiller883064c2015-11-04 10:06:10 -08001053 if args.travis:
David Garcia Quintas79e389f2015-06-02 17:49:42 -07001054 massaged_one_run = sorted(one_run, key=lambda x: x.shortname)
1055 else:
1056 # whereas otherwise, we want to shuffle things up to give all tests a
1057 # chance to run.
1058 massaged_one_run = list(one_run) # random.shuffle needs an indexable seq.
1059 random.shuffle(massaged_one_run) # which it modifies in-place.
Craig Tillerf7b7c892015-06-22 14:33:25 -07001060 if infinite_runs:
1061 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 -07001062 runs_sequence = (itertools.repeat(massaged_one_run) if infinite_runs
1063 else itertools.repeat(massaged_one_run, runs_per_test))
David Garcia Quintase90cd372015-05-31 18:15:26 -07001064 all_runs = itertools.chain.from_iterable(runs_sequence)
Nicolas "Pixel" Noble5937b5b2015-06-26 02:04:12 +02001065
Adele Zhou803af152015-11-30 15:16:16 -08001066 num_test_failures, resultset = jobset.run(
Adele Zhou2271ab52015-10-28 13:59:14 -07001067 all_runs, check_cancelled, newline_on_success=newline_on_success,
Craig Tiller883064c2015-11-04 10:06:10 -08001068 travis=args.travis, infinite_runs=infinite_runs, maxjobs=args.jobs,
murgatroid998ae409f2015-10-26 16:39:00 -07001069 stop_on_failure=args.stop_on_failure,
Adele Zhouf2ca7bc2015-10-23 15:38:00 -07001070 cache=cache if not xml_report else None,
Adele Zhouf2ca7bc2015-10-23 15:38:00 -07001071 add_env={'GRPC_TEST_PORT_SERVER': 'localhost:%d' % port_server_port})
Adele Zhoud5fffa52015-10-23 15:51:42 -07001072 if resultset:
1073 for k, v in resultset.iteritems():
1074 num_runs, num_failures = _calculate_num_runs_failures(v)
1075 if num_failures == num_runs: # what about infinite_runs???
1076 jobset.message('FAILED', k, do_newline=True)
1077 elif num_failures > 0:
1078 jobset.message(
1079 'FLAKE', '%s [%d/%d runs flaked]' % (k, num_failures, num_runs),
1080 do_newline=True)
1081 else:
1082 jobset.message('PASSED', k, do_newline=True)
Craig Tiller234b6e72015-05-23 10:12:40 -07001083 finally:
1084 for antagonist in antagonists:
1085 antagonist.kill()
Adele Zhou7cf72112015-11-04 11:18:43 -08001086 if xml_report and resultset:
Adele Zhou3bc7ba42015-11-05 10:21:58 -08001087 report_utils.render_junit_xml_report(resultset, xml_report)
Craig Tillerd86a3942015-01-14 12:48:54 -08001088
Adele Zhouf2ca7bc2015-10-23 15:38:00 -07001089 number_failures, _ = jobset.run(
1090 post_tests_steps, maxjobs=1, stop_on_failure=True,
Craig Tiller883064c2015-11-04 10:06:10 -08001091 newline_on_success=newline_on_success, travis=args.travis)
Craig Tillereb9de8b2016-01-08 08:57:41 -08001092
1093 out = []
1094 if number_failures:
1095 out.append(BuildAndRunError.POST_TEST)
1096 if num_test_failures:
1097 out.append(BuildAndRunError.TEST)
Nicolas "Pixel" Noble3fcd3bf2015-10-10 02:30:38 +02001098
Craig Tiller69cd2372015-06-11 09:38:09 -07001099 if cache: cache.save()
1100
Craig Tillereb9de8b2016-01-08 08:57:41 -08001101 return out
ctiller3040cb72015-01-07 12:13:17 -08001102
1103
David Klempner25739582015-02-11 15:57:32 -08001104test_cache = TestCache(runs_per_test == 1)
Craig Tiller547db2b2015-01-30 14:08:39 -08001105test_cache.maybe_load()
Craig Tiller71735182015-01-15 17:07:13 -08001106
ctiller3040cb72015-01-07 12:13:17 -08001107if forever:
Nicolas Noble044db742015-01-14 16:57:24 -08001108 success = True
ctiller3040cb72015-01-07 12:13:17 -08001109 while True:
Craig Tiller42bc87c2015-02-23 08:50:19 -08001110 dw = watch_dirs.DirWatcher(['src', 'include', 'test', 'examples'])
ctiller3040cb72015-01-07 12:13:17 -08001111 initial_time = dw.most_recent_change()
1112 have_files_changed = lambda: dw.most_recent_change() != initial_time
Nicolas Noble044db742015-01-14 16:57:24 -08001113 previous_success = success
Craig Tillereb9de8b2016-01-08 08:57:41 -08001114 errors = _build_and_run(check_cancelled=have_files_changed,
1115 newline_on_success=False,
1116 cache=test_cache,
1117 build_only=args.build_only) == 0
1118 if not previous_success and not errors:
Nicolas Nobleb09078f2015-01-14 18:06:05 -08001119 jobset.message('SUCCESS',
1120 'All tests are now passing properly',
1121 do_newline=True)
Nicolas Noble044db742015-01-14 16:57:24 -08001122 jobset.message('IDLE', 'No change detected')
ctiller3040cb72015-01-07 12:13:17 -08001123 while not have_files_changed():
1124 time.sleep(1)
1125else:
Craig Tillereb9de8b2016-01-08 08:57:41 -08001126 errors = _build_and_run(check_cancelled=lambda: False,
Craig Tiller71735182015-01-15 17:07:13 -08001127 newline_on_success=args.newline_on_success,
Nicolas "Pixel" Noble5937b5b2015-06-26 02:04:12 +02001128 cache=test_cache,
Jan Tattermusch2dd156e2015-12-04 18:26:17 -08001129 xml_report=args.xml_report,
1130 build_only=args.build_only)
Craig Tillereb9de8b2016-01-08 08:57:41 -08001131 if not errors:
Nicolas Nobleb09078f2015-01-14 18:06:05 -08001132 jobset.message('SUCCESS', 'All tests passed', do_newline=True)
1133 else:
1134 jobset.message('FAILED', 'Some tests failed', do_newline=True)
Craig Tillereb9de8b2016-01-08 08:57:41 -08001135 exit_code = 0
1136 if BuildAndRunError.BUILD in errors:
1137 exit_code |= 1
Craig Tiller4f2be362016-01-08 08:59:20 -08001138 if BuildAndRunError.TEST in errors and not args.travis:
Craig Tillereb9de8b2016-01-08 08:57:41 -08001139 exit_code |= 2
Craig Tiller4f2be362016-01-08 08:59:20 -08001140 if BuildAndRunError.POST_TEST in errors:
1141 exit_code |= 4
Craig Tillereb9de8b2016-01-08 08:57:41 -08001142 sys.exit(exit_code)
Craig Tillera0f85172016-01-20 15:56:06 -08001143