| #!/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', 'epollsig', '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' |
| ]) |
| |
| |
| def _python_config_generator(name, major, minor, bits, config_vars): |
| 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])]) |
| |
| |
| 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_UV_THREAD_CHECK' |
| 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=''): |
| 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')) |
| 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 ['python3.5', 'python3.6']: |
| return 'pyenv' |
| 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'] |
| |
| 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) |
| 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) |
| 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 == '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, |
| ) |
| 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=10 * 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, ['coreclr', 'default']) |
| _check_arch(self.args.arch, ['default']) |
| self._cmake_arch_option = 'x64' |
| self._make_options = [] |
| else: |
| _check_compiler(self.args.compiler, ['default', 'coreclr']) |
| self._docker_distro = 'jessie' |
| |
| if self.platform == 'mac': |
| # TODO(jtattermusch): EMBED_ZLIB=true currently breaks the mac build |
| self._make_options = ['EMBED_OPENSSL=true'] |
| if self.args.compiler != 'coreclr': |
| # On Mac, official distribution of mono is 32bit. |
| self._make_options += ['ARCH_FLAGS=-m32', 'LDFLAGS=-m32'] |
| else: |
| self._make_options = ['EMBED_OPENSSL=true', 'EMBED_ZLIB=true'] |
| |
| 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 = [] |
| 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 self._make_options |
| |
| 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: |
| return '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' |
| }), |
| ] |
| |
| 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']] |
| |
| 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', 'python2.7', |
| 'python3.4', 'python3.5', 'python3.6', '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'], |
| 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 epollsig,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( |
| '--disable_auto_set_flakes', |
| default=False, |
| const=True, |
| action='store_const', |
| help='Disable rerunning historically flaky tests') |
| args = argp.parse_args() |
| |
| flaky_tests = set() |
| shortname_to_cpu = {} |
| if not args.disable_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 ['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_results_to_bq(resultset, args.bq_result_table, args, |
| platform_string()) |
| 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) |