1#!/usr/bin/env python 2 3# 4# Licensed to the Apache Software Foundation (ASF) under one 5# or more contributor license agreements. See the NOTICE file 6# distributed with this work for additional information 7# regarding copyright ownership. The ASF licenses this file 8# to you under the Apache License, Version 2.0 (the 9# "License"); you may not use this file except in compliance 10# with the License. You may obtain a copy of the License at 11# 12# http://www.apache.org/licenses/LICENSE-2.0 13# 14# Unless required by applicable law or agreed to in writing, 15# software distributed under the License is distributed on an 16# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17# KIND, either express or implied. See the License for the 18# specific language governing permissions and limitations 19# under the License. 20# 21 22from __future__ import division 23from __future__ import print_function 24import platform 25import copy 26import os 27import signal 28import socket 29import subprocess 30import sys 31import time 32from optparse import OptionParser 33 34from util import local_libpath 35 36SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__)) 37 38SCRIPTS = [ 39 'FastbinaryTest.py', 40 'TestFrozen.py', 41 'TestRenderedDoubleConstants.py', 42 'TSimpleJSONProtocolTest.py', 43 'SerializationTest.py', 44 'TestEof.py', 45 'TestSyntax.py', 46 'TestSocket.py', 47] 48FRAMED = ["TNonblockingServer"] 49SKIP_ZLIB = ['TNonblockingServer', 'THttpServer'] 50SKIP_SSL = ['THttpServer'] 51EXTRA_DELAY = dict(TProcessPoolServer=5.5) 52 53PROTOS = [ 54 'accel', 55 'accelc', 56 'binary', 57 'compact', 58 'json', 59 'header', 60] 61 62 63def default_servers(): 64 servers = [ 65 'TSimpleServer', 66 'TThreadedServer', 67 'TThreadPoolServer', 68 'TNonblockingServer', 69 'THttpServer', 70 ] 71 if platform.system() != 'Windows': 72 servers.append('TProcessPoolServer') 73 servers.append('TForkingServer') 74 return servers 75 76 77def relfile(fname): 78 return os.path.join(SCRIPT_DIR, fname) 79 80 81def setup_pypath(libdir, gendir): 82 dirs = [libdir, gendir] 83 env = copy.deepcopy(os.environ) 84 pypath = env.get('PYTHONPATH', None) 85 if pypath: 86 dirs.append(pypath) 87 env['PYTHONPATH'] = os.pathsep.join(dirs) 88 if gendir.endswith('gen-py-no_utf8strings'): 89 env['THRIFT_TEST_PY_NO_UTF8STRINGS'] = '1' 90 return env 91 92 93def runScriptTest(libdir, genbase, genpydir, script): 94 env = setup_pypath(libdir, os.path.join(genbase, genpydir)) 95 script_args = [sys.executable, relfile(script)] 96 print('\nTesting script: %s\n----' % (' '.join(script_args))) 97 ret = subprocess.call(script_args, env=env) 98 if ret != 0: 99 print('*** FAILED ***', file=sys.stderr) 100 print('LIBDIR: %s' % libdir, file=sys.stderr) 101 print('PY_GEN: %s' % genpydir, file=sys.stderr) 102 print('SCRIPT: %s' % script, file=sys.stderr) 103 raise Exception("Script subprocess failed, retcode=%d, args: %s" % (ret, ' '.join(script_args))) 104 105 106def runServiceTest(libdir, genbase, genpydir, server_class, proto, port, use_zlib, use_ssl, verbose): 107 env = setup_pypath(libdir, os.path.join(genbase, genpydir)) 108 # Build command line arguments 109 server_args = [sys.executable, relfile('TestServer.py')] 110 cli_args = [sys.executable, relfile('TestClient.py')] 111 for which in (server_args, cli_args): 112 which.append('--protocol=%s' % proto) # accel, binary, compact or json 113 which.append('--port=%d' % port) # default to 9090 114 if use_zlib: 115 which.append('--zlib') 116 if use_ssl: 117 which.append('--ssl') 118 if verbose == 0: 119 which.append('-q') 120 if verbose == 2: 121 which.append('-v') 122 # server-specific option to select server class 123 server_args.append(server_class) 124 # client-specific cmdline options 125 if server_class in FRAMED: 126 cli_args.append('--transport=framed') 127 else: 128 cli_args.append('--transport=buffered') 129 if server_class == 'THttpServer': 130 cli_args.append('--http=/') 131 if verbose > 0: 132 print('Testing server %s: %s' % (server_class, ' '.join(server_args))) 133 serverproc = subprocess.Popen(server_args, env=env) 134 135 def ensureServerAlive(): 136 if serverproc.poll() is not None: 137 print(('FAIL: Server process (%s) failed with retcode %d') 138 % (' '.join(server_args), serverproc.returncode)) 139 raise Exception('Server subprocess %s died, args: %s' 140 % (server_class, ' '.join(server_args))) 141 142 # Wait for the server to start accepting connections on the given port. 143 sleep_time = 0.1 # Seconds 144 max_attempts = 100 145 attempt = 0 146 while True: 147 sock4 = socket.socket() 148 sock6 = socket.socket(socket.AF_INET6) 149 try: 150 if sock4.connect_ex(('127.0.0.1', port)) == 0 \ 151 or sock6.connect_ex(('::1', port)) == 0: 152 break 153 attempt += 1 154 if attempt >= max_attempts: 155 raise Exception("TestServer not ready on port %d after %.2f seconds" 156 % (port, sleep_time * attempt)) 157 ensureServerAlive() 158 time.sleep(sleep_time) 159 finally: 160 sock4.close() 161 sock6.close() 162 163 try: 164 if verbose > 0: 165 print('Testing client: %s' % (' '.join(cli_args))) 166 ret = subprocess.call(cli_args, env=env) 167 if ret != 0: 168 print('*** FAILED ***', file=sys.stderr) 169 print('LIBDIR: %s' % libdir, file=sys.stderr) 170 print('PY_GEN: %s' % genpydir, file=sys.stderr) 171 raise Exception("Client subprocess failed, retcode=%d, args: %s" % (ret, ' '.join(cli_args))) 172 finally: 173 # check that server didn't die 174 ensureServerAlive() 175 extra_sleep = EXTRA_DELAY.get(server_class, 0) 176 if extra_sleep > 0 and verbose > 0: 177 print('Giving %s (proto=%s,zlib=%s,ssl=%s) an extra %d seconds for child' 178 'processes to terminate via alarm' 179 % (server_class, proto, use_zlib, use_ssl, extra_sleep)) 180 time.sleep(extra_sleep) 181 sig = signal.SIGKILL if platform.system() != 'Windows' else signal.SIGABRT 182 os.kill(serverproc.pid, sig) 183 serverproc.wait() 184 185 186class TestCases(object): 187 def __init__(self, genbase, libdir, port, gendirs, servers, verbose): 188 self.genbase = genbase 189 self.libdir = libdir 190 self.port = port 191 self.verbose = verbose 192 self.gendirs = gendirs 193 self.servers = servers 194 195 def default_conf(self): 196 return { 197 'gendir': self.gendirs[0], 198 'server': self.servers[0], 199 'proto': PROTOS[0], 200 'zlib': False, 201 'ssl': False, 202 } 203 204 def run(self, conf, test_count): 205 with_zlib = conf['zlib'] 206 with_ssl = conf['ssl'] 207 try_server = conf['server'] 208 try_proto = conf['proto'] 209 genpydir = conf['gendir'] 210 # skip any servers that don't work with the Zlib transport 211 if with_zlib and try_server in SKIP_ZLIB: 212 return False 213 # skip any servers that don't work with SSL 214 if with_ssl and try_server in SKIP_SSL: 215 return False 216 if self.verbose > 0: 217 print('\nTest run #%d: (includes %s) Server=%s, Proto=%s, zlib=%s, SSL=%s' 218 % (test_count, genpydir, try_server, try_proto, with_zlib, with_ssl)) 219 runServiceTest(self.libdir, self.genbase, genpydir, try_server, try_proto, self.port, with_zlib, with_ssl, self.verbose) 220 if self.verbose > 0: 221 print('OK: Finished (includes %s) %s / %s proto / zlib=%s / SSL=%s. %d combinations tested.' 222 % (genpydir, try_server, try_proto, with_zlib, with_ssl, test_count)) 223 return True 224 225 def test_feature(self, name, values): 226 test_count = 0 227 conf = self.default_conf() 228 for try_server in values: 229 conf[name] = try_server 230 if self.run(conf, test_count): 231 test_count += 1 232 return test_count 233 234 def run_all_tests(self): 235 test_count = 0 236 for try_server in self.servers: 237 for genpydir in self.gendirs: 238 for try_proto in PROTOS: 239 for with_zlib in (False, True): 240 # skip any servers that don't work with the Zlib transport 241 if with_zlib and try_server in SKIP_ZLIB: 242 continue 243 for with_ssl in (False, True): 244 # skip any servers that don't work with SSL 245 if with_ssl and try_server in SKIP_SSL: 246 continue 247 test_count += 1 248 if self.verbose > 0: 249 print('\nTest run #%d: (includes %s) Server=%s, Proto=%s, zlib=%s, SSL=%s' 250 % (test_count, genpydir, try_server, try_proto, with_zlib, with_ssl)) 251 runServiceTest(self.libdir, self.genbase, genpydir, try_server, try_proto, self.port, with_zlib, with_ssl) 252 if self.verbose > 0: 253 print('OK: Finished (includes %s) %s / %s proto / zlib=%s / SSL=%s. %d combinations tested.' 254 % (genpydir, try_server, try_proto, with_zlib, with_ssl, test_count)) 255 return test_count 256 257 258def main(): 259 parser = OptionParser() 260 parser.add_option('--all', action="store_true", dest='all') 261 parser.add_option('--genpydirs', type='string', dest='genpydirs', 262 default='default,slots,oldstyle,no_utf8strings,dynamic,dynamicslots,enum', 263 help='directory extensions for generated code, used as suffixes for \"gen-py-*\" added sys.path for individual tests') 264 parser.add_option("--port", type="int", dest="port", default=9090, 265 help="port number for server to listen on") 266 parser.add_option('-v', '--verbose', action="store_const", 267 dest="verbose", const=2, 268 help="verbose output") 269 parser.add_option('-q', '--quiet', action="store_const", 270 dest="verbose", const=0, 271 help="minimal output") 272 parser.add_option('-L', '--libdir', dest="libdir", default=local_libpath(), 273 help="directory path that contains Thrift Python library") 274 parser.add_option('--gen-base', dest="gen_base", default=SCRIPT_DIR, 275 help="directory path that contains Thrift Python library") 276 parser.set_defaults(verbose=1) 277 options, args = parser.parse_args() 278 279 generated_dirs = [] 280 for gp_dir in options.genpydirs.split(','): 281 generated_dirs.append('gen-py-%s' % (gp_dir)) 282 283 # commandline permits a single class name to be specified to override SERVERS=[...] 284 servers = default_servers() 285 if len(args) == 1: 286 if args[0] in servers: 287 servers = args 288 else: 289 print('Unavailable server type "%s", please choose one of: %s' % (args[0], servers)) 290 sys.exit(0) 291 292 tests = TestCases(options.gen_base, options.libdir, options.port, generated_dirs, servers, options.verbose) 293 294 # run tests without a client/server first 295 print('----------------') 296 print(' Executing individual test scripts with various generated code directories') 297 print(' Directories to be tested: ' + ', '.join(generated_dirs)) 298 print(' Scripts to be tested: ' + ', '.join(SCRIPTS)) 299 print('----------------') 300 for genpydir in generated_dirs: 301 for script in SCRIPTS: 302 runScriptTest(options.libdir, options.gen_base, genpydir, script) 303 304 print('----------------') 305 print(' Executing Client/Server tests with various generated code directories') 306 print(' Servers to be tested: ' + ', '.join(servers)) 307 print(' Directories to be tested: ' + ', '.join(generated_dirs)) 308 print(' Protocols to be tested: ' + ', '.join(PROTOS)) 309 print(' Options to be tested: ZLIB(yes/no), SSL(yes/no)') 310 print('----------------') 311 312 if options.all: 313 tests.run_all_tests() 314 else: 315 tests.test_feature('gendir', generated_dirs) 316 tests.test_feature('server', servers) 317 tests.test_feature('proto', PROTOS) 318 tests.test_feature('zlib', [False, True]) 319 tests.test_feature('ssl', [False, True]) 320 321 322if __name__ == '__main__': 323 sys.exit(main()) 324