aboutsummaryrefslogtreecommitdiffhomepage
path: root/tools/run_tests/run_interop_tests.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/run_tests/run_interop_tests.py')
-rwxr-xr-xtools/run_tests/run_interop_tests.py279
1 files changed, 184 insertions, 95 deletions
diff --git a/tools/run_tests/run_interop_tests.py b/tools/run_tests/run_interop_tests.py
index 5435a1d02f..e69e9877c5 100755
--- a/tools/run_tests/run_interop_tests.py
+++ b/tools/run_tests/run_interop_tests.py
@@ -31,27 +31,34 @@
"""Run interop (cross-language) tests in parallel."""
import argparse
+import atexit
import dockerjob
import itertools
import jobset
+import json
import multiprocessing
import os
+import re
import report_utils
+import subprocess
import sys
import tempfile
import time
import uuid
+# Docker doesn't clean up after itself, so we do it on exit.
+atexit.register(lambda: subprocess.call(['stty', 'echo']))
+
ROOT = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '../..'))
os.chdir(ROOT)
_DEFAULT_SERVER_PORT=8080
-# TOOD(jtattermusch) wrapped languages use this variable for location
-# of roots.pem. We might want to use GRPC_DEFAULT_SSL_ROOTS_FILE_PATH
-# supported by C core SslCredentials instead.
-_SSL_CERT_ENV = { 'SSL_CERT_FILE':'/usr/local/share/grpc/roots.pem' }
+_SKIP_COMPRESSION = ['large_compressed_unary',
+ 'server_compressed_streaming']
+_SKIP_ADVANCED = ['custom_metadata', 'status_code_and_message',
+ 'unimplemented_method']
class CXXLanguage:
@@ -60,20 +67,23 @@ class CXXLanguage:
self.server_cwd = None
self.safename = 'cxx'
- def client_args(self):
- return ['bins/opt/interop_client']
+ def client_cmd(self, args):
+ return ['bins/opt/interop_client'] + args
def cloud_to_prod_env(self):
return {}
- def server_args(self):
- return ['bins/opt/interop_server', '--use_tls=true']
+ def server_cmd(self, args):
+ return ['bins/opt/interop_server', '--use_tls=true'] + args
def global_env(self):
return {}
def unimplemented_test_cases(self):
- return []
+ return _SKIP_ADVANCED + _SKIP_COMPRESSION
+
+ def unimplemented_test_cases_server(self):
+ return _SKIP_ADVANCED + _SKIP_COMPRESSION
def __str__(self):
return 'c++'
@@ -86,20 +96,24 @@ class CSharpLanguage:
self.server_cwd = 'src/csharp/Grpc.IntegrationTesting.Server/bin/Debug'
self.safename = str(self)
- def client_args(self):
- return ['mono', 'Grpc.IntegrationTesting.Client.exe']
+ def client_cmd(self, args):
+ return ['mono', 'Grpc.IntegrationTesting.Client.exe'] + args
def cloud_to_prod_env(self):
- return _SSL_CERT_ENV
+ return {}
- def server_args(self):
- return ['mono', 'Grpc.IntegrationTesting.Server.exe', '--use_tls=true']
+ def server_cmd(self, args):
+ return ['mono', 'Grpc.IntegrationTesting.Server.exe', '--use_tls=true'] + args
def global_env(self):
return {}
def unimplemented_test_cases(self):
- return []
+ # TODO: status_code_and_message doesn't work against node_server
+ return _SKIP_COMPRESSION + ['status_code_and_message']
+
+ def unimplemented_test_cases_server(self):
+ return _SKIP_COMPRESSION
def __str__(self):
return 'csharp'
@@ -112,20 +126,23 @@ class JavaLanguage:
self.server_cwd = '../grpc-java'
self.safename = str(self)
- def client_args(self):
- return ['./run-test-client.sh']
+ def client_cmd(self, args):
+ return ['./run-test-client.sh'] + args
def cloud_to_prod_env(self):
return {}
- def server_args(self):
- return ['./run-test-server.sh', '--use_tls=true']
+ def server_cmd(self, args):
+ return ['./run-test-server.sh', '--use_tls=true'] + args
def global_env(self):
return {}
def unimplemented_test_cases(self):
- return []
+ return _SKIP_ADVANCED + _SKIP_COMPRESSION
+
+ def unimplemented_test_cases_server(self):
+ return _SKIP_ADVANCED + _SKIP_COMPRESSION
def __str__(self):
return 'java'
@@ -139,20 +156,23 @@ class GoLanguage:
self.server_cwd = '/go/src/google.golang.org/grpc/interop/server'
self.safename = str(self)
- def client_args(self):
- return ['go', 'run', 'client.go']
+ def client_cmd(self, args):
+ return ['go', 'run', 'client.go'] + args
def cloud_to_prod_env(self):
return {}
- def server_args(self):
- return ['go', 'run', 'server.go', '--use_tls=true']
+ def server_cmd(self, args):
+ return ['go', 'run', 'server.go', '--use_tls=true'] + args
def global_env(self):
return {}
def unimplemented_test_cases(self):
- return []
+ return _SKIP_ADVANCED + _SKIP_COMPRESSION
+
+ def unimplemented_test_cases_server(self):
+ return _SKIP_ADVANCED + _SKIP_COMPRESSION
def __str__(self):
return 'go'
@@ -168,8 +188,8 @@ class Http2Client:
self.client_cwd = None
self.safename = str(self)
- def client_args(self):
- return ['tools/http2_interop/http2_interop.test', '-test.v']
+ def client_cmd(self, args):
+ return ['tools/http2_interop/http2_interop.test', '-test.v'] + args
def cloud_to_prod_env(self):
return {}
@@ -180,6 +200,9 @@ class Http2Client:
def unimplemented_test_cases(self):
return _TEST_CASES
+ def unimplemented_test_cases_server(self):
+ return []
+
def __str__(self):
return 'http2'
@@ -190,20 +213,23 @@ class NodeLanguage:
self.server_cwd = None
self.safename = str(self)
- def client_args(self):
- return ['node', 'src/node/interop/interop_client.js']
+ def client_cmd(self, args):
+ return ['node', 'src/node/interop/interop_client.js'] + args
def cloud_to_prod_env(self):
- return _SSL_CERT_ENV
+ return {}
- def server_args(self):
- return ['node', 'src/node/interop/interop_server.js', '--use_tls=true']
+ def server_cmd(self, args):
+ return ['node', 'src/node/interop/interop_server.js', '--use_tls=true'] + args
def global_env(self):
return {}
def unimplemented_test_cases(self):
- return []
+ return _SKIP_COMPRESSION
+
+ def unimplemented_test_cases_server(self):
+ return _SKIP_COMPRESSION
def __str__(self):
return 'node'
@@ -215,16 +241,19 @@ class PHPLanguage:
self.client_cwd = None
self.safename = str(self)
- def client_args(self):
- return ['src/php/bin/interop_client.sh']
+ def client_cmd(self, args):
+ return ['src/php/bin/interop_client.sh'] + args
def cloud_to_prod_env(self):
- return _SSL_CERT_ENV
+ return {}
def global_env(self):
return {}
def unimplemented_test_cases(self):
+ return _SKIP_ADVANCED + _SKIP_COMPRESSION
+
+ def unimplemented_test_cases_server(self):
return []
def __str__(self):
@@ -238,20 +267,23 @@ class RubyLanguage:
self.server_cwd = None
self.safename = str(self)
- def client_args(self):
- return ['ruby', 'src/ruby/bin/interop/interop_client.rb']
+ def client_cmd(self, args):
+ return ['ruby', 'src/ruby/bin/interop/interop_client.rb'] + args
def cloud_to_prod_env(self):
- return _SSL_CERT_ENV
+ return {}
- def server_args(self):
- return ['ruby', 'src/ruby/bin/interop/interop_server.rb', '--use_tls=true']
+ def server_cmd(self, args):
+ return ['ruby', 'src/ruby/bin/interop/interop_server.rb', '--use_tls=true'] + args
def global_env(self):
return {}
def unimplemented_test_cases(self):
- return []
+ return _SKIP_ADVANCED + _SKIP_COMPRESSION
+
+ def unimplemented_test_cases_server(self):
+ return _SKIP_ADVANCED + _SKIP_COMPRESSION
def __str__(self):
return 'ruby'
@@ -264,20 +296,36 @@ class PythonLanguage:
self.server_cwd = None
self.safename = str(self)
- def client_args(self):
- return ['python2.7_virtual_environment/bin/python', '-m', 'grpc_interop.client']
+ def client_cmd(self, args):
+ return [
+ 'src/python/grpcio/.tox/py27/bin/python',
+ 'src/python/grpcio/setup.py',
+ 'run_interop',
+ '--client',
+ '--args=\'{}\''.format(' '.join(args))
+ ]
def cloud_to_prod_env(self):
- return _SSL_CERT_ENV
+ return {}
- def server_args(self):
- return ['python2.7_virtual_environment/bin/python', '-m', 'grpc_interop.server', '--use_tls=true']
+ def server_cmd(self, args):
+ return [
+ 'src/python/grpcio/.tox/py27/bin/python',
+ 'src/python/grpcio/setup.py',
+ 'run_interop',
+ '--server',
+ '--args=\'{}\''.format(' '.join(args) + ' --use_tls=true')
+ ]
def global_env(self):
- return {'LD_LIBRARY_PATH': 'libs/opt'}
+ return {'LD_LIBRARY_PATH': '{}/libs/opt'.format(DOCKER_WORKDIR_ROOT)}
def unimplemented_test_cases(self):
- return ['jwt_token_creds', 'per_rpc_creds']
+ return _SKIP_ADVANCED + _SKIP_COMPRESSION + ['jwt_token_creds',
+ 'per_rpc_creds']
+
+ def unimplemented_test_cases_server(self):
+ return _SKIP_ADVANCED + _SKIP_COMPRESSION
def __str__(self):
return 'python'
@@ -300,13 +348,17 @@ _SERVERS = ['c++', 'node', 'csharp', 'java', 'go', 'ruby', 'python']
_TEST_CASES = ['large_unary', 'empty_unary', 'ping_pong',
'empty_stream', 'client_streaming', 'server_streaming',
'cancel_after_begin', 'cancel_after_first_response',
- 'timeout_on_sleeping_server']
+ 'timeout_on_sleeping_server', 'custom_metadata',
+ 'status_code_and_message', 'unimplemented_method',
+ 'large_compressed_unary', 'server_compressed_streaming']
_AUTH_TEST_CASES = ['compute_engine_creds', 'jwt_token_creds',
'oauth2_auth_token', 'per_rpc_creds']
_HTTP2_TEST_CASES = ["tls", "framing"]
+DOCKER_WORKDIR_ROOT = '/var/local/git/grpc'
+
def docker_run_cmdline(cmdline, image, docker_args=[], cwd=None, environ=None):
"""Wraps given cmdline array to create 'docker run' cmdline from it."""
docker_cmdline = ['docker', 'run', '-i', '--rm=true']
@@ -317,7 +369,7 @@ def docker_run_cmdline(cmdline, image, docker_args=[], cwd=None, environ=None):
docker_cmdline += ['-e', '%s=%s' % (k,v)]
# set working directory
- workdir = '/var/local/git/grpc'
+ workdir = DOCKER_WORKDIR_ROOT
if cwd:
workdir = os.path.join(workdir, cwd)
docker_cmdline += ['-w', workdir]
@@ -334,12 +386,12 @@ def bash_login_cmdline(cmdline):
return ['bash', '-l', '-c', ' '.join(cmdline)]
-def add_auth_options(language, test_case, cmdline, env):
+def auth_options(language, test_case):
"""Returns (cmdline, env) tuple with cloud_to_prod_auth test options."""
language = str(language)
- cmdline = list(cmdline)
- env = env.copy()
+ cmdargs = []
+ env = {}
# TODO(jtattermusch): this file path only works inside docker
key_filepath = '/root/service_account/stubbyCloudTestingTest-ee3fce360ac5.json'
@@ -351,19 +403,19 @@ def add_auth_options(language, test_case, cmdline, env):
if language in ['csharp', 'node', 'php', 'python', 'ruby']:
env['GOOGLE_APPLICATION_CREDENTIALS'] = key_filepath
else:
- cmdline += [key_file_arg]
+ cmdargs += [key_file_arg]
if test_case in ['per_rpc_creds', 'oauth2_auth_token']:
- cmdline += [oauth_scope_arg]
+ cmdargs += [oauth_scope_arg]
if test_case == 'oauth2_auth_token' and language == 'c++':
# C++ oauth2 test uses GCE creds and thus needs to know the default account
- cmdline += [default_account_arg]
+ cmdargs += [default_account_arg]
if test_case == 'compute_engine_creds':
- cmdline += [oauth_scope_arg, default_account_arg]
+ cmdargs += [oauth_scope_arg, default_account_arg]
- return (cmdline, env)
+ return (cmdargs, env)
def _job_kill_handler(job):
@@ -378,18 +430,20 @@ def _job_kill_handler(job):
def cloud_to_prod_jobspec(language, test_case, docker_image=None, auth=False):
"""Creates jobspec for cloud-to-prod interop test"""
- cmdline = language.client_args() + [
+ container_name = None
+ cmdargs = [
'--server_host_override=grpc-test.sandbox.google.com',
'--server_host=grpc-test.sandbox.google.com',
'--server_port=443',
'--use_tls=true',
'--test_case=%s' % test_case]
- cwd = language.client_cwd
environ = dict(language.cloud_to_prod_env(), **language.global_env())
- container_name = None
if auth:
- cmdline, environ = add_auth_options(language, test_case, cmdline, environ)
- cmdline = bash_login_cmdline(cmdline)
+ auth_cmdargs, auth_env = auth_options(language, test_case)
+ cmdargs += auth_cmdargs
+ environ.update(auth_env)
+ cmdline = bash_login_cmdline(language.client_cmd(cmdargs))
+ cwd = language.client_cwd
if docker_image:
container_name = dockerjob.random_name('interop_client_%s' % language.safename)
@@ -419,13 +473,13 @@ def cloud_to_prod_jobspec(language, test_case, docker_image=None, auth=False):
def cloud_to_cloud_jobspec(language, test_case, server_name, server_host,
server_port, docker_image=None):
"""Creates jobspec for cloud-to-cloud interop test"""
- cmdline = bash_login_cmdline(language.client_args() +
- ['--server_host_override=foo.test.google.fr',
- '--use_tls=true',
- '--use_test_ca=true',
- '--test_case=%s' % test_case,
- '--server_host=%s' % server_host,
- '--server_port=%s' % server_port])
+ cmdline = bash_login_cmdline(language.client_cmd([
+ '--server_host_override=foo.test.google.fr',
+ '--use_tls=true',
+ '--use_test_ca=true',
+ '--test_case=%s' % test_case,
+ '--server_host=%s' % server_host,
+ '--server_port=%s' % server_port]))
cwd = language.client_cwd
environ = language.global_env()
if docker_image:
@@ -455,8 +509,8 @@ def cloud_to_cloud_jobspec(language, test_case, server_name, server_host,
def server_jobspec(language, docker_image):
"""Create jobspec for running a server"""
container_name = dockerjob.random_name('interop_server_%s' % language.safename)
- cmdline = bash_login_cmdline(language.server_args() +
- ['--port=%s' % _DEFAULT_SERVER_PORT])
+ cmdline = bash_login_cmdline(
+ language.server_cmd(['--port=%s' % _DEFAULT_SERVER_PORT]))
environ = language.global_env()
docker_cmdline = docker_run_cmdline(cmdline,
image=docker_image,
@@ -497,6 +551,33 @@ def build_interop_image_jobspec(language, tag=None):
return build_job
+def aggregate_http2_results(stdout):
+ match = re.search(r'\{"cases[^\]]*\]\}', stdout)
+ if not match:
+ return None
+
+ results = json.loads(match.group(0))
+ skipped = 0
+ passed = 0
+ failed = 0
+ failed_cases = []
+ for case in results['cases']:
+ if case.get('skipped', False):
+ skipped += 1
+ else:
+ if case.get('passed', False):
+ passed += 1
+ else:
+ failed += 1
+ failed_cases.append(case.get('name', "NONAME"))
+ return {
+ 'passed': passed,
+ 'failed': failed,
+ 'skipped': skipped,
+ 'failed_cases': ', '.join(failed_cases),
+ 'percent': 1.0 * passed / (passed + failed)
+ }
+
argp = argparse.ArgumentParser(description='Run interop tests.')
argp.add_argument('-l', '--language',
choices=['all'] + sorted(_LANGUAGES),
@@ -547,7 +628,7 @@ argp.add_argument('--http2_interop',
action='store_const',
const=True,
help='Enable HTTP/2 interop tests')
-
+
args = argp.parse_args()
servers = set(s for s in itertools.chain.from_iterable(_SERVERS
@@ -571,7 +652,7 @@ languages = set(_LANGUAGES[l]
for l in itertools.chain.from_iterable(
_LANGUAGES.iterkeys() if x == 'all' else [x]
for x in args.language))
-
+
http2Interop = Http2Client() if args.http2_interop else None
docker_images={}
@@ -593,10 +674,10 @@ if args.use_docker:
num_failures, _ = jobset.run(
build_jobs, newline_on_success=True, maxjobs=args.jobs)
if num_failures == 0:
- jobset.message('SUCCESS', 'All docker images built successfully.',
+ jobset.message('SUCCESS', 'All docker images built successfully.',
do_newline=True)
else:
- jobset.message('FAILED', 'Failed to build interop docker images.',
+ jobset.message('FAILED', 'Failed to build interop docker images.',
do_newline=True)
for image in docker_images.itervalues():
dockerjob.remove_image(image, skip_nonexistent=True)
@@ -619,18 +700,17 @@ try:
for language in languages:
for test_case in _TEST_CASES:
if not test_case in language.unimplemented_test_cases():
- test_job = cloud_to_prod_jobspec(language, test_case,
- docker_image=docker_images.get(str(language)))
- jobs.append(test_job)
-
- # TODO(carl-mastrangelo): Currently prod TLS terminators aren't spec compliant. Reenable
- # this once a better solution is in place.
- if args.http2_interop and False:
+ if not test_case in _SKIP_ADVANCED + _SKIP_COMPRESSION:
+ test_job = cloud_to_prod_jobspec(language, test_case,
+ docker_image=docker_images.get(str(language)))
+ jobs.append(test_job)
+
+ if args.http2_interop:
for test_case in _HTTP2_TEST_CASES:
test_job = cloud_to_prod_jobspec(http2Interop, test_case,
docker_image=docker_images.get(str(http2Interop)))
jobs.append(test_job)
-
+
if args.cloud_to_prod_auth:
for language in languages:
@@ -648,17 +728,22 @@ try:
for server_name, server_address in server_addresses.iteritems():
(server_host, server_port) = server_address
+ server_language = _LANGUAGES.get(server_name, None)
+ skip_server = [] # test cases unimplemented by server
+ if server_language:
+ skip_server = server_language.unimplemented_test_cases_server()
for language in languages:
for test_case in _TEST_CASES:
if not test_case in language.unimplemented_test_cases():
- test_job = cloud_to_cloud_jobspec(language,
- test_case,
- server_name,
- server_host,
- server_port,
- docker_image=docker_images.get(str(language)))
- jobs.append(test_job)
-
+ if not test_case in skip_server:
+ test_job = cloud_to_cloud_jobspec(language,
+ test_case,
+ server_name,
+ server_host,
+ server_port,
+ docker_image=docker_images.get(str(language)))
+ jobs.append(test_job)
+
if args.http2_interop:
for test_case in _HTTP2_TEST_CASES:
if server_name == "go":
@@ -678,7 +763,7 @@ try:
dockerjob.remove_image(image, skip_nonexistent=True)
sys.exit(1)
- num_failures, resultset = jobset.run(jobs, newline_on_success=True,
+ num_failures, resultset = jobset.run(jobs, newline_on_success=True,
maxjobs=args.jobs)
if num_failures:
jobset.message('FAILED', 'Some tests failed', do_newline=True)
@@ -686,7 +771,11 @@ try:
jobset.message('SUCCESS', 'All tests passed', do_newline=True)
report_utils.render_junit_xml_report(resultset, 'report.xml')
-
+
+ for name, job in resultset.iteritems():
+ if "http2" in name:
+ job[0].http2results = aggregate_http2_results(job[0].message)
+
report_utils.render_interop_html_report(
set([str(l) for l in languages]), servers, _TEST_CASES, _AUTH_TEST_CASES,
_HTTP2_TEST_CASES, resultset, num_failures,