#!/usr/bin/env python # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you 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. # from __future__ import division from __future__ import print_function import platform import copy import os import signal import socket import subprocess import sys import time from optparse import OptionParser from util import local_libpath SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__)) SCRIPTS = [ 'FastbinaryTest.py', 'TestFrozen.py', 'TestRenderedDoubleConstants.py', 'TSimpleJSONProtocolTest.py', 'SerializationTest.py', 'TestEof.py', 'TestSyntax.py', 'TestSocket.py', ] FRAMED = ["TNonblockingServer"] SKIP_ZLIB = ['TNonblockingServer', 'THttpServer'] SKIP_SSL = ['THttpServer'] EXTRA_DELAY = dict(TProcessPoolServer=5.5) PROTOS = [ 'accel', 'accelc', 'binary', 'compact', 'json', 'header', ] def default_servers(): servers = [ 'TSimpleServer', 'TThreadedServer', 'TThreadPoolServer', 'TNonblockingServer', 'THttpServer', ] if platform.system() != 'Windows': servers.append('TProcessPoolServer') servers.append('TForkingServer') return servers def relfile(fname): return os.path.join(SCRIPT_DIR, fname) def setup_pypath(libdir, gendir): dirs = [libdir, gendir] env = copy.deepcopy(os.environ) pypath = env.get('PYTHONPATH', None) if pypath: dirs.append(pypath) env['PYTHONPATH'] = os.pathsep.join(dirs) if gendir.endswith('gen-py-no_utf8strings'): env['THRIFT_TEST_PY_NO_UTF8STRINGS'] = '1' return env def runScriptTest(libdir, genbase, genpydir, script): env = setup_pypath(libdir, os.path.join(genbase, genpydir)) script_args = [sys.executable, relfile(script)] print('\nTesting script: %s\n----' % (' '.join(script_args))) ret = subprocess.call(script_args, env=env) if ret != 0: print('*** FAILED ***', file=sys.stderr) print('LIBDIR: %s' % libdir, file=sys.stderr) print('PY_GEN: %s' % genpydir, file=sys.stderr) print('SCRIPT: %s' % script, file=sys.stderr) raise Exception("Script subprocess failed, retcode=%d, args: %s" % (ret, ' '.join(script_args))) def runServiceTest(libdir, genbase, genpydir, server_class, proto, port, use_zlib, use_ssl, verbose): env = setup_pypath(libdir, os.path.join(genbase, genpydir)) # Build command line arguments server_args = [sys.executable, relfile('TestServer.py')] cli_args = [sys.executable, relfile('TestClient.py')] for which in (server_args, cli_args): which.append('--protocol=%s' % proto) # accel, binary, compact or json which.append('--port=%d' % port) # default to 9090 if use_zlib: which.append('--zlib') if use_ssl: which.append('--ssl') if verbose == 0: which.append('-q') if verbose == 2: which.append('-v') # server-specific option to select server class server_args.append(server_class) # client-specific cmdline options if server_class in FRAMED: cli_args.append('--transport=framed') else: cli_args.append('--transport=buffered') if server_class == 'THttpServer': cli_args.append('--http=/') if verbose > 0: print('Testing server %s: %s' % (server_class, ' '.join(server_args))) serverproc = subprocess.Popen(server_args, env=env) def ensureServerAlive(): if serverproc.poll() is not None: print(('FAIL: Server process (%s) failed with retcode %d') % (' '.join(server_args), serverproc.returncode)) raise Exception('Server subprocess %s died, args: %s' % (server_class, ' '.join(server_args))) # Wait for the server to start accepting connections on the given port. sleep_time = 0.1 # Seconds max_attempts = 100 attempt = 0 while True: sock4 = socket.socket() sock6 = socket.socket(socket.AF_INET6) try: if sock4.connect_ex(('127.0.0.1', port)) == 0 \ or sock6.connect_ex(('::1', port)) == 0: break attempt += 1 if attempt >= max_attempts: raise Exception("TestServer not ready on port %d after %.2f seconds" % (port, sleep_time * attempt)) ensureServerAlive() time.sleep(sleep_time) finally: sock4.close() sock6.close() try: if verbose > 0: print('Testing client: %s' % (' '.join(cli_args))) ret = subprocess.call(cli_args, env=env) if ret != 0: print('*** FAILED ***', file=sys.stderr) print('LIBDIR: %s' % libdir, file=sys.stderr) print('PY_GEN: %s' % genpydir, file=sys.stderr) raise Exception("Client subprocess failed, retcode=%d, args: %s" % (ret, ' '.join(cli_args))) finally: # check that server didn't die ensureServerAlive() extra_sleep = EXTRA_DELAY.get(server_class, 0) if extra_sleep > 0 and verbose > 0: print('Giving %s (proto=%s,zlib=%s,ssl=%s) an extra %d seconds for child' 'processes to terminate via alarm' % (server_class, proto, use_zlib, use_ssl, extra_sleep)) time.sleep(extra_sleep) sig = signal.SIGKILL if platform.system() != 'Windows' else signal.SIGABRT os.kill(serverproc.pid, sig) serverproc.wait() class TestCases(object): def __init__(self, genbase, libdir, port, gendirs, servers, verbose): self.genbase = genbase self.libdir = libdir self.port = port self.verbose = verbose self.gendirs = gendirs self.servers = servers def default_conf(self): return { 'gendir': self.gendirs[0], 'server': self.servers[0], 'proto': PROTOS[0], 'zlib': False, 'ssl': False, } def run(self, conf, test_count): with_zlib = conf['zlib'] with_ssl = conf['ssl'] try_server = conf['server'] try_proto = conf['proto'] genpydir = conf['gendir'] # skip any servers that don't work with the Zlib transport if with_zlib and try_server in SKIP_ZLIB: return False # skip any servers that don't work with SSL if with_ssl and try_server in SKIP_SSL: return False if self.verbose > 0: print('\nTest run #%d: (includes %s) Server=%s, Proto=%s, zlib=%s, SSL=%s' % (test_count, genpydir, try_server, try_proto, with_zlib, with_ssl)) runServiceTest(self.libdir, self.genbase, genpydir, try_server, try_proto, self.port, with_zlib, with_ssl, self.verbose) if self.verbose > 0: print('OK: Finished (includes %s) %s / %s proto / zlib=%s / SSL=%s. %d combinations tested.' % (genpydir, try_server, try_proto, with_zlib, with_ssl, test_count)) return True def test_feature(self, name, values): test_count = 0 conf = self.default_conf() for try_server in values: conf[name] = try_server if self.run(conf, test_count): test_count += 1 return test_count def run_all_tests(self): test_count = 0 for try_server in self.servers: for genpydir in self.gendirs: for try_proto in PROTOS: for with_zlib in (False, True): # skip any servers that don't work with the Zlib transport if with_zlib and try_server in SKIP_ZLIB: continue for with_ssl in (False, True): # skip any servers that don't work with SSL if with_ssl and try_server in SKIP_SSL: continue test_count += 1 if self.verbose > 0: print('\nTest run #%d: (includes %s) Server=%s, Proto=%s, zlib=%s, SSL=%s' % (test_count, genpydir, try_server, try_proto, with_zlib, with_ssl)) runServiceTest(self.libdir, self.genbase, genpydir, try_server, try_proto, self.port, with_zlib, with_ssl) if self.verbose > 0: print('OK: Finished (includes %s) %s / %s proto / zlib=%s / SSL=%s. %d combinations tested.' % (genpydir, try_server, try_proto, with_zlib, with_ssl, test_count)) return test_count def main(): parser = OptionParser() parser.add_option('--all', action="store_true", dest='all') parser.add_option('--genpydirs', type='string', dest='genpydirs', default='default,slots,oldstyle,no_utf8strings,dynamic,dynamicslots,enum', help='directory extensions for generated code, used as suffixes for \"gen-py-*\" added sys.path for individual tests') parser.add_option("--port", type="int", dest="port", default=9090, help="port number for server to listen on") parser.add_option('-v', '--verbose', action="store_const", dest="verbose", const=2, help="verbose output") parser.add_option('-q', '--quiet', action="store_const", dest="verbose", const=0, help="minimal output") parser.add_option('-L', '--libdir', dest="libdir", default=local_libpath(), help="directory path that contains Thrift Python library") parser.add_option('--gen-base', dest="gen_base", default=SCRIPT_DIR, help="directory path that contains Thrift Python library") parser.set_defaults(verbose=1) options, args = parser.parse_args() generated_dirs = [] for gp_dir in options.genpydirs.split(','): generated_dirs.append('gen-py-%s' % (gp_dir)) # commandline permits a single class name to be specified to override SERVERS=[...] servers = default_servers() if len(args) == 1: if args[0] in servers: servers = args else: print('Unavailable server type "%s", please choose one of: %s' % (args[0], servers)) sys.exit(0) tests = TestCases(options.gen_base, options.libdir, options.port, generated_dirs, servers, options.verbose) # run tests without a client/server first print('----------------') print(' Executing individual test scripts with various generated code directories') print(' Directories to be tested: ' + ', '.join(generated_dirs)) print(' Scripts to be tested: ' + ', '.join(SCRIPTS)) print('----------------') for genpydir in generated_dirs: for script in SCRIPTS: runScriptTest(options.libdir, options.gen_base, genpydir, script) print('----------------') print(' Executing Client/Server tests with various generated code directories') print(' Servers to be tested: ' + ', '.join(servers)) print(' Directories to be tested: ' + ', '.join(generated_dirs)) print(' Protocols to be tested: ' + ', '.join(PROTOS)) print(' Options to be tested: ZLIB(yes/no), SSL(yes/no)') print('----------------') if options.all: tests.run_all_tests() else: tests.test_feature('gendir', generated_dirs) tests.test_feature('server', servers) tests.test_feature('proto', PROTOS) tests.test_feature('zlib', [False, True]) tests.test_feature('ssl', [False, True]) if __name__ == '__main__': sys.exit(main())