blob: 583d88a64228104a45601051e2dba0db78ab3014 [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 Sukegawa06e8fd42016-02-28 12:50:03 +090024import platform
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090025import copy
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090026import 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 Sukegawa7af189a2016-02-11 16:21:01 +090034from util import local_libpath
35
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090036SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
Bryan Duxbury59d4efd2011-03-21 17:38:22 +000037
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090038SCRIPTS = [
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090039 'FastbinaryTest.py',
40 'TestFrozen.py',
Ozan Can Altioke46419b2018-03-20 15:02:28 +030041 'TestRenderedDoubleConstants.py',
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090042 'TSimpleJSONProtocolTest.py',
43 'SerializationTest.py',
44 'TestEof.py',
45 'TestSyntax.py',
46 'TestSocket.py',
arkuhn7de26c42024-06-19 01:20:47 -040047 'TestTypes.py'
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090048]
Bryan Duxbury59d4efd2011-03-21 17:38:22 +000049FRAMED = ["TNonblockingServer"]
Bryan Duxbury16066592011-03-22 18:06:04 +000050SKIP_ZLIB = ['TNonblockingServer', 'THttpServer']
Nobuaki Sukegawa4626fd82017-02-12 21:11:36 +090051SKIP_SSL = ['THttpServer']
Roger Meier6857b7f2015-09-16 19:53:07 +020052EXTRA_DELAY = dict(TProcessPoolServer=5.5)
Bryan Duxbury59d4efd2011-03-21 17:38:22 +000053
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090054PROTOS = [
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090055 'accel',
Nobuaki Sukegawa6525f6a2016-02-11 13:58:39 +090056 'accelc',
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090057 'binary',
58 'compact',
59 'json',
Neil Williams66a44c52018-08-13 16:12:24 -070060 'header',
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090061]
Bryan Duxbury59d4efd2011-03-21 17:38:22 +000062
Nobuaki Sukegawa06e8fd42016-02-28 12:50:03 +090063
64def default_servers():
65 servers = [
66 'TSimpleServer',
67 'TThreadedServer',
68 'TThreadPoolServer',
69 'TNonblockingServer',
70 'THttpServer',
71 ]
72 if platform.system() != 'Windows':
73 servers.append('TProcessPoolServer')
74 servers.append('TForkingServer')
75 return servers
Bryan Duxbury59d4efd2011-03-21 17:38:22 +000076
Mark Slee5299a952007-10-05 00:13:24 +000077
David Reiss2a4bfd62008-04-07 23:45:00 +000078def relfile(fname):
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090079 return os.path.join(SCRIPT_DIR, fname)
David Reiss2a4bfd62008-04-07 23:45:00 +000080
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090081
Nobuaki Sukegawaa3b88a02016-01-06 20:44:17 +090082def setup_pypath(libdir, gendir):
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090083 dirs = [libdir, gendir]
84 env = copy.deepcopy(os.environ)
85 pypath = env.get('PYTHONPATH', None)
86 if pypath:
87 dirs.append(pypath)
Nobuaki Sukegawa06e8fd42016-02-28 12:50:03 +090088 env['PYTHONPATH'] = os.pathsep.join(dirs)
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090089 if gendir.endswith('gen-py-no_utf8strings'):
90 env['THRIFT_TEST_PY_NO_UTF8STRINGS'] = '1'
91 return env
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090092
93
Nobuaki Sukegawaa3b88a02016-01-06 20:44:17 +090094def runScriptTest(libdir, genbase, genpydir, script):
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090095 env = setup_pypath(libdir, os.path.join(genbase, genpydir))
96 script_args = [sys.executable, relfile(script)]
97 print('\nTesting script: %s\n----' % (' '.join(script_args)))
98 ret = subprocess.call(script_args, env=env)
99 if ret != 0:
100 print('*** FAILED ***', file=sys.stderr)
101 print('LIBDIR: %s' % libdir, file=sys.stderr)
102 print('PY_GEN: %s' % genpydir, file=sys.stderr)
103 print('SCRIPT: %s' % script, file=sys.stderr)
104 raise Exception("Script subprocess failed, retcode=%d, args: %s" % (ret, ' '.join(script_args)))
Roger Meier76150722014-05-31 22:22:07 +0200105
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900106
Nobuaki Sukegawaa3b88a02016-01-06 20:44:17 +0900107def runServiceTest(libdir, genbase, genpydir, server_class, proto, port, use_zlib, use_ssl, verbose):
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900108 env = setup_pypath(libdir, os.path.join(genbase, genpydir))
109 # Build command line arguments
110 server_args = [sys.executable, relfile('TestServer.py')]
111 cli_args = [sys.executable, relfile('TestClient.py')]
112 for which in (server_args, cli_args):
113 which.append('--protocol=%s' % proto) # accel, binary, compact or json
114 which.append('--port=%d' % port) # default to 9090
115 if use_zlib:
116 which.append('--zlib')
117 if use_ssl:
118 which.append('--ssl')
119 if verbose == 0:
120 which.append('-q')
121 if verbose == 2:
122 which.append('-v')
123 # server-specific option to select server class
124 server_args.append(server_class)
125 # client-specific cmdline options
126 if server_class in FRAMED:
127 cli_args.append('--transport=framed')
128 else:
129 cli_args.append('--transport=buffered')
130 if server_class == 'THttpServer':
131 cli_args.append('--http=/')
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900132 if verbose > 0:
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900133 print('Testing server %s: %s' % (server_class, ' '.join(server_args)))
134 serverproc = subprocess.Popen(server_args, env=env)
135
136 def ensureServerAlive():
137 if serverproc.poll() is not None:
138 print(('FAIL: Server process (%s) failed with retcode %d')
139 % (' '.join(server_args), serverproc.returncode))
140 raise Exception('Server subprocess %s died, args: %s'
141 % (server_class, ' '.join(server_args)))
142
143 # Wait for the server to start accepting connections on the given port.
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900144 sleep_time = 0.1 # Seconds
145 max_attempts = 100
Nobuaki Sukegawae9b32342016-02-27 03:44:02 +0900146 attempt = 0
147 while True:
148 sock4 = socket.socket()
149 sock6 = socket.socket(socket.AF_INET6)
150 try:
151 if sock4.connect_ex(('127.0.0.1', port)) == 0 \
152 or sock6.connect_ex(('::1', port)) == 0:
153 break
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900154 attempt += 1
155 if attempt >= max_attempts:
156 raise Exception("TestServer not ready on port %d after %.2f seconds"
157 % (port, sleep_time * attempt))
158 ensureServerAlive()
159 time.sleep(sleep_time)
Nobuaki Sukegawae9b32342016-02-27 03:44:02 +0900160 finally:
161 sock4.close()
162 sock6.close()
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900163
164 try:
165 if verbose > 0:
166 print('Testing client: %s' % (' '.join(cli_args)))
167 ret = subprocess.call(cli_args, env=env)
168 if ret != 0:
169 print('*** FAILED ***', file=sys.stderr)
170 print('LIBDIR: %s' % libdir, file=sys.stderr)
171 print('PY_GEN: %s' % genpydir, file=sys.stderr)
172 raise Exception("Client subprocess failed, retcode=%d, args: %s" % (ret, ' '.join(cli_args)))
173 finally:
174 # check that server didn't die
175 ensureServerAlive()
176 extra_sleep = EXTRA_DELAY.get(server_class, 0)
177 if extra_sleep > 0 and verbose > 0:
178 print('Giving %s (proto=%s,zlib=%s,ssl=%s) an extra %d seconds for child'
179 'processes to terminate via alarm'
180 % (server_class, proto, use_zlib, use_ssl, extra_sleep))
181 time.sleep(extra_sleep)
Nobuaki Sukegawa06e8fd42016-02-28 12:50:03 +0900182 sig = signal.SIGKILL if platform.system() != 'Windows' else signal.SIGABRT
183 os.kill(serverproc.pid, sig)
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900184 serverproc.wait()
David Reissbcaa2ad2008-06-10 22:55:26 +0000185
Roger Meier76150722014-05-31 22:22:07 +0200186
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900187class TestCases(object):
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900188 def __init__(self, genbase, libdir, port, gendirs, servers, verbose):
189 self.genbase = genbase
190 self.libdir = libdir
191 self.port = port
192 self.verbose = verbose
193 self.gendirs = gendirs
194 self.servers = servers
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900195
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900196 def default_conf(self):
197 return {
198 'gendir': self.gendirs[0],
199 'server': self.servers[0],
200 'proto': PROTOS[0],
201 'zlib': False,
202 'ssl': False,
203 }
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900204
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900205 def run(self, conf, test_count):
206 with_zlib = conf['zlib']
207 with_ssl = conf['ssl']
208 try_server = conf['server']
209 try_proto = conf['proto']
210 genpydir = conf['gendir']
211 # skip any servers that don't work with the Zlib transport
212 if with_zlib and try_server in SKIP_ZLIB:
213 return False
214 # skip any servers that don't work with SSL
215 if with_ssl and try_server in SKIP_SSL:
216 return False
217 if self.verbose > 0:
218 print('\nTest run #%d: (includes %s) Server=%s, Proto=%s, zlib=%s, SSL=%s'
219 % (test_count, genpydir, try_server, try_proto, with_zlib, with_ssl))
220 runServiceTest(self.libdir, self.genbase, genpydir, try_server, try_proto, self.port, with_zlib, with_ssl, self.verbose)
221 if self.verbose > 0:
222 print('OK: Finished (includes %s) %s / %s proto / zlib=%s / SSL=%s. %d combinations tested.'
223 % (genpydir, try_server, try_proto, with_zlib, with_ssl, test_count))
224 return True
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900225
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900226 def test_feature(self, name, values):
227 test_count = 0
228 conf = self.default_conf()
229 for try_server in values:
230 conf[name] = try_server
231 if self.run(conf, test_count):
232 test_count += 1
233 return test_count
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900234
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900235 def run_all_tests(self):
236 test_count = 0
237 for try_server in self.servers:
238 for genpydir in self.gendirs:
239 for try_proto in PROTOS:
240 for with_zlib in (False, True):
241 # skip any servers that don't work with the Zlib transport
242 if with_zlib and try_server in SKIP_ZLIB:
243 continue
244 for with_ssl in (False, True):
245 # skip any servers that don't work with SSL
246 if with_ssl and try_server in SKIP_SSL:
247 continue
248 test_count += 1
249 if self.verbose > 0:
250 print('\nTest run #%d: (includes %s) Server=%s, Proto=%s, zlib=%s, SSL=%s'
251 % (test_count, genpydir, try_server, try_proto, with_zlib, with_ssl))
252 runServiceTest(self.libdir, self.genbase, genpydir, try_server, try_proto, self.port, with_zlib, with_ssl)
253 if self.verbose > 0:
254 print('OK: Finished (includes %s) %s / %s proto / zlib=%s / SSL=%s. %d combinations tested.'
255 % (genpydir, try_server, try_proto, with_zlib, with_ssl, test_count))
256 return test_count
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900257
258
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900259def main():
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900260 parser = OptionParser()
261 parser.add_option('--all', action="store_true", dest='all')
262 parser.add_option('--genpydirs', type='string', dest='genpydirs',
arkuhn12b01162024-02-23 20:18:55 -0500263 default='default,slots,oldstyle,no_utf8strings,dynamic,dynamicslots,enum,type_hints',
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900264 help='directory extensions for generated code, used as suffixes for \"gen-py-*\" added sys.path for individual tests')
265 parser.add_option("--port", type="int", dest="port", default=9090,
266 help="port number for server to listen on")
267 parser.add_option('-v', '--verbose', action="store_const",
268 dest="verbose", const=2,
269 help="verbose output")
270 parser.add_option('-q', '--quiet', action="store_const",
271 dest="verbose", const=0,
272 help="minimal output")
Nobuaki Sukegawa7af189a2016-02-11 16:21:01 +0900273 parser.add_option('-L', '--libdir', dest="libdir", default=local_libpath(),
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900274 help="directory path that contains Thrift Python library")
275 parser.add_option('--gen-base', dest="gen_base", default=SCRIPT_DIR,
276 help="directory path that contains Thrift Python library")
277 parser.set_defaults(verbose=1)
278 options, args = parser.parse_args()
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900279
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900280 generated_dirs = []
281 for gp_dir in options.genpydirs.split(','):
282 generated_dirs.append('gen-py-%s' % (gp_dir))
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900283
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900284 # commandline permits a single class name to be specified to override SERVERS=[...]
Nobuaki Sukegawa06e8fd42016-02-28 12:50:03 +0900285 servers = default_servers()
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900286 if len(args) == 1:
Nobuaki Sukegawa06e8fd42016-02-28 12:50:03 +0900287 if args[0] in servers:
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900288 servers = args
289 else:
290 print('Unavailable server type "%s", please choose one of: %s' % (args[0], servers))
291 sys.exit(0)
292
293 tests = TestCases(options.gen_base, options.libdir, options.port, generated_dirs, servers, options.verbose)
294
295 # run tests without a client/server first
296 print('----------------')
297 print(' Executing individual test scripts with various generated code directories')
298 print(' Directories to be tested: ' + ', '.join(generated_dirs))
299 print(' Scripts to be tested: ' + ', '.join(SCRIPTS))
300 print('----------------')
301 for genpydir in generated_dirs:
302 for script in SCRIPTS:
303 runScriptTest(options.libdir, options.gen_base, genpydir, script)
304
305 print('----------------')
306 print(' Executing Client/Server tests with various generated code directories')
307 print(' Servers to be tested: ' + ', '.join(servers))
308 print(' Directories to be tested: ' + ', '.join(generated_dirs))
309 print(' Protocols to be tested: ' + ', '.join(PROTOS))
310 print(' Options to be tested: ZLIB(yes/no), SSL(yes/no)')
311 print('----------------')
312
313 if options.all:
314 tests.run_all_tests()
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900315 else:
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900316 tests.test_feature('gendir', generated_dirs)
317 tests.test_feature('server', servers)
318 tests.test_feature('proto', PROTOS)
319 tests.test_feature('zlib', [False, True])
320 tests.test_feature('ssl', [False, True])
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900321
322
323if __name__ == '__main__':
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900324 sys.exit(main())