| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896 | 
							- #!/usr/bin/env python
 
- # Copyright 2015 gRPC authors.
 
- #
 
- # Licensed under the Apache License, Version 2.0 (the "License");
 
- # you may not use this file except in compliance with the License.
 
- # You may obtain a copy of the License at
 
- #
 
- #     http://www.apache.org/licenses/LICENSE-2.0
 
- #
 
- # Unless required by applicable law or agreed to in writing, software
 
- # distributed under the License is distributed on an "AS IS" BASIS,
 
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
- # See the License for the specific language governing permissions and
 
- # limitations under the License.
 
- """Run tests in parallel."""
 
- from __future__ import print_function
 
- import argparse
 
- import ast
 
- import collections
 
- import glob
 
- import itertools
 
- import json
 
- import logging
 
- import multiprocessing
 
- import os
 
- import os.path
 
- import pipes
 
- import platform
 
- import random
 
- import re
 
- import socket
 
- import subprocess
 
- import sys
 
- import tempfile
 
- import traceback
 
- import time
 
- from six.moves import urllib
 
- import uuid
 
- import six
 
- import python_utils.jobset as jobset
 
- import python_utils.report_utils as report_utils
 
- import python_utils.watch_dirs as watch_dirs
 
- import python_utils.start_port_server as start_port_server
 
- try:
 
-     from python_utils.upload_test_results import upload_results_to_bq
 
- except (ImportError):
 
-     pass  # It's ok to not import because this is only necessary to upload results to BQ.
 
- gcp_utils_dir = os.path.abspath(
 
-     os.path.join(os.path.dirname(__file__), '../gcp/utils'))
 
- sys.path.append(gcp_utils_dir)
 
- _ROOT = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '../..'))
 
- os.chdir(_ROOT)
 
- _FORCE_ENVIRON_FOR_WRAPPERS = {
 
-     'GRPC_VERBOSITY': 'DEBUG',
 
- }
 
- _POLLING_STRATEGIES = {
 
-     'linux': ['epollex', 'epoll1', 'poll', 'poll-cv'],
 
-     'mac': ['poll'],
 
- }
 
- BigQueryTestData = collections.namedtuple('BigQueryTestData', 'name flaky cpu')
 
- def get_bqtest_data(limit=None):
 
-     import big_query_utils
 
-     bq = big_query_utils.create_big_query()
 
-     query = """
 
- SELECT
 
-   filtered_test_name,
 
-   SUM(result != 'PASSED' AND result != 'SKIPPED') > 0 as flaky,
 
-   MAX(cpu_measured) + 0.01 as cpu
 
-   FROM (
 
-   SELECT
 
-     REGEXP_REPLACE(test_name, r'/\d+', '') AS filtered_test_name,
 
-     result, cpu_measured
 
-   FROM
 
-     [grpc-testing:jenkins_test_results.aggregate_results]
 
-   WHERE
 
-     timestamp >= DATE_ADD(CURRENT_DATE(), -1, "WEEK")
 
-     AND platform = '""" + platform_string() + """'
 
-     AND NOT REGEXP_MATCH(job_name, '.*portability.*') )
 
- GROUP BY
 
-   filtered_test_name"""
 
-     if limit:
 
-         query += " limit {}".format(limit)
 
-     query_job = big_query_utils.sync_query_job(bq, 'grpc-testing', query)
 
-     page = bq.jobs().getQueryResults(
 
-         pageToken=None, **query_job['jobReference']).execute(num_retries=3)
 
-     test_data = [
 
-         BigQueryTestData(row['f'][0]['v'], row['f'][1]['v'] == 'true',
 
-                          float(row['f'][2]['v'])) for row in page['rows']
 
-     ]
 
-     return test_data
 
- def platform_string():
 
-     return jobset.platform_string()
 
- _DEFAULT_TIMEOUT_SECONDS = 5 * 60
 
- def run_shell_command(cmd, env=None, cwd=None):
 
-     try:
 
-         subprocess.check_output(cmd, shell=True, env=env, cwd=cwd)
 
-     except subprocess.CalledProcessError as e:
 
-         logging.exception(
 
-             "Error while running command '%s'. Exit status %d. Output:\n%s",
 
-             e.cmd, e.returncode, e.output)
 
-         raise
 
- def max_parallel_tests_for_current_platform():
 
-     # Too much test parallelization has only been seen to be a problem
 
-     # so far on windows.
 
-     if jobset.platform_string() == 'windows':
 
-         return 64
 
-     return 1024
 
- # SimpleConfig: just compile with CONFIG=config, and run the binary to test
 
- class Config(object):
 
-     def __init__(self,
 
-                  config,
 
-                  environ=None,
 
-                  timeout_multiplier=1,
 
-                  tool_prefix=[],
 
-                  iomgr_platform='native'):
 
-         if environ is None:
 
-             environ = {}
 
-         self.build_config = config
 
-         self.environ = environ
 
-         self.environ['CONFIG'] = config
 
-         self.tool_prefix = tool_prefix
 
-         self.timeout_multiplier = timeout_multiplier
 
-         self.iomgr_platform = iomgr_platform
 
-     def job_spec(self,
 
-                  cmdline,
 
-                  timeout_seconds=_DEFAULT_TIMEOUT_SECONDS,
 
-                  shortname=None,
 
-                  environ={},
 
-                  cpu_cost=1.0,
 
-                  flaky=False):
 
-         """Construct a jobset.JobSpec for a test under this config
 
-        Args:
 
-          cmdline:      a list of strings specifying the command line the test
 
-                        would like to run
 
-     """
 
-         actual_environ = self.environ.copy()
 
-         for k, v in environ.items():
 
-             actual_environ[k] = v
 
-         if not flaky and shortname and shortname in flaky_tests:
 
-             flaky = True
 
-         if shortname in shortname_to_cpu:
 
-             cpu_cost = shortname_to_cpu[shortname]
 
-         return jobset.JobSpec(
 
-             cmdline=self.tool_prefix + cmdline,
 
-             shortname=shortname,
 
-             environ=actual_environ,
 
-             cpu_cost=cpu_cost,
 
-             timeout_seconds=(self.timeout_multiplier * timeout_seconds
 
-                              if timeout_seconds else None),
 
-             flake_retries=4 if flaky or args.allow_flakes else 0,
 
-             timeout_retries=1 if flaky or args.allow_flakes else 0)
 
- def get_c_tests(travis, test_lang):
 
-     out = []
 
-     platforms_str = 'ci_platforms' if travis else 'platforms'
 
-     with open('tools/run_tests/generated/tests.json') as f:
 
-         js = json.load(f)
 
-         return [
 
-             tgt for tgt in js
 
-             if tgt['language'] == test_lang and platform_string() in
 
-             tgt[platforms_str] and not (travis and tgt['flaky'])
 
-         ]
 
- def _check_compiler(compiler, supported_compilers):
 
-     if compiler not in supported_compilers:
 
-         raise Exception(
 
-             'Compiler %s not supported (on this platform).' % compiler)
 
- def _check_arch(arch, supported_archs):
 
-     if arch not in supported_archs:
 
-         raise Exception('Architecture %s not supported.' % arch)
 
- def _is_use_docker_child():
 
-     """Returns True if running running as a --use_docker child."""
 
-     return True if os.getenv('RUN_TESTS_COMMAND') else False
 
- _PythonConfigVars = collections.namedtuple('_ConfigVars', [
 
-     'shell',
 
-     'builder',
 
-     'builder_prefix_arguments',
 
-     'venv_relative_python',
 
-     'toolchain',
 
-     'runner',
 
-     'test_name',
 
-     'iomgr_platform',
 
- ])
 
- def _python_config_generator(name, major, minor, bits, config_vars):
 
-     name += '_' + config_vars.iomgr_platform
 
-     return PythonConfig(
 
-         name, config_vars.shell + config_vars.builder +
 
-         config_vars.builder_prefix_arguments + [
 
-             _python_pattern_function(major=major, minor=minor, bits=bits)
 
-         ] + [name] + config_vars.venv_relative_python + config_vars.toolchain,
 
-         config_vars.shell + config_vars.runner + [
 
-             os.path.join(name, config_vars.venv_relative_python[0]),
 
-             config_vars.test_name
 
-         ])
 
- def _pypy_config_generator(name, major, config_vars):
 
-     return PythonConfig(
 
-         name,
 
-         config_vars.shell + config_vars.builder +
 
-         config_vars.builder_prefix_arguments + [
 
-             _pypy_pattern_function(major=major)
 
-         ] + [name] + config_vars.venv_relative_python + config_vars.toolchain,
 
-         config_vars.shell + config_vars.runner +
 
-         [os.path.join(name, config_vars.venv_relative_python[0])])
 
- def _python_pattern_function(major, minor, bits):
 
-     # Bit-ness is handled by the test machine's environment
 
-     if os.name == "nt":
 
-         if bits == "64":
 
-             return '/c/Python{major}{minor}/python.exe'.format(
 
-                 major=major, minor=minor, bits=bits)
 
-         else:
 
-             return '/c/Python{major}{minor}_{bits}bits/python.exe'.format(
 
-                 major=major, minor=minor, bits=bits)
 
-     else:
 
-         return 'python{major}.{minor}'.format(major=major, minor=minor)
 
- def _pypy_pattern_function(major):
 
-     if major == '2':
 
-         return 'pypy'
 
-     elif major == '3':
 
-         return 'pypy3'
 
-     else:
 
-         raise ValueError("Unknown PyPy major version")
 
- class CLanguage(object):
 
-     def __init__(self, make_target, test_lang):
 
-         self.make_target = make_target
 
-         self.platform = platform_string()
 
-         self.test_lang = test_lang
 
-     def configure(self, config, args):
 
-         self.config = config
 
-         self.args = args
 
-         if self.platform == 'windows':
 
-             _check_compiler(
 
-                 self.args.compiler,
 
-                 ['default', 'cmake', 'cmake_vs2015', 'cmake_vs2017'])
 
-             _check_arch(self.args.arch, ['default', 'x64', 'x86'])
 
-             self._cmake_generator_option = 'Visual Studio 15 2017' if self.args.compiler == 'cmake_vs2017' else 'Visual Studio 14 2015'
 
-             self._cmake_arch_option = 'x64' if self.args.arch == 'x64' else 'Win32'
 
-             self._use_cmake = True
 
-             self._make_options = []
 
-         elif self.args.compiler == 'cmake':
 
-             _check_arch(self.args.arch, ['default'])
 
-             self._use_cmake = True
 
-             self._docker_distro = 'jessie'
 
-             self._make_options = []
 
-         else:
 
-             self._use_cmake = False
 
-             self._docker_distro, self._make_options = self._compiler_options(
 
-                 self.args.use_docker, self.args.compiler)
 
-         if args.iomgr_platform == "uv":
 
-             cflags = '-DGRPC_UV -DGRPC_CUSTOM_IOMGR_THREAD_CHECK -DGRPC_CUSTOM_SOCKET '
 
-             try:
 
-                 cflags += subprocess.check_output(
 
-                     ['pkg-config', '--cflags', 'libuv']).strip() + ' '
 
-             except (subprocess.CalledProcessError, OSError):
 
-                 pass
 
-             try:
 
-                 ldflags = subprocess.check_output(
 
-                     ['pkg-config', '--libs', 'libuv']).strip() + ' '
 
-             except (subprocess.CalledProcessError, OSError):
 
-                 ldflags = '-luv '
 
-             self._make_options += [
 
-                 'EXTRA_CPPFLAGS={}'.format(cflags),
 
-                 'EXTRA_LDLIBS={}'.format(ldflags)
 
-             ]
 
-     def test_specs(self):
 
-         out = []
 
-         binaries = get_c_tests(self.args.travis, self.test_lang)
 
-         for target in binaries:
 
-             if self._use_cmake and target.get('boringssl', False):
 
-                 # cmake doesn't build boringssl tests
 
-                 continue
 
-             auto_timeout_scaling = target.get('auto_timeout_scaling', True)
 
-             polling_strategies = (_POLLING_STRATEGIES.get(
 
-                 self.platform, ['all']) if target.get('uses_polling', True) else
 
-                                   ['none'])
 
-             if self.args.iomgr_platform == 'uv':
 
-                 polling_strategies = ['all']
 
-             for polling_strategy in polling_strategies:
 
-                 env = {
 
-                     'GRPC_DEFAULT_SSL_ROOTS_FILE_PATH':
 
-                     _ROOT + '/src/core/tsi/test_creds/ca.pem',
 
-                     'GRPC_POLL_STRATEGY':
 
-                     polling_strategy,
 
-                     'GRPC_VERBOSITY':
 
-                     'DEBUG'
 
-                 }
 
-                 resolver = os.environ.get('GRPC_DNS_RESOLVER', None)
 
-                 if resolver:
 
-                     env['GRPC_DNS_RESOLVER'] = resolver
 
-                 shortname_ext = '' if polling_strategy == 'all' else ' GRPC_POLL_STRATEGY=%s' % polling_strategy
 
-                 if polling_strategy in target.get('excluded_poll_engines', []):
 
-                     continue
 
-                 timeout_scaling = 1
 
-                 if auto_timeout_scaling:
 
-                     config = self.args.config
 
-                     if ('asan' in config or config == 'msan' or
 
-                             config == 'tsan' or config == 'ubsan' or
 
-                             config == 'helgrind' or config == 'memcheck'):
 
-                         # Scale overall test timeout if running under various sanitizers.
 
-                         # scaling value is based on historical data analysis
 
-                         timeout_scaling *= 3
 
-                     elif polling_strategy == 'poll-cv':
 
-                         # scale test timeout if running with poll-cv
 
-                         # sanitizer and poll-cv scaling is not cumulative to ensure
 
-                         # reasonable timeout values.
 
-                         # TODO(jtattermusch): based on historical data and 5min default
 
-                         # test timeout poll-cv scaling is currently not useful.
 
-                         # Leaving here so it can be reintroduced if the default test timeout
 
-                         # is decreased in the future.
 
-                         timeout_scaling *= 1
 
-                 if self.config.build_config in target['exclude_configs']:
 
-                     continue
 
-                 if self.args.iomgr_platform in target.get('exclude_iomgrs', []):
 
-                     continue
 
-                 if self.platform == 'windows':
 
-                     binary = 'cmake/build/%s/%s.exe' % (
 
-                         _MSBUILD_CONFIG[self.config.build_config],
 
-                         target['name'])
 
-                 else:
 
-                     if self._use_cmake:
 
-                         binary = 'cmake/build/%s' % target['name']
 
-                     else:
 
-                         binary = 'bins/%s/%s' % (self.config.build_config,
 
-                                                  target['name'])
 
-                 cpu_cost = target['cpu_cost']
 
-                 if cpu_cost == 'capacity':
 
-                     cpu_cost = multiprocessing.cpu_count()
 
-                 if os.path.isfile(binary):
 
-                     list_test_command = None
 
-                     filter_test_command = None
 
-                     # these are the flag defined by gtest and benchmark framework to list
 
-                     # and filter test runs. We use them to split each individual test
 
-                     # into its own JobSpec, and thus into its own process.
 
-                     if 'benchmark' in target and target['benchmark']:
 
-                         with open(os.devnull, 'w') as fnull:
 
-                             tests = subprocess.check_output(
 
-                                 [binary, '--benchmark_list_tests'],
 
-                                 stderr=fnull)
 
-                         for line in tests.split('\n'):
 
-                             test = line.strip()
 
-                             if not test: continue
 
-                             cmdline = [binary,
 
-                                        '--benchmark_filter=%s$' % test
 
-                                       ] + target['args']
 
-                             out.append(
 
-                                 self.config.job_spec(
 
-                                     cmdline,
 
-                                     shortname='%s %s' % (' '.join(cmdline),
 
-                                                          shortname_ext),
 
-                                     cpu_cost=cpu_cost,
 
-                                     timeout_seconds=target.get(
 
-                                         'timeout_seconds',
 
-                                         _DEFAULT_TIMEOUT_SECONDS) *
 
-                                     timeout_scaling,
 
-                                     environ=env))
 
-                     elif 'gtest' in target and target['gtest']:
 
-                         # here we parse the output of --gtest_list_tests to build up a complete
 
-                         # list of the tests contained in a binary for each test, we then
 
-                         # add a job to run, filtering for just that test.
 
-                         with open(os.devnull, 'w') as fnull:
 
-                             tests = subprocess.check_output(
 
-                                 [binary, '--gtest_list_tests'], stderr=fnull)
 
-                         base = None
 
-                         for line in tests.split('\n'):
 
-                             i = line.find('#')
 
-                             if i >= 0: line = line[:i]
 
-                             if not line: continue
 
-                             if line[0] != ' ':
 
-                                 base = line.strip()
 
-                             else:
 
-                                 assert base is not None
 
-                                 assert line[1] == ' '
 
-                                 test = base + line.strip()
 
-                                 cmdline = [binary,
 
-                                            '--gtest_filter=%s' % test
 
-                                           ] + target['args']
 
-                                 out.append(
 
-                                     self.config.job_spec(
 
-                                         cmdline,
 
-                                         shortname='%s %s' % (' '.join(cmdline),
 
-                                                              shortname_ext),
 
-                                         cpu_cost=cpu_cost,
 
-                                         timeout_seconds=target.get(
 
-                                             'timeout_seconds',
 
-                                             _DEFAULT_TIMEOUT_SECONDS) *
 
-                                         timeout_scaling,
 
-                                         environ=env))
 
-                     else:
 
-                         cmdline = [binary] + target['args']
 
-                         shortname = target.get('shortname', ' '.join(
 
-                             pipes.quote(arg) for arg in cmdline))
 
-                         shortname += shortname_ext
 
-                         out.append(
 
-                             self.config.job_spec(
 
-                                 cmdline,
 
-                                 shortname=shortname,
 
-                                 cpu_cost=cpu_cost,
 
-                                 flaky=target.get('flaky', False),
 
-                                 timeout_seconds=target.get(
 
-                                     'timeout_seconds', _DEFAULT_TIMEOUT_SECONDS)
 
-                                 * timeout_scaling,
 
-                                 environ=env))
 
-                 elif self.args.regex == '.*' or self.platform == 'windows':
 
-                     print('\nWARNING: binary not found, skipping', binary)
 
-         return sorted(out)
 
-     def make_targets(self):
 
-         if self.platform == 'windows':
 
-             # don't build tools on windows just yet
 
-             return ['buildtests_%s' % self.make_target]
 
-         return [
 
-             'buildtests_%s' % self.make_target,
 
-             'tools_%s' % self.make_target, 'check_epollexclusive'
 
-         ]
 
-     def make_options(self):
 
-         return self._make_options
 
-     def pre_build_steps(self):
 
-         if self.platform == 'windows':
 
-             return [[
 
-                 'tools\\run_tests\\helper_scripts\\pre_build_cmake.bat',
 
-                 self._cmake_generator_option, self._cmake_arch_option
 
-             ]]
 
-         elif self._use_cmake:
 
-             return [['tools/run_tests/helper_scripts/pre_build_cmake.sh']]
 
-         else:
 
-             return []
 
-     def build_steps(self):
 
-         return []
 
-     def post_tests_steps(self):
 
-         if self.platform == 'windows':
 
-             return []
 
-         else:
 
-             return [['tools/run_tests/helper_scripts/post_tests_c.sh']]
 
-     def makefile_name(self):
 
-         if self._use_cmake:
 
-             return 'cmake/build/Makefile'
 
-         else:
 
-             return 'Makefile'
 
-     def _clang_make_options(self, version_suffix=''):
 
-         if self.args.config == 'ubsan':
 
-             return [
 
-                 'CC=clang%s' % version_suffix,
 
-                 'CXX=clang++%s' % version_suffix,
 
-                 'LD=clang++%s' % version_suffix,
 
-                 'LDXX=clang++%s' % version_suffix
 
-             ]
 
-         return [
 
-             'CC=clang%s' % version_suffix,
 
-             'CXX=clang++%s' % version_suffix,
 
-             'LD=clang%s' % version_suffix,
 
-             'LDXX=clang++%s' % version_suffix
 
-         ]
 
-     def _gcc_make_options(self, version_suffix):
 
-         return [
 
-             'CC=gcc%s' % version_suffix,
 
-             'CXX=g++%s' % version_suffix,
 
-             'LD=gcc%s' % version_suffix,
 
-             'LDXX=g++%s' % version_suffix
 
-         ]
 
-     def _compiler_options(self, use_docker, compiler):
 
-         """Returns docker distro and make options to use for given compiler."""
 
-         if not use_docker and not _is_use_docker_child():
 
-             _check_compiler(compiler, ['default'])
 
-         if compiler == 'gcc4.9' or compiler == 'default':
 
-             return ('jessie', [])
 
-         elif compiler == 'gcc4.8':
 
-             return ('jessie', self._gcc_make_options(version_suffix='-4.8'))
 
-         elif compiler == 'gcc5.3':
 
-             return ('ubuntu1604', [])
 
-         elif compiler == 'gcc7.2':
 
-             return ('ubuntu1710', [])
 
-         elif compiler == 'gcc_musl':
 
-             return ('alpine', [])
 
-         elif compiler == 'clang3.4':
 
-             # on ubuntu1404, clang-3.4 alias doesn't exist, just use 'clang'
 
-             return ('ubuntu1404', self._clang_make_options())
 
-         elif compiler == 'clang3.5':
 
-             return ('jessie', self._clang_make_options(version_suffix='-3.5'))
 
-         elif compiler == 'clang3.6':
 
-             return ('ubuntu1604',
 
-                     self._clang_make_options(version_suffix='-3.6'))
 
-         elif compiler == 'clang3.7':
 
-             return ('ubuntu1604',
 
-                     self._clang_make_options(version_suffix='-3.7'))
 
-         elif compiler == 'clang7.0':
 
-             # clang++-7.0 alias doesn't exist and there are no other clang versions
 
-             # installed.
 
-             return ('sanitizers_jessie', self._clang_make_options())
 
-         else:
 
-             raise Exception('Compiler %s not supported.' % compiler)
 
-     def dockerfile_dir(self):
 
-         return 'tools/dockerfile/test/cxx_%s_%s' % (
 
-             self._docker_distro, _docker_arch_suffix(self.args.arch))
 
-     def __str__(self):
 
-         return self.make_target
 
- # This tests Node on grpc/grpc-node and will become the standard for Node testing
 
- class RemoteNodeLanguage(object):
 
-     def __init__(self):
 
-         self.platform = platform_string()
 
-     def configure(self, config, args):
 
-         self.config = config
 
-         self.args = args
 
-         # Note: electron ABI only depends on major and minor version, so that's all
 
-         # we should specify in the compiler argument
 
-         _check_compiler(self.args.compiler, [
 
-             'default', 'node0.12', 'node4', 'node5', 'node6', 'node7', 'node8',
 
-             'electron1.3', 'electron1.6'
 
-         ])
 
-         if self.args.compiler == 'default':
 
-             self.runtime = 'node'
 
-             self.node_version = '8'
 
-         else:
 
-             if self.args.compiler.startswith('electron'):
 
-                 self.runtime = 'electron'
 
-                 self.node_version = self.args.compiler[8:]
 
-             else:
 
-                 self.runtime = 'node'
 
-                 # Take off the word "node"
 
-                 self.node_version = self.args.compiler[4:]
 
-     # TODO: update with Windows/electron scripts when available for grpc/grpc-node
 
-     def test_specs(self):
 
-         if self.platform == 'windows':
 
-             return [
 
-                 self.config.job_spec(
 
-                     ['tools\\run_tests\\helper_scripts\\run_node.bat'])
 
-             ]
 
-         else:
 
-             return [
 
-                 self.config.job_spec(
 
-                     ['tools/run_tests/helper_scripts/run_grpc-node.sh'],
 
-                     None,
 
-                     environ=_FORCE_ENVIRON_FOR_WRAPPERS)
 
-             ]
 
-     def pre_build_steps(self):
 
-         return []
 
-     def make_targets(self):
 
-         return []
 
-     def make_options(self):
 
-         return []
 
-     def build_steps(self):
 
-         return []
 
-     def post_tests_steps(self):
 
-         return []
 
-     def makefile_name(self):
 
-         return 'Makefile'
 
-     def dockerfile_dir(self):
 
-         return 'tools/dockerfile/test/node_jessie_%s' % _docker_arch_suffix(
 
-             self.args.arch)
 
-     def __str__(self):
 
-         return 'grpc-node'
 
- class PhpLanguage(object):
 
-     def configure(self, config, args):
 
-         self.config = config
 
-         self.args = args
 
-         _check_compiler(self.args.compiler, ['default'])
 
-         self._make_options = ['EMBED_OPENSSL=true', 'EMBED_ZLIB=true']
 
-     def test_specs(self):
 
-         return [
 
-             self.config.job_spec(
 
-                 ['src/php/bin/run_tests.sh'],
 
-                 environ=_FORCE_ENVIRON_FOR_WRAPPERS)
 
-         ]
 
-     def pre_build_steps(self):
 
-         return []
 
-     def make_targets(self):
 
-         return ['static_c', 'shared_c']
 
-     def make_options(self):
 
-         return self._make_options
 
-     def build_steps(self):
 
-         return [['tools/run_tests/helper_scripts/build_php.sh']]
 
-     def post_tests_steps(self):
 
-         return [['tools/run_tests/helper_scripts/post_tests_php.sh']]
 
-     def makefile_name(self):
 
-         return 'Makefile'
 
-     def dockerfile_dir(self):
 
-         return 'tools/dockerfile/test/php_jessie_%s' % _docker_arch_suffix(
 
-             self.args.arch)
 
-     def __str__(self):
 
-         return 'php'
 
- class Php7Language(object):
 
-     def configure(self, config, args):
 
-         self.config = config
 
-         self.args = args
 
-         _check_compiler(self.args.compiler, ['default'])
 
-         self._make_options = ['EMBED_OPENSSL=true', 'EMBED_ZLIB=true']
 
-     def test_specs(self):
 
-         return [
 
-             self.config.job_spec(
 
-                 ['src/php/bin/run_tests.sh'],
 
-                 environ=_FORCE_ENVIRON_FOR_WRAPPERS)
 
-         ]
 
-     def pre_build_steps(self):
 
-         return []
 
-     def make_targets(self):
 
-         return ['static_c', 'shared_c']
 
-     def make_options(self):
 
-         return self._make_options
 
-     def build_steps(self):
 
-         return [['tools/run_tests/helper_scripts/build_php.sh']]
 
-     def post_tests_steps(self):
 
-         return [['tools/run_tests/helper_scripts/post_tests_php.sh']]
 
-     def makefile_name(self):
 
-         return 'Makefile'
 
-     def dockerfile_dir(self):
 
-         return 'tools/dockerfile/test/php7_jessie_%s' % _docker_arch_suffix(
 
-             self.args.arch)
 
-     def __str__(self):
 
-         return 'php7'
 
- class PythonConfig(
 
-         collections.namedtuple('PythonConfig', ['name', 'build', 'run'])):
 
-     """Tuple of commands (named s.t. 'what it says on the tin' applies)"""
 
- class PythonLanguage(object):
 
-     def configure(self, config, args):
 
-         self.config = config
 
-         self.args = args
 
-         self.pythons = self._get_pythons(self.args)
 
-     def test_specs(self):
 
-         # load list of known test suites
 
-         with open(
 
-                 'src/python/grpcio_tests/tests/tests.json') as tests_json_file:
 
-             tests_json = json.load(tests_json_file)
 
-         environment = dict(_FORCE_ENVIRON_FOR_WRAPPERS)
 
-         return [
 
-             self.config.job_spec(
 
-                 config.run,
 
-                 timeout_seconds=5 * 60,
 
-                 environ=dict(
 
-                     list(environment.items()) + [(
 
-                         'GRPC_PYTHON_TESTRUNNER_FILTER', str(suite_name))]),
 
-                 shortname='%s.test.%s' % (config.name, suite_name),
 
-             ) for suite_name in tests_json for config in self.pythons
 
-         ]
 
-     def pre_build_steps(self):
 
-         return []
 
-     def make_targets(self):
 
-         return []
 
-     def make_options(self):
 
-         return []
 
-     def build_steps(self):
 
-         return [config.build for config in self.pythons]
 
-     def post_tests_steps(self):
 
-         if self.config.build_config != 'gcov':
 
-             return []
 
-         else:
 
-             return [['tools/run_tests/helper_scripts/post_tests_python.sh']]
 
-     def makefile_name(self):
 
-         return 'Makefile'
 
-     def dockerfile_dir(self):
 
-         return 'tools/dockerfile/test/python_%s_%s' % (
 
-             self.python_manager_name(), _docker_arch_suffix(self.args.arch))
 
-     def python_manager_name(self):
 
-         if self.args.compiler in [
 
-                 'python2.7', 'python3.5', 'python3.6', 'python3.7'
 
-         ]:
 
-             return 'stretch_' + self.args.compiler[len('python'):]
 
-         elif self.args.compiler == 'python_alpine':
 
-             return 'alpine'
 
-         else:
 
-             return 'jessie'
 
-     def _get_pythons(self, args):
 
-         if args.arch == 'x86':
 
-             bits = '32'
 
-         else:
 
-             bits = '64'
 
-         if os.name == 'nt':
 
-             shell = ['bash']
 
-             builder = [
 
-                 os.path.abspath(
 
-                     'tools/run_tests/helper_scripts/build_python_msys2.sh')
 
-             ]
 
-             builder_prefix_arguments = ['MINGW{}'.format(bits)]
 
-             venv_relative_python = ['Scripts/python.exe']
 
-             toolchain = ['mingw32']
 
-         else:
 
-             shell = []
 
-             builder = [
 
-                 os.path.abspath(
 
-                     'tools/run_tests/helper_scripts/build_python.sh')
 
-             ]
 
-             builder_prefix_arguments = []
 
-             venv_relative_python = ['bin/python']
 
-             toolchain = ['unix']
 
-         test_command = 'test_lite'
 
-         if args.iomgr_platform == 'gevent':
 
-             test_command = 'test_gevent'
 
-         runner = [
 
-             os.path.abspath('tools/run_tests/helper_scripts/run_python.sh')
 
-         ]
 
-         config_vars = _PythonConfigVars(
 
-             shell, builder, builder_prefix_arguments, venv_relative_python,
 
-             toolchain, runner, test_command, args.iomgr_platform)
 
-         python27_config = _python_config_generator(
 
-             name='py27',
 
-             major='2',
 
-             minor='7',
 
-             bits=bits,
 
-             config_vars=config_vars)
 
-         python34_config = _python_config_generator(
 
-             name='py34',
 
-             major='3',
 
-             minor='4',
 
-             bits=bits,
 
-             config_vars=config_vars)
 
-         python35_config = _python_config_generator(
 
-             name='py35',
 
-             major='3',
 
-             minor='5',
 
-             bits=bits,
 
-             config_vars=config_vars)
 
-         python36_config = _python_config_generator(
 
-             name='py36',
 
-             major='3',
 
-             minor='6',
 
-             bits=bits,
 
-             config_vars=config_vars)
 
-         python37_config = _python_config_generator(
 
-             name='py37',
 
-             major='3',
 
-             minor='7',
 
-             bits=bits,
 
-             config_vars=config_vars)
 
-         pypy27_config = _pypy_config_generator(
 
-             name='pypy', major='2', config_vars=config_vars)
 
-         pypy32_config = _pypy_config_generator(
 
-             name='pypy3', major='3', config_vars=config_vars)
 
-         if args.compiler == 'default':
 
-             if os.name == 'nt':
 
-                 return (python35_config,)
 
-             else:
 
-                 return (
 
-                     python27_config,
 
-                     python34_config,
 
-                 )
 
-         elif args.compiler == 'python2.7':
 
-             return (python27_config,)
 
-         elif args.compiler == 'python3.4':
 
-             return (python34_config,)
 
-         elif args.compiler == 'python3.5':
 
-             return (python35_config,)
 
-         elif args.compiler == 'python3.6':
 
-             return (python36_config,)
 
-         elif args.compiler == 'python3.7':
 
-             return (python37_config,)
 
-         elif args.compiler == 'pypy':
 
-             return (pypy27_config,)
 
-         elif args.compiler == 'pypy3':
 
-             return (pypy32_config,)
 
-         elif args.compiler == 'python_alpine':
 
-             return (python27_config,)
 
-         elif args.compiler == 'all_the_cpythons':
 
-             return (
 
-                 python27_config,
 
-                 python34_config,
 
-                 python35_config,
 
-                 python36_config,
 
-                 python37_config,
 
-             )
 
-         else:
 
-             raise Exception('Compiler %s not supported.' % args.compiler)
 
-     def __str__(self):
 
-         return 'python'
 
- class RubyLanguage(object):
 
-     def configure(self, config, args):
 
-         self.config = config
 
-         self.args = args
 
-         _check_compiler(self.args.compiler, ['default'])
 
-     def test_specs(self):
 
-         tests = [
 
-             self.config.job_spec(
 
-                 ['tools/run_tests/helper_scripts/run_ruby.sh'],
 
-                 timeout_seconds=10 * 60,
 
-                 environ=_FORCE_ENVIRON_FOR_WRAPPERS)
 
-         ]
 
-         tests.append(
 
-             self.config.job_spec(
 
-                 ['tools/run_tests/helper_scripts/run_ruby_end2end_tests.sh'],
 
-                 timeout_seconds=20 * 60,
 
-                 environ=_FORCE_ENVIRON_FOR_WRAPPERS))
 
-         return tests
 
-     def pre_build_steps(self):
 
-         return [['tools/run_tests/helper_scripts/pre_build_ruby.sh']]
 
-     def make_targets(self):
 
-         return []
 
-     def make_options(self):
 
-         return []
 
-     def build_steps(self):
 
-         return [['tools/run_tests/helper_scripts/build_ruby.sh']]
 
-     def post_tests_steps(self):
 
-         return [['tools/run_tests/helper_scripts/post_tests_ruby.sh']]
 
-     def makefile_name(self):
 
-         return 'Makefile'
 
-     def dockerfile_dir(self):
 
-         return 'tools/dockerfile/test/ruby_jessie_%s' % _docker_arch_suffix(
 
-             self.args.arch)
 
-     def __str__(self):
 
-         return 'ruby'
 
- class CSharpLanguage(object):
 
-     def __init__(self):
 
-         self.platform = platform_string()
 
-     def configure(self, config, args):
 
-         self.config = config
 
-         self.args = args
 
-         if self.platform == 'windows':
 
-             _check_compiler(self.args.compiler, ['default', 'coreclr'])
 
-             _check_arch(self.args.arch, ['default'])
 
-             self._cmake_arch_option = 'x64'
 
-         else:
 
-             _check_compiler(self.args.compiler, ['default', 'coreclr'])
 
-             self._docker_distro = 'jessie'
 
-     def test_specs(self):
 
-         with open('src/csharp/tests.json') as f:
 
-             tests_by_assembly = json.load(f)
 
-         msbuild_config = _MSBUILD_CONFIG[self.config.build_config]
 
-         nunit_args = ['--labels=All', '--noresult', '--workers=1']
 
-         assembly_subdir = 'bin/%s' % msbuild_config
 
-         assembly_extension = '.exe'
 
-         if self.args.compiler == 'coreclr':
 
-             assembly_subdir += '/netcoreapp1.0'
 
-             runtime_cmd = ['dotnet', 'exec']
 
-             assembly_extension = '.dll'
 
-         else:
 
-             assembly_subdir += '/net45'
 
-             if self.platform == 'windows':
 
-                 runtime_cmd = []
 
-             elif self.platform == 'mac':
 
-                 # mono before version 5.2 on MacOS defaults to 32bit runtime
 
-                 runtime_cmd = ['mono', '--arch=64']
 
-             else:
 
-                 runtime_cmd = ['mono']
 
-         specs = []
 
-         for assembly in six.iterkeys(tests_by_assembly):
 
-             assembly_file = 'src/csharp/%s/%s/%s%s' % (assembly,
 
-                                                        assembly_subdir,
 
-                                                        assembly,
 
-                                                        assembly_extension)
 
-             if self.config.build_config != 'gcov' or self.platform != 'windows':
 
-                 # normally, run each test as a separate process
 
-                 for test in tests_by_assembly[assembly]:
 
-                     cmdline = runtime_cmd + [assembly_file,
 
-                                              '--test=%s' % test] + nunit_args
 
-                     specs.append(
 
-                         self.config.job_spec(
 
-                             cmdline,
 
-                             shortname='csharp.%s' % test,
 
-                             environ=_FORCE_ENVIRON_FOR_WRAPPERS))
 
-             else:
 
-                 # For C# test coverage, run all tests from the same assembly at once
 
-                 # using OpenCover.Console (only works on Windows).
 
-                 cmdline = [
 
-                     'src\\csharp\\packages\\OpenCover.4.6.519\\tools\\OpenCover.Console.exe',
 
-                     '-target:%s' % assembly_file, '-targetdir:src\\csharp',
 
-                     '-targetargs:%s' % ' '.join(nunit_args),
 
-                     '-filter:+[Grpc.Core]*', '-register:user',
 
-                     '-output:src\\csharp\\coverage_csharp_%s.xml' % assembly
 
-                 ]
 
-                 # set really high cpu_cost to make sure instances of OpenCover.Console run exclusively
 
-                 # to prevent problems with registering the profiler.
 
-                 run_exclusive = 1000000
 
-                 specs.append(
 
-                     self.config.job_spec(
 
-                         cmdline,
 
-                         shortname='csharp.coverage.%s' % assembly,
 
-                         cpu_cost=run_exclusive,
 
-                         environ=_FORCE_ENVIRON_FOR_WRAPPERS))
 
-         return specs
 
-     def pre_build_steps(self):
 
-         if self.platform == 'windows':
 
-             return [[
 
-                 'tools\\run_tests\\helper_scripts\\pre_build_csharp.bat',
 
-                 self._cmake_arch_option
 
-             ]]
 
-         else:
 
-             return [['tools/run_tests/helper_scripts/pre_build_csharp.sh']]
 
-     def make_targets(self):
 
-         return ['grpc_csharp_ext']
 
-     def make_options(self):
 
-         return []
 
-     def build_steps(self):
 
-         if self.platform == 'windows':
 
-             return [['tools\\run_tests\\helper_scripts\\build_csharp.bat']]
 
-         else:
 
-             return [['tools/run_tests/helper_scripts/build_csharp.sh']]
 
-     def post_tests_steps(self):
 
-         if self.platform == 'windows':
 
-             return [['tools\\run_tests\\helper_scripts\\post_tests_csharp.bat']]
 
-         else:
 
-             return [['tools/run_tests/helper_scripts/post_tests_csharp.sh']]
 
-     def makefile_name(self):
 
-         if self.platform == 'windows':
 
-             return 'cmake/build/%s/Makefile' % self._cmake_arch_option
 
-         else:
 
-             # no need to set x86 specific flags as run_tests.py
 
-             # currently forbids x86 C# builds on both Linux and MacOS.
 
-             return 'cmake/build/Makefile'
 
-     def dockerfile_dir(self):
 
-         return 'tools/dockerfile/test/csharp_%s_%s' % (
 
-             self._docker_distro, _docker_arch_suffix(self.args.arch))
 
-     def __str__(self):
 
-         return 'csharp'
 
- class ObjCLanguage(object):
 
-     def configure(self, config, args):
 
-         self.config = config
 
-         self.args = args
 
-         _check_compiler(self.args.compiler, ['default'])
 
-     def test_specs(self):
 
-         return [
 
-             self.config.job_spec(
 
-                 ['src/objective-c/tests/run_tests.sh'],
 
-                 timeout_seconds=60 * 60,
 
-                 shortname='objc-tests',
 
-                 cpu_cost=1e6,
 
-                 environ=_FORCE_ENVIRON_FOR_WRAPPERS),
 
-             self.config.job_spec(
 
-                 ['src/objective-c/tests/run_plugin_tests.sh'],
 
-                 timeout_seconds=60 * 60,
 
-                 shortname='objc-plugin-tests',
 
-                 cpu_cost=1e6,
 
-                 environ=_FORCE_ENVIRON_FOR_WRAPPERS),
 
-             self.config.job_spec(
 
-                 ['src/objective-c/tests/build_one_example.sh'],
 
-                 timeout_seconds=10 * 60,
 
-                 shortname='objc-build-example-helloworld',
 
-                 cpu_cost=1e6,
 
-                 environ={
 
-                     'SCHEME': 'HelloWorld',
 
-                     'EXAMPLE_PATH': 'examples/objective-c/helloworld'
 
-                 }),
 
-             self.config.job_spec(
 
-                 ['src/objective-c/tests/build_one_example.sh'],
 
-                 timeout_seconds=10 * 60,
 
-                 shortname='objc-build-example-routeguide',
 
-                 cpu_cost=1e6,
 
-                 environ={
 
-                     'SCHEME': 'RouteGuideClient',
 
-                     'EXAMPLE_PATH': 'examples/objective-c/route_guide'
 
-                 }),
 
-             self.config.job_spec(
 
-                 ['src/objective-c/tests/build_one_example.sh'],
 
-                 timeout_seconds=10 * 60,
 
-                 shortname='objc-build-example-authsample',
 
-                 cpu_cost=1e6,
 
-                 environ={
 
-                     'SCHEME': 'AuthSample',
 
-                     'EXAMPLE_PATH': 'examples/objective-c/auth_sample'
 
-                 }),
 
-             self.config.job_spec(
 
-                 ['src/objective-c/tests/build_one_example.sh'],
 
-                 timeout_seconds=10 * 60,
 
-                 shortname='objc-build-example-sample',
 
-                 cpu_cost=1e6,
 
-                 environ={
 
-                     'SCHEME': 'Sample',
 
-                     'EXAMPLE_PATH': 'src/objective-c/examples/Sample'
 
-                 }),
 
-             self.config.job_spec(
 
-                 ['src/objective-c/tests/build_one_example.sh'],
 
-                 timeout_seconds=10 * 60,
 
-                 shortname='objc-build-example-sample-frameworks',
 
-                 cpu_cost=1e6,
 
-                 environ={
 
-                     'SCHEME': 'Sample',
 
-                     'EXAMPLE_PATH': 'src/objective-c/examples/Sample',
 
-                     'FRAMEWORKS': 'YES'
 
-                 }),
 
-             self.config.job_spec(
 
-                 ['src/objective-c/tests/build_one_example.sh'],
 
-                 timeout_seconds=10 * 60,
 
-                 shortname='objc-build-example-switftsample',
 
-                 cpu_cost=1e6,
 
-                 environ={
 
-                     'SCHEME': 'SwiftSample',
 
-                     'EXAMPLE_PATH': 'src/objective-c/examples/SwiftSample'
 
-                 }),
 
-             self.config.job_spec(
 
-                 ['test/core/iomgr/ios/CFStreamTests/run_tests.sh'],
 
-                 timeout_seconds=10 * 60,
 
-                 shortname='cfstream-tests',
 
-                 cpu_cost=1e6,
 
-                 environ=_FORCE_ENVIRON_FOR_WRAPPERS),
 
-         ]
 
-     def pre_build_steps(self):
 
-         return []
 
-     def make_targets(self):
 
-         return ['interop_server']
 
-     def make_options(self):
 
-         return []
 
-     def build_steps(self):
 
-         return [
 
-             ['src/objective-c/tests/build_tests.sh'],
 
-             ['test/core/iomgr/ios/CFStreamTests/build_tests.sh'],
 
-         ]
 
-     def post_tests_steps(self):
 
-         return []
 
-     def makefile_name(self):
 
-         return 'Makefile'
 
-     def dockerfile_dir(self):
 
-         return None
 
-     def __str__(self):
 
-         return 'objc'
 
- class Sanity(object):
 
-     def configure(self, config, args):
 
-         self.config = config
 
-         self.args = args
 
-         _check_compiler(self.args.compiler, ['default'])
 
-     def test_specs(self):
 
-         import yaml
 
-         with open('tools/run_tests/sanity/sanity_tests.yaml', 'r') as f:
 
-             environ = {'TEST': 'true'}
 
-             if _is_use_docker_child():
 
-                 environ['CLANG_FORMAT_SKIP_DOCKER'] = 'true'
 
-                 environ['CLANG_TIDY_SKIP_DOCKER'] = 'true'
 
-             return [
 
-                 self.config.job_spec(
 
-                     cmd['script'].split(),
 
-                     timeout_seconds=30 * 60,
 
-                     environ=environ,
 
-                     cpu_cost=cmd.get('cpu_cost', 1)) for cmd in yaml.load(f)
 
-             ]
 
-     def pre_build_steps(self):
 
-         return []
 
-     def make_targets(self):
 
-         return ['run_dep_checks']
 
-     def make_options(self):
 
-         return []
 
-     def build_steps(self):
 
-         return []
 
-     def post_tests_steps(self):
 
-         return []
 
-     def makefile_name(self):
 
-         return 'Makefile'
 
-     def dockerfile_dir(self):
 
-         return 'tools/dockerfile/test/sanity'
 
-     def __str__(self):
 
-         return 'sanity'
 
- # different configurations we can run under
 
- with open('tools/run_tests/generated/configs.json') as f:
 
-     _CONFIGS = dict(
 
-         (cfg['config'], Config(**cfg)) for cfg in ast.literal_eval(f.read()))
 
- _LANGUAGES = {
 
-     'c++': CLanguage('cxx', 'c++'),
 
-     'c': CLanguage('c', 'c'),
 
-     'grpc-node': RemoteNodeLanguage(),
 
-     'php': PhpLanguage(),
 
-     'php7': Php7Language(),
 
-     'python': PythonLanguage(),
 
-     'ruby': RubyLanguage(),
 
-     'csharp': CSharpLanguage(),
 
-     'objc': ObjCLanguage(),
 
-     'sanity': Sanity()
 
- }
 
- _MSBUILD_CONFIG = {
 
-     'dbg': 'Debug',
 
-     'opt': 'Release',
 
-     'gcov': 'Debug',
 
- }
 
- def _windows_arch_option(arch):
 
-     """Returns msbuild cmdline option for selected architecture."""
 
-     if arch == 'default' or arch == 'x86':
 
-         return '/p:Platform=Win32'
 
-     elif arch == 'x64':
 
-         return '/p:Platform=x64'
 
-     else:
 
-         print('Architecture %s not supported.' % arch)
 
-         sys.exit(1)
 
- def _check_arch_option(arch):
 
-     """Checks that architecture option is valid."""
 
-     if platform_string() == 'windows':
 
-         _windows_arch_option(arch)
 
-     elif platform_string() == 'linux':
 
-         # On linux, we need to be running under docker with the right architecture.
 
-         runtime_arch = platform.architecture()[0]
 
-         if arch == 'default':
 
-             return
 
-         elif runtime_arch == '64bit' and arch == 'x64':
 
-             return
 
-         elif runtime_arch == '32bit' and arch == 'x86':
 
-             return
 
-         else:
 
-             print('Architecture %s does not match current runtime architecture.'
 
-                   % arch)
 
-             sys.exit(1)
 
-     else:
 
-         if args.arch != 'default':
 
-             print('Architecture %s not supported on current platform.' %
 
-                   args.arch)
 
-             sys.exit(1)
 
- def _docker_arch_suffix(arch):
 
-     """Returns suffix to dockerfile dir to use."""
 
-     if arch == 'default' or arch == 'x64':
 
-         return 'x64'
 
-     elif arch == 'x86':
 
-         return 'x86'
 
-     else:
 
-         print('Architecture %s not supported with current settings.' % arch)
 
-         sys.exit(1)
 
- def runs_per_test_type(arg_str):
 
-     """Auxilary function to parse the "runs_per_test" flag.
 
-        Returns:
 
-            A positive integer or 0, the latter indicating an infinite number of
 
-            runs.
 
-        Raises:
 
-            argparse.ArgumentTypeError: Upon invalid input.
 
-     """
 
-     if arg_str == 'inf':
 
-         return 0
 
-     try:
 
-         n = int(arg_str)
 
-         if n <= 0: raise ValueError
 
-         return n
 
-     except:
 
-         msg = '\'{}\' is not a positive integer or \'inf\''.format(arg_str)
 
-         raise argparse.ArgumentTypeError(msg)
 
- def percent_type(arg_str):
 
-     pct = float(arg_str)
 
-     if pct > 100 or pct < 0:
 
-         raise argparse.ArgumentTypeError(
 
-             "'%f' is not a valid percentage in the [0, 100] range" % pct)
 
-     return pct
 
- # This is math.isclose in python >= 3.5
 
- def isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
 
-     return abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
 
- # parse command line
 
- argp = argparse.ArgumentParser(description='Run grpc tests.')
 
- argp.add_argument(
 
-     '-c', '--config', choices=sorted(_CONFIGS.keys()), default='opt')
 
- argp.add_argument(
 
-     '-n',
 
-     '--runs_per_test',
 
-     default=1,
 
-     type=runs_per_test_type,
 
-     help='A positive integer or "inf". If "inf", all tests will run in an '
 
-     'infinite loop. Especially useful in combination with "-f"')
 
- argp.add_argument('-r', '--regex', default='.*', type=str)
 
- argp.add_argument('--regex_exclude', default='', type=str)
 
- argp.add_argument('-j', '--jobs', default=multiprocessing.cpu_count(), type=int)
 
- argp.add_argument('-s', '--slowdown', default=1.0, type=float)
 
- argp.add_argument(
 
-     '-p',
 
-     '--sample_percent',
 
-     default=100.0,
 
-     type=percent_type,
 
-     help='Run a random sample with that percentage of tests')
 
- argp.add_argument(
 
-     '-f', '--forever', default=False, action='store_const', const=True)
 
- argp.add_argument(
 
-     '-t', '--travis', default=False, action='store_const', const=True)
 
- argp.add_argument(
 
-     '--newline_on_success', default=False, action='store_const', const=True)
 
- argp.add_argument(
 
-     '-l',
 
-     '--language',
 
-     choices=['all'] + sorted(_LANGUAGES.keys()),
 
-     nargs='+',
 
-     default=['all'])
 
- argp.add_argument(
 
-     '-S', '--stop_on_failure', default=False, action='store_const', const=True)
 
- argp.add_argument(
 
-     '--use_docker',
 
-     default=False,
 
-     action='store_const',
 
-     const=True,
 
-     help='Run all the tests under docker. That provides ' +
 
-     'additional isolation and prevents the need to install ' +
 
-     'language specific prerequisites. Only available on Linux.')
 
- argp.add_argument(
 
-     '--allow_flakes',
 
-     default=False,
 
-     action='store_const',
 
-     const=True,
 
-     help=
 
-     'Allow flaky tests to show as passing (re-runs failed tests up to five times)'
 
- )
 
- argp.add_argument(
 
-     '--arch',
 
-     choices=['default', 'x86', 'x64'],
 
-     default='default',
 
-     help=
 
-     'Selects architecture to target. For some platforms "default" is the only supported choice.'
 
- )
 
- argp.add_argument(
 
-     '--compiler',
 
-     choices=[
 
-         'default', 'gcc4.4', 'gcc4.6', 'gcc4.8', 'gcc4.9', 'gcc5.3', 'gcc7.2',
 
-         'gcc_musl', 'clang3.4', 'clang3.5', 'clang3.6', 'clang3.7', 'clang7.0',
 
-         'python2.7', 'python3.4', 'python3.5', 'python3.6', 'python3.7', 'pypy',
 
-         'pypy3', 'python_alpine', 'all_the_cpythons', 'electron1.3',
 
-         'electron1.6', 'coreclr', 'cmake', 'cmake_vs2015', 'cmake_vs2017'
 
-     ],
 
-     default='default',
 
-     help=
 
-     'Selects compiler to use. Allowed values depend on the platform and language.'
 
- )
 
- argp.add_argument(
 
-     '--iomgr_platform',
 
-     choices=['native', 'uv', 'gevent'],
 
-     default='native',
 
-     help='Selects iomgr platform to build on')
 
- argp.add_argument(
 
-     '--build_only',
 
-     default=False,
 
-     action='store_const',
 
-     const=True,
 
-     help='Perform all the build steps but don\'t run any tests.')
 
- argp.add_argument(
 
-     '--measure_cpu_costs',
 
-     default=False,
 
-     action='store_const',
 
-     const=True,
 
-     help='Measure the cpu costs of tests')
 
- argp.add_argument(
 
-     '--update_submodules',
 
-     default=[],
 
-     nargs='*',
 
-     help=
 
-     'Update some submodules before building. If any are updated, also run generate_projects. '
 
-     +
 
-     'Submodules are specified as SUBMODULE_NAME:BRANCH; if BRANCH is omitted, master is assumed.'
 
- )
 
- argp.add_argument('-a', '--antagonists', default=0, type=int)
 
- argp.add_argument(
 
-     '-x',
 
-     '--xml_report',
 
-     default=None,
 
-     type=str,
 
-     help='Generates a JUnit-compatible XML report')
 
- argp.add_argument(
 
-     '--report_suite_name',
 
-     default='tests',
 
-     type=str,
 
-     help='Test suite name to use in generated JUnit XML report')
 
- argp.add_argument(
 
-     '--quiet_success',
 
-     default=False,
 
-     action='store_const',
 
-     const=True,
 
-     help=
 
-     'Don\'t print anything when a test passes. Passing tests also will not be reported in XML report. '
 
-     + 'Useful when running many iterations of each test (argument -n).')
 
- argp.add_argument(
 
-     '--force_default_poller',
 
-     default=False,
 
-     action='store_const',
 
-     const=True,
 
-     help='Don\'t try to iterate over many polling strategies when they exist')
 
- argp.add_argument(
 
-     '--force_use_pollers',
 
-     default=None,
 
-     type=str,
 
-     help='Only use the specified comma-delimited list of polling engines. '
 
-     'Example: --force_use_pollers epoll1,poll '
 
-     ' (This flag has no effect if --force_default_poller flag is also used)')
 
- argp.add_argument(
 
-     '--max_time', default=-1, type=int, help='Maximum test runtime in seconds')
 
- argp.add_argument(
 
-     '--bq_result_table',
 
-     default='',
 
-     type=str,
 
-     nargs='?',
 
-     help='Upload test results to a specified BQ table.')
 
- argp.add_argument(
 
-     '--auto_set_flakes',
 
-     default=False,
 
-     const=True,
 
-     action='store_const',
 
-     help=
 
-     'Allow repeated runs for tests that have been failing recently (based on BQ historical data).'
 
- )
 
- args = argp.parse_args()
 
- flaky_tests = set()
 
- shortname_to_cpu = {}
 
- if args.auto_set_flakes:
 
-     try:
 
-         for test in get_bqtest_data():
 
-             if test.flaky: flaky_tests.add(test.name)
 
-             if test.cpu > 0: shortname_to_cpu[test.name] = test.cpu
 
-     except:
 
-         print(
 
-             "Unexpected error getting flaky tests: %s" % traceback.format_exc())
 
- if args.force_default_poller:
 
-     _POLLING_STRATEGIES = {}
 
- elif args.force_use_pollers:
 
-     _POLLING_STRATEGIES[platform_string()] = args.force_use_pollers.split(',')
 
- jobset.measure_cpu_costs = args.measure_cpu_costs
 
- # update submodules if necessary
 
- need_to_regenerate_projects = False
 
- for spec in args.update_submodules:
 
-     spec = spec.split(':', 1)
 
-     if len(spec) == 1:
 
-         submodule = spec[0]
 
-         branch = 'master'
 
-     elif len(spec) == 2:
 
-         submodule = spec[0]
 
-         branch = spec[1]
 
-     cwd = 'third_party/%s' % submodule
 
-     def git(cmd, cwd=cwd):
 
-         print('in %s: git %s' % (cwd, cmd))
 
-         run_shell_command('git %s' % cmd, cwd=cwd)
 
-     git('fetch')
 
-     git('checkout %s' % branch)
 
-     git('pull origin %s' % branch)
 
-     if os.path.exists('src/%s/gen_build_yaml.py' % submodule):
 
-         need_to_regenerate_projects = True
 
- if need_to_regenerate_projects:
 
-     if jobset.platform_string() == 'linux':
 
-         run_shell_command('tools/buildgen/generate_projects.sh')
 
-     else:
 
-         print(
 
-             'WARNING: may need to regenerate projects, but since we are not on')
 
-         print(
 
-             '         Linux this step is being skipped. Compilation MAY fail.')
 
- # grab config
 
- run_config = _CONFIGS[args.config]
 
- build_config = run_config.build_config
 
- if args.travis:
 
-     _FORCE_ENVIRON_FOR_WRAPPERS = {'GRPC_TRACE': 'api'}
 
- if 'all' in args.language:
 
-     lang_list = _LANGUAGES.keys()
 
- else:
 
-     lang_list = args.language
 
- # We don't support code coverage on some languages
 
- if 'gcov' in args.config:
 
-     for bad in ['csharp', 'grpc-node', 'objc', 'sanity']:
 
-         if bad in lang_list:
 
-             lang_list.remove(bad)
 
- languages = set(_LANGUAGES[l] for l in lang_list)
 
- for l in languages:
 
-     l.configure(run_config, args)
 
- language_make_options = []
 
- if any(language.make_options() for language in languages):
 
-     if not 'gcov' in args.config and len(languages) != 1:
 
-         print(
 
-             'languages with custom make options cannot be built simultaneously with other languages'
 
-         )
 
-         sys.exit(1)
 
-     else:
 
-         # Combining make options is not clean and just happens to work. It allows C/C++ and C# to build
 
-         # together, and is only used under gcov. All other configs should build languages individually.
 
-         language_make_options = list(
 
-             set([
 
-                 make_option
 
-                 for lang in languages
 
-                 for make_option in lang.make_options()
 
-             ]))
 
- if args.use_docker:
 
-     if not args.travis:
 
-         print('Seen --use_docker flag, will run tests under docker.')
 
-         print('')
 
-         print(
 
-             'IMPORTANT: The changes you are testing need to be locally committed'
 
-         )
 
-         print(
 
-             'because only the committed changes in the current branch will be')
 
-         print('copied to the docker environment.')
 
-         time.sleep(5)
 
-     dockerfile_dirs = set([l.dockerfile_dir() for l in languages])
 
-     if len(dockerfile_dirs) > 1:
 
-         if 'gcov' in args.config:
 
-             dockerfile_dir = 'tools/dockerfile/test/multilang_jessie_x64'
 
-             print(
 
-                 'Using multilang_jessie_x64 docker image for code coverage for '
 
-                 'all languages.')
 
-         else:
 
-             print(
 
-                 'Languages to be tested require running under different docker '
 
-                 'images.')
 
-             sys.exit(1)
 
-     else:
 
-         dockerfile_dir = next(iter(dockerfile_dirs))
 
-     child_argv = [arg for arg in sys.argv if not arg == '--use_docker']
 
-     run_tests_cmd = 'python tools/run_tests/run_tests.py %s' % ' '.join(
 
-         child_argv[1:])
 
-     env = os.environ.copy()
 
-     env['RUN_TESTS_COMMAND'] = run_tests_cmd
 
-     env['DOCKERFILE_DIR'] = dockerfile_dir
 
-     env['DOCKER_RUN_SCRIPT'] = 'tools/run_tests/dockerize/docker_run_tests.sh'
 
-     if args.xml_report:
 
-         env['XML_REPORT'] = args.xml_report
 
-     if not args.travis:
 
-         env['TTY_FLAG'] = '-t'  # enables Ctrl-C when not on Jenkins.
 
-     subprocess.check_call(
 
-         'tools/run_tests/dockerize/build_docker_and_run_tests.sh',
 
-         shell=True,
 
-         env=env)
 
-     sys.exit(0)
 
- _check_arch_option(args.arch)
 
- def make_jobspec(cfg, targets, makefile='Makefile'):
 
-     if platform_string() == 'windows':
 
-         return [
 
-             jobset.JobSpec(
 
-                 [
 
-                     'cmake', '--build', '.', '--target',
 
-                     '%s' % target, '--config', _MSBUILD_CONFIG[cfg]
 
-                 ],
 
-                 cwd=os.path.dirname(makefile),
 
-                 timeout_seconds=None) for target in targets
 
-         ]
 
-     else:
 
-         if targets and makefile.startswith('cmake/build/'):
 
-             # With cmake, we've passed all the build configuration in the pre-build step already
 
-             return [
 
-                 jobset.JobSpec(
 
-                     [os.getenv('MAKE', 'make'), '-j',
 
-                      '%d' % args.jobs] + targets,
 
-                     cwd='cmake/build',
 
-                     timeout_seconds=None)
 
-             ]
 
-         if targets:
 
-             return [
 
-                 jobset.JobSpec(
 
-                     [
 
-                         os.getenv('MAKE', 'make'), '-f', makefile, '-j',
 
-                         '%d' % args.jobs,
 
-                         'EXTRA_DEFINES=GRPC_TEST_SLOWDOWN_MACHINE_FACTOR=%f' %
 
-                         args.slowdown,
 
-                         'CONFIG=%s' % cfg, 'Q='
 
-                     ] + language_make_options +
 
-                     ([] if not args.travis else ['JENKINS_BUILD=1']) + targets,
 
-                     timeout_seconds=None)
 
-             ]
 
-         else:
 
-             return []
 
- make_targets = {}
 
- for l in languages:
 
-     makefile = l.makefile_name()
 
-     make_targets[makefile] = make_targets.get(makefile, set()).union(
 
-         set(l.make_targets()))
 
- def build_step_environ(cfg):
 
-     environ = {'CONFIG': cfg}
 
-     msbuild_cfg = _MSBUILD_CONFIG.get(cfg)
 
-     if msbuild_cfg:
 
-         environ['MSBUILD_CONFIG'] = msbuild_cfg
 
-     return environ
 
- build_steps = list(
 
-     set(
 
-         jobset.JobSpec(
 
-             cmdline, environ=build_step_environ(build_config), flake_retries=2)
 
-         for l in languages
 
-         for cmdline in l.pre_build_steps()))
 
- if make_targets:
 
-     make_commands = itertools.chain.from_iterable(
 
-         make_jobspec(build_config, list(targets), makefile)
 
-         for (makefile, targets) in make_targets.items())
 
-     build_steps.extend(set(make_commands))
 
- build_steps.extend(
 
-     set(
 
-         jobset.JobSpec(
 
-             cmdline,
 
-             environ=build_step_environ(build_config),
 
-             timeout_seconds=None)
 
-         for l in languages
 
-         for cmdline in l.build_steps()))
 
- post_tests_steps = list(
 
-     set(
 
-         jobset.JobSpec(cmdline, environ=build_step_environ(build_config))
 
-         for l in languages
 
-         for cmdline in l.post_tests_steps()))
 
- runs_per_test = args.runs_per_test
 
- forever = args.forever
 
- def _shut_down_legacy_server(legacy_server_port):
 
-     try:
 
-         version = int(
 
-             urllib.request.urlopen(
 
-                 'http://localhost:%d/version_number' % legacy_server_port,
 
-                 timeout=10).read())
 
-     except:
 
-         pass
 
-     else:
 
-         urllib.request.urlopen(
 
-             'http://localhost:%d/quitquitquit' % legacy_server_port).read()
 
- def _calculate_num_runs_failures(list_of_results):
 
-     """Caculate number of runs and failures for a particular test.
 
-   Args:
 
-     list_of_results: (List) of JobResult object.
 
-   Returns:
 
-     A tuple of total number of runs and failures.
 
-   """
 
-     num_runs = len(list_of_results)  # By default, there is 1 run per JobResult.
 
-     num_failures = 0
 
-     for jobresult in list_of_results:
 
-         if jobresult.retries > 0:
 
-             num_runs += jobresult.retries
 
-         if jobresult.num_failures > 0:
 
-             num_failures += jobresult.num_failures
 
-     return num_runs, num_failures
 
- # _build_and_run results
 
- class BuildAndRunError(object):
 
-     BUILD = object()
 
-     TEST = object()
 
-     POST_TEST = object()
 
- def _has_epollexclusive():
 
-     binary = 'bins/%s/check_epollexclusive' % args.config
 
-     if not os.path.exists(binary):
 
-         return False
 
-     try:
 
-         subprocess.check_call(binary)
 
-         return True
 
-     except subprocess.CalledProcessError, e:
 
-         return False
 
-     except OSError, e:
 
-         # For languages other than C and Windows the binary won't exist
 
-         return False
 
- # returns a list of things that failed (or an empty list on success)
 
- def _build_and_run(check_cancelled,
 
-                    newline_on_success,
 
-                    xml_report=None,
 
-                    build_only=False):
 
-     """Do one pass of building & running tests."""
 
-     # build latest sequentially
 
-     num_failures, resultset = jobset.run(
 
-         build_steps,
 
-         maxjobs=1,
 
-         stop_on_failure=True,
 
-         newline_on_success=newline_on_success,
 
-         travis=args.travis)
 
-     if num_failures:
 
-         return [BuildAndRunError.BUILD]
 
-     if build_only:
 
-         if xml_report:
 
-             report_utils.render_junit_xml_report(
 
-                 resultset, xml_report, suite_name=args.report_suite_name)
 
-         return []
 
-     if not args.travis and not _has_epollexclusive() and platform_string(
 
-     ) in _POLLING_STRATEGIES and 'epollex' in _POLLING_STRATEGIES[platform_string(
 
-     )]:
 
-         print('\n\nOmitting EPOLLEXCLUSIVE tests\n\n')
 
-         _POLLING_STRATEGIES[platform_string()].remove('epollex')
 
-     # start antagonists
 
-     antagonists = [
 
-         subprocess.Popen(['tools/run_tests/python_utils/antagonist.py'])
 
-         for _ in range(0, args.antagonists)
 
-     ]
 
-     start_port_server.start_port_server()
 
-     resultset = None
 
-     num_test_failures = 0
 
-     try:
 
-         infinite_runs = runs_per_test == 0
 
-         one_run = set(
 
-             spec for language in languages for spec in language.test_specs()
 
-             if (re.search(args.regex, spec.shortname) and
 
-                 (args.regex_exclude == '' or
 
-                  not re.search(args.regex_exclude, spec.shortname))))
 
-         # When running on travis, we want out test runs to be as similar as possible
 
-         # for reproducibility purposes.
 
-         if args.travis and args.max_time <= 0:
 
-             massaged_one_run = sorted(one_run, key=lambda x: x.cpu_cost)
 
-         else:
 
-             # whereas otherwise, we want to shuffle things up to give all tests a
 
-             # chance to run.
 
-             massaged_one_run = list(
 
-                 one_run)  # random.sample needs an indexable seq.
 
-             num_jobs = len(massaged_one_run)
 
-             # for a random sample, get as many as indicated by the 'sample_percent'
 
-             # argument. By default this arg is 100, resulting in a shuffle of all
 
-             # jobs.
 
-             sample_size = int(num_jobs * args.sample_percent / 100.0)
 
-             massaged_one_run = random.sample(massaged_one_run, sample_size)
 
-             if not isclose(args.sample_percent, 100.0):
 
-                 assert args.runs_per_test == 1, "Can't do sampling (-p) over multiple runs (-n)."
 
-                 print("Running %d tests out of %d (~%d%%)" %
 
-                       (sample_size, num_jobs, args.sample_percent))
 
-         if infinite_runs:
 
-             assert len(massaged_one_run
 
-                       ) > 0, 'Must have at least one test for a -n inf run'
 
-         runs_sequence = (itertools.repeat(massaged_one_run)
 
-                          if infinite_runs else itertools.repeat(
 
-                              massaged_one_run, runs_per_test))
 
-         all_runs = itertools.chain.from_iterable(runs_sequence)
 
-         if args.quiet_success:
 
-             jobset.message(
 
-                 'START',
 
-                 'Running tests quietly, only failing tests will be reported',
 
-                 do_newline=True)
 
-         num_test_failures, resultset = jobset.run(
 
-             all_runs,
 
-             check_cancelled,
 
-             newline_on_success=newline_on_success,
 
-             travis=args.travis,
 
-             maxjobs=args.jobs,
 
-             maxjobs_cpu_agnostic=max_parallel_tests_for_current_platform(),
 
-             stop_on_failure=args.stop_on_failure,
 
-             quiet_success=args.quiet_success,
 
-             max_time=args.max_time)
 
-         if resultset:
 
-             for k, v in sorted(resultset.items()):
 
-                 num_runs, num_failures = _calculate_num_runs_failures(v)
 
-                 if num_failures > 0:
 
-                     if num_failures == num_runs:  # what about infinite_runs???
 
-                         jobset.message('FAILED', k, do_newline=True)
 
-                     else:
 
-                         jobset.message(
 
-                             'FLAKE',
 
-                             '%s [%d/%d runs flaked]' % (k, num_failures,
 
-                                                         num_runs),
 
-                             do_newline=True)
 
-     finally:
 
-         for antagonist in antagonists:
 
-             antagonist.kill()
 
-         if args.bq_result_table and resultset:
 
-             upload_extra_fields = {
 
-                 'compiler': args.compiler,
 
-                 'config': args.config,
 
-                 'iomgr_platform': args.iomgr_platform,
 
-                 'language': args.language[
 
-                     0],  # args.language is a list but will always have one element when uploading to BQ is enabled.
 
-                 'platform': platform_string()
 
-             }
 
-             upload_results_to_bq(resultset, args.bq_result_table,
 
-                                  upload_extra_fields)
 
-         if xml_report and resultset:
 
-             report_utils.render_junit_xml_report(
 
-                 resultset, xml_report, suite_name=args.report_suite_name)
 
-     number_failures, _ = jobset.run(
 
-         post_tests_steps,
 
-         maxjobs=1,
 
-         stop_on_failure=False,
 
-         newline_on_success=newline_on_success,
 
-         travis=args.travis)
 
-     out = []
 
-     if number_failures:
 
-         out.append(BuildAndRunError.POST_TEST)
 
-     if num_test_failures:
 
-         out.append(BuildAndRunError.TEST)
 
-     return out
 
- if forever:
 
-     success = True
 
-     while True:
 
-         dw = watch_dirs.DirWatcher(['src', 'include', 'test', 'examples'])
 
-         initial_time = dw.most_recent_change()
 
-         have_files_changed = lambda: dw.most_recent_change() != initial_time
 
-         previous_success = success
 
-         errors = _build_and_run(
 
-             check_cancelled=have_files_changed,
 
-             newline_on_success=False,
 
-             build_only=args.build_only) == 0
 
-         if not previous_success and not errors:
 
-             jobset.message(
 
-                 'SUCCESS',
 
-                 'All tests are now passing properly',
 
-                 do_newline=True)
 
-         jobset.message('IDLE', 'No change detected')
 
-         while not have_files_changed():
 
-             time.sleep(1)
 
- else:
 
-     errors = _build_and_run(
 
-         check_cancelled=lambda: False,
 
-         newline_on_success=args.newline_on_success,
 
-         xml_report=args.xml_report,
 
-         build_only=args.build_only)
 
-     if not errors:
 
-         jobset.message('SUCCESS', 'All tests passed', do_newline=True)
 
-     else:
 
-         jobset.message('FAILED', 'Some tests failed', do_newline=True)
 
-     exit_code = 0
 
-     if BuildAndRunError.BUILD in errors:
 
-         exit_code |= 1
 
-     if BuildAndRunError.TEST in errors:
 
-         exit_code |= 2
 
-     if BuildAndRunError.POST_TEST in errors:
 
-         exit_code |= 4
 
-     sys.exit(exit_code)
 
 
  |