blob: f084a41eddf8caaeb0e0604eaca95e2ef67de345 [file] [log] [blame]
Mark Slee5299a952007-10-05 00:13:24 +00001#!/usr/bin/env python
2
David Reissea2cba82009-03-30 21:35:00 +00003#
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
Bryan Duxbury59d4efd2011-03-21 17:38:22 +000022from __future__ import division
Nobuaki Sukegawa760511f2015-11-06 21:24:16 +090023from __future__ import print_function
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090024import copy
25import glob
26import os
27import signal
Roger Meier9b328532014-04-21 21:22:54 +020028import socket
Mark Slee5299a952007-10-05 00:13:24 +000029import subprocess
30import sys
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090031import time
Bryan Duxbury59d4efd2011-03-21 17:38:22 +000032from optparse import OptionParser
33
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090034SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
35ROOT_DIR = os.path.dirname(os.path.dirname(SCRIPT_DIR))
36DEFAULT_LIBDIR_GLOB = os.path.join(ROOT_DIR, 'lib', 'py', 'build', 'lib.*')
37DEFAULT_LIBDIR_PY3 = os.path.join(ROOT_DIR, 'lib', 'py', 'build', 'lib')
Bryan Duxbury59d4efd2011-03-21 17:38:22 +000038
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090039SCRIPTS = [
Nobuaki Sukegawae841b3d2015-11-17 11:01:17 +090040 'TestFrozen.py',
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090041 'TSimpleJSONProtocolTest.py',
42 'SerializationTest.py',
43 'TestEof.py',
44 'TestSyntax.py',
45 'TestSocket.py',
46]
Bryan Duxbury59d4efd2011-03-21 17:38:22 +000047FRAMED = ["TNonblockingServer"]
Bryan Duxbury16066592011-03-22 18:06:04 +000048SKIP_ZLIB = ['TNonblockingServer', 'THttpServer']
49SKIP_SSL = ['TNonblockingServer', 'THttpServer']
Roger Meier6857b7f2015-09-16 19:53:07 +020050EXTRA_DELAY = dict(TProcessPoolServer=5.5)
Bryan Duxbury59d4efd2011-03-21 17:38:22 +000051
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090052PROTOS = [
53 'accel',
54 'binary',
55 'compact',
56 'json',
57]
Bryan Duxbury59d4efd2011-03-21 17:38:22 +000058
59SERVERS = [
60 "TSimpleServer",
61 "TThreadedServer",
62 "TThreadPoolServer",
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090063 "TProcessPoolServer",
Bryan Duxbury59d4efd2011-03-21 17:38:22 +000064 "TForkingServer",
65 "TNonblockingServer",
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090066 "THttpServer",
67]
Bryan Duxbury59d4efd2011-03-21 17:38:22 +000068
Mark Slee5299a952007-10-05 00:13:24 +000069
David Reiss2a4bfd62008-04-07 23:45:00 +000070def relfile(fname):
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090071 return os.path.join(SCRIPT_DIR, fname)
David Reiss2a4bfd62008-04-07 23:45:00 +000072
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090073
74def setup_pypath(dirs):
75 env = copy.copy(os.environ)
76 pypath = env.get('PYTHONPATH', None)
77 if pypath:
78 dirs.append(pypath)
79 env['PYTHONPATH'] = ':'.join(dirs)
80 return env
81
82
83def runScriptTest(libdir, genpydir, script):
84 env = setup_pypath([libdir, genpydir])
85 script_args = [sys.executable, relfile(script)]
Nobuaki Sukegawa760511f2015-11-06 21:24:16 +090086 print('\nTesting script: %s\n----' % (' '.join(script_args)))
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090087 ret = subprocess.call(script_args, env=env)
Roger Meierf4eec7a2011-09-11 18:16:21 +000088 if ret != 0:
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090089 print('*** FAILED ***', file=sys.stderr)
90 print('LIBDIR: %s' % libdir, file=sys.stderr)
91 print('PY_GEN: %s' % genpydir, file=sys.stderr)
92 print('SCRIPT: %s' % script, file=sys.stderr)
Roger Meierf4eec7a2011-09-11 18:16:21 +000093 raise Exception("Script subprocess failed, retcode=%d, args: %s" % (ret, ' '.join(script_args)))
Roger Meier76150722014-05-31 22:22:07 +020094
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090095
96def runServiceTest(libdir, genpydir, server_class, proto, port, use_zlib, use_ssl, verbose):
97 env = setup_pypath([libdir, genpydir])
Bryan Duxbury16066592011-03-22 18:06:04 +000098 # Build command line arguments
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090099 server_args = [sys.executable, relfile('TestServer.py')]
100 cli_args = [sys.executable, relfile('TestClient.py')]
Bryan Duxbury16066592011-03-22 18:06:04 +0000101 for which in (server_args, cli_args):
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900102 which.append('--protocol=%s' % proto) # accel, binary, compact or json
103 which.append('--port=%d' % port) # default to 9090
Bryan Duxbury16066592011-03-22 18:06:04 +0000104 if use_zlib:
105 which.append('--zlib')
106 if use_ssl:
107 which.append('--ssl')
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900108 if verbose == 0:
Bryan Duxbury16066592011-03-22 18:06:04 +0000109 which.append('-q')
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900110 if verbose == 2:
Bryan Duxbury16066592011-03-22 18:06:04 +0000111 which.append('-v')
112 # server-specific option to select server class
113 server_args.append(server_class)
114 # client-specific cmdline options
115 if server_class in FRAMED:
Roger Meier76150722014-05-31 22:22:07 +0200116 cli_args.append('--transport=framed')
117 else:
118 cli_args.append('--transport=buffered')
Bryan Duxbury16066592011-03-22 18:06:04 +0000119 if server_class == 'THttpServer':
120 cli_args.append('--http=/')
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900121 if verbose > 0:
Nobuaki Sukegawa760511f2015-11-06 21:24:16 +0900122 print('Testing server %s: %s' % (server_class, ' '.join(server_args)))
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900123 serverproc = subprocess.Popen(server_args, env=env)
Roger Meier9b328532014-04-21 21:22:54 +0200124
125 def ensureServerAlive():
126 if serverproc.poll() is not None:
Nobuaki Sukegawa760511f2015-11-06 21:24:16 +0900127 print(('FAIL: Server process (%s) failed with retcode %d')
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900128 % (' '.join(server_args), serverproc.returncode))
Roger Meier9b328532014-04-21 21:22:54 +0200129 raise Exception('Server subprocess %s died, args: %s'
130 % (server_class, ' '.join(server_args)))
131
132 # Wait for the server to start accepting connections on the given port.
133 sock = socket.socket()
134 sleep_time = 0.1 # Seconds
135 max_attempts = 100
136 try:
137 attempt = 0
138 while sock.connect_ex(('127.0.0.1', port)) != 0:
139 attempt += 1
140 if attempt >= max_attempts:
141 raise Exception("TestServer not ready on port %d after %.2f seconds"
142 % (port, sleep_time * attempt))
143 ensureServerAlive()
144 time.sleep(sleep_time)
145 finally:
146 sock.close()
147
Bryan Duxbury16066592011-03-22 18:06:04 +0000148 try:
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900149 if verbose > 0:
Nobuaki Sukegawa760511f2015-11-06 21:24:16 +0900150 print('Testing client: %s' % (' '.join(cli_args)))
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900151 ret = subprocess.call(cli_args, env=env)
Bryan Duxbury16066592011-03-22 18:06:04 +0000152 if ret != 0:
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900153 print('*** FAILED ***', file=sys.stderr)
154 print('LIBDIR: %s' % libdir, file=sys.stderr)
155 print('PY_GEN: %s' % genpydir, file=sys.stderr)
Bryan Duxbury16066592011-03-22 18:06:04 +0000156 raise Exception("Client subprocess failed, retcode=%d, args: %s" % (ret, ' '.join(cli_args)))
157 finally:
158 # check that server didn't die
Roger Meier9b328532014-04-21 21:22:54 +0200159 ensureServerAlive()
160 extra_sleep = EXTRA_DELAY.get(server_class, 0)
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900161 if extra_sleep > 0 and verbose > 0:
Nobuaki Sukegawa760511f2015-11-06 21:24:16 +0900162 print('Giving %s (proto=%s,zlib=%s,ssl=%s) an extra %d seconds for child'
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900163 'processes to terminate via alarm'
164 % (server_class, proto, use_zlib, use_ssl, extra_sleep))
Roger Meier9b328532014-04-21 21:22:54 +0200165 time.sleep(extra_sleep)
166 os.kill(serverproc.pid, signal.SIGKILL)
167 serverproc.wait()
David Reissbcaa2ad2008-06-10 22:55:26 +0000168
Roger Meier76150722014-05-31 22:22:07 +0200169
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900170class TestCases(object):
171 def __init__(self, libdir, port, gendirs, servers, verbose):
172 self.libdir = libdir
173 self.port = port
174 self.verbose = verbose
175 self.gendirs = gendirs
176 self.servers = servers
177
178 def default_conf(self):
179 return {
180 'gendir': self.gendirs[0],
181 'server': self.servers[0],
182 'proto': PROTOS[0],
183 'zlib': False,
184 'ssl': False,
185 }
186
187 def run(self, conf, test_count):
188 with_zlib = conf['zlib']
189 with_ssl = conf['ssl']
190 try_server = conf['server']
191 try_proto = conf['proto']
192 genpydir = conf['gendir']
193 # skip any servers that don't work with the Zlib transport
194 if with_zlib and try_server in SKIP_ZLIB:
195 return False
196 # skip any servers that don't work with SSL
197 if with_ssl and try_server in SKIP_SSL:
198 return False
199 if self.verbose > 0:
200 print('\nTest run #%d: (includes %s) Server=%s, Proto=%s, zlib=%s, SSL=%s'
201 % (test_count, genpydir, try_server, try_proto, with_zlib, with_ssl))
202 runServiceTest(self.libdir, genpydir, try_server, try_proto, self.port, with_zlib, with_ssl, self.verbose)
203 if self.verbose > 0:
204 print('OK: Finished (includes %s) %s / %s proto / zlib=%s / SSL=%s. %d combinations tested.'
205 % (genpydir, try_server, try_proto, with_zlib, with_ssl, test_count))
206 return True
207
208 def test_feature(self, name, values):
209 test_count = 0
210 conf = self.default_conf()
211 for try_server in values:
212 conf[name] = try_server
213 if self.run(conf, test_count):
214 test_count += 1
215 return test_count
216
217 def run_all_tests(self):
218 test_count = 0
219 for try_server in self.servers:
220 for genpydir in self.gendirs:
221 for try_proto in PROTOS:
222 for with_zlib in (False, True):
223 # skip any servers that don't work with the Zlib transport
224 if with_zlib and try_server in SKIP_ZLIB:
225 continue
226 for with_ssl in (False, True):
227 # skip any servers that don't work with SSL
228 if with_ssl and try_server in SKIP_SSL:
229 continue
230 test_count += 1
231 if self.verbose > 0:
232 print('\nTest run #%d: (includes %s) Server=%s, Proto=%s, zlib=%s, SSL=%s'
233 % (test_count, genpydir, try_server, try_proto, with_zlib, with_ssl))
234 runServiceTest(self.libdir, genpydir, try_server, try_proto, self.port, with_zlib, with_ssl)
235 if self.verbose > 0:
236 print('OK: Finished (includes %s) %s / %s proto / zlib=%s / SSL=%s. %d combinations tested.'
237 % (genpydir, try_server, try_proto, with_zlib, with_ssl, test_count))
238 return test_count
239
240
241def default_libdir():
242 if sys.version_info[0] == 2:
243 return glob.glob(DEFAULT_LIBDIR_GLOB)[0]
244 else:
245 return DEFAULT_LIBDIR_PY3
246
247
248def main():
249 parser = OptionParser()
250 parser.add_option('--all', action="store_true", dest='all')
251 parser.add_option('--genpydirs', type='string', dest='genpydirs',
252 default='default,slots,newstyle,newstyleslots,dynamic,dynamicslots',
253 help='directory extensions for generated code, used as suffixes for \"gen-py-*\" added sys.path for individual tests')
254 parser.add_option("--port", type="int", dest="port", default=9090,
255 help="port number for server to listen on")
256 parser.add_option('-v', '--verbose', action="store_const",
257 dest="verbose", const=2,
258 help="verbose output")
259 parser.add_option('-q', '--quiet', action="store_const",
260 dest="verbose", const=0,
261 help="minimal output")
262 parser.add_option('-L', '--libdir', dest="libdir", default=default_libdir(),
263 help="directory path that contains Thrift Python library")
264 parser.set_defaults(verbose=1)
265 options, args = parser.parse_args()
266
267 generated_dirs = []
268 for gp_dir in options.genpydirs.split(','):
269 generated_dirs.append('gen-py-%s' % (gp_dir))
270
271 # commandline permits a single class name to be specified to override SERVERS=[...]
272 servers = SERVERS
273 if len(args) == 1:
274 if args[0] in SERVERS:
275 servers = args
276 else:
277 print('Unavailable server type "%s", please choose one of: %s' % (args[0], servers))
278 sys.exit(0)
279
280 tests = TestCases(options.libdir, options.port, generated_dirs, servers, options.verbose)
281
282 # run tests without a client/server first
283 print('----------------')
284 print(' Executing individual test scripts with various generated code directories')
285 print(' Directories to be tested: ' + ', '.join(generated_dirs))
286 print(' Scripts to be tested: ' + ', '.join(SCRIPTS))
287 print('----------------')
Roger Meierf4eec7a2011-09-11 18:16:21 +0000288 for genpydir in generated_dirs:
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900289 for script in SCRIPTS:
290 runScriptTest(options.libdir, genpydir, script)
291
292 print('----------------')
293 print(' Executing Client/Server tests with various generated code directories')
294 print(' Servers to be tested: ' + ', '.join(servers))
295 print(' Directories to be tested: ' + ', '.join(generated_dirs))
296 print(' Protocols to be tested: ' + ', '.join(PROTOS))
297 print(' Options to be tested: ZLIB(yes/no), SSL(yes/no)')
298 print('----------------')
299
300 if options.all:
301 tests.run_all_tests()
302 else:
303 tests.test_feature('gendir', generated_dirs)
304 tests.test_feature('server', servers)
305 tests.test_feature('proto', PROTOS)
306 tests.test_feature('zlib', [False, True])
307 tests.test_feature('ssl', [False, True])
308
309
310if __name__ == '__main__':
311 sys.exit(main())