blob: 809c93bba02dc3455d2e0719f223ef95af48d9f4 [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
Nobuaki Sukegawa06e8fd42016-02-28 12:50:03 +090022import platform
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090023import copy
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090024import os
25import signal
Roger Meier9b328532014-04-21 21:22:54 +020026import socket
Mark Slee5299a952007-10-05 00:13:24 +000027import subprocess
28import sys
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090029import time
Bryan Duxbury59d4efd2011-03-21 17:38:22 +000030from optparse import OptionParser
31
Nobuaki Sukegawa7af189a2016-02-11 16:21:01 +090032from util import local_libpath
33
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090034SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
Bryan Duxbury59d4efd2011-03-21 17:38:22 +000035
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090036SCRIPTS = [
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090037 'FastbinaryTest.py',
38 'TestFrozen.py',
Ozan Can Altioke46419b2018-03-20 15:02:28 +030039 'TestRenderedDoubleConstants.py',
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090040 'TSimpleJSONProtocolTest.py',
41 'SerializationTest.py',
42 'TestEof.py',
43 'TestSyntax.py',
44 'TestSocket.py',
arkuhn7de26c42024-06-19 01:20:47 -040045 'TestTypes.py'
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090046]
Bryan Duxbury59d4efd2011-03-21 17:38:22 +000047FRAMED = ["TNonblockingServer"]
Bryan Duxbury16066592011-03-22 18:06:04 +000048SKIP_ZLIB = ['TNonblockingServer', 'THttpServer']
Nobuaki Sukegawa4626fd82017-02-12 21:11:36 +090049SKIP_SSL = ['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 = [
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090053 'accel',
Nobuaki Sukegawa6525f6a2016-02-11 13:58:39 +090054 'accelc',
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090055 'binary',
56 'compact',
57 'json',
Neil Williams66a44c52018-08-13 16:12:24 -070058 'header',
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090059]
Bryan Duxbury59d4efd2011-03-21 17:38:22 +000060
Nobuaki Sukegawa06e8fd42016-02-28 12:50:03 +090061
62def default_servers():
63 servers = [
64 'TSimpleServer',
65 'TThreadedServer',
66 'TThreadPoolServer',
67 'TNonblockingServer',
68 'THttpServer',
69 ]
70 if platform.system() != 'Windows':
71 servers.append('TProcessPoolServer')
72 servers.append('TForkingServer')
73 return servers
Bryan Duxbury59d4efd2011-03-21 17:38:22 +000074
Mark Slee5299a952007-10-05 00:13:24 +000075
David Reiss2a4bfd62008-04-07 23:45:00 +000076def relfile(fname):
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090077 return os.path.join(SCRIPT_DIR, fname)
David Reiss2a4bfd62008-04-07 23:45:00 +000078
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090079
Nobuaki Sukegawaa3b88a02016-01-06 20:44:17 +090080def setup_pypath(libdir, gendir):
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090081 dirs = [libdir, gendir]
82 env = copy.deepcopy(os.environ)
83 pypath = env.get('PYTHONPATH', None)
84 if pypath:
85 dirs.append(pypath)
Nobuaki Sukegawa06e8fd42016-02-28 12:50:03 +090086 env['PYTHONPATH'] = os.pathsep.join(dirs)
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090087 if gendir.endswith('gen-py-no_utf8strings'):
88 env['THRIFT_TEST_PY_NO_UTF8STRINGS'] = '1'
89 return env
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +090090
91
Nobuaki Sukegawaa3b88a02016-01-06 20:44:17 +090092def runScriptTest(libdir, genbase, genpydir, script):
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +090093 env = setup_pypath(libdir, os.path.join(genbase, genpydir))
94 script_args = [sys.executable, relfile(script)]
95 print('\nTesting script: %s\n----' % (' '.join(script_args)))
96 ret = subprocess.call(script_args, env=env)
97 if ret != 0:
98 print('*** FAILED ***', file=sys.stderr)
99 print('LIBDIR: %s' % libdir, file=sys.stderr)
100 print('PY_GEN: %s' % genpydir, file=sys.stderr)
101 print('SCRIPT: %s' % script, file=sys.stderr)
102 raise Exception("Script subprocess failed, retcode=%d, args: %s" % (ret, ' '.join(script_args)))
Roger Meier76150722014-05-31 22:22:07 +0200103
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900104
Gregg Donovan62ec9292026-01-29 16:51:37 -0500105def pick_unused_port():
106 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
107 sock.bind(('127.0.0.1', 0))
108 port = sock.getsockname()[1]
109 sock.close()
110 return port
111
112
Nobuaki Sukegawaa3b88a02016-01-06 20:44:17 +0900113def runServiceTest(libdir, genbase, genpydir, server_class, proto, port, use_zlib, use_ssl, verbose):
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900114 env = setup_pypath(libdir, os.path.join(genbase, genpydir))
115 # Build command line arguments
116 server_args = [sys.executable, relfile('TestServer.py')]
117 cli_args = [sys.executable, relfile('TestClient.py')]
118 for which in (server_args, cli_args):
119 which.append('--protocol=%s' % proto) # accel, binary, compact or json
120 which.append('--port=%d' % port) # default to 9090
121 if use_zlib:
122 which.append('--zlib')
123 if use_ssl:
124 which.append('--ssl')
125 if verbose == 0:
126 which.append('-q')
127 if verbose == 2:
128 which.append('-v')
129 # server-specific option to select server class
130 server_args.append(server_class)
131 # client-specific cmdline options
132 if server_class in FRAMED:
133 cli_args.append('--transport=framed')
134 else:
135 cli_args.append('--transport=buffered')
136 if server_class == 'THttpServer':
137 cli_args.append('--http=/')
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900138 if verbose > 0:
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900139 print('Testing server %s: %s' % (server_class, ' '.join(server_args)))
Gregg Donovan62ec9292026-01-29 16:51:37 -0500140 popen_kwargs = {'env': env}
141 # Windows uses process groups; POSIX starts a new session so we can killpg().
142 if platform.system() == 'Windows':
143 if hasattr(subprocess, 'CREATE_NEW_PROCESS_GROUP'):
144 popen_kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP
145 else:
146 popen_kwargs['start_new_session'] = True
147 serverproc = subprocess.Popen(server_args, **popen_kwargs)
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900148
149 def ensureServerAlive():
150 if serverproc.poll() is not None:
151 print(('FAIL: Server process (%s) failed with retcode %d')
152 % (' '.join(server_args), serverproc.returncode))
153 raise Exception('Server subprocess %s died, args: %s'
154 % (server_class, ' '.join(server_args)))
155
156 # Wait for the server to start accepting connections on the given port.
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900157 sleep_time = 0.1 # Seconds
158 max_attempts = 100
Nobuaki Sukegawae9b32342016-02-27 03:44:02 +0900159 attempt = 0
160 while True:
161 sock4 = socket.socket()
162 sock6 = socket.socket(socket.AF_INET6)
163 try:
164 if sock4.connect_ex(('127.0.0.1', port)) == 0 \
165 or sock6.connect_ex(('::1', port)) == 0:
166 break
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900167 attempt += 1
168 if attempt >= max_attempts:
169 raise Exception("TestServer not ready on port %d after %.2f seconds"
170 % (port, sleep_time * attempt))
171 ensureServerAlive()
172 time.sleep(sleep_time)
Nobuaki Sukegawae9b32342016-02-27 03:44:02 +0900173 finally:
174 sock4.close()
175 sock6.close()
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900176
177 try:
178 if verbose > 0:
179 print('Testing client: %s' % (' '.join(cli_args)))
180 ret = subprocess.call(cli_args, env=env)
181 if ret != 0:
182 print('*** FAILED ***', file=sys.stderr)
183 print('LIBDIR: %s' % libdir, file=sys.stderr)
184 print('PY_GEN: %s' % genpydir, file=sys.stderr)
185 raise Exception("Client subprocess failed, retcode=%d, args: %s" % (ret, ' '.join(cli_args)))
186 finally:
Gregg Donovan62ec9292026-01-29 16:51:37 -0500187 # check that server didn't die, but still attempt cleanup
188 cleanup_exc = None
189 try:
190 ensureServerAlive()
191 except Exception as exc:
192 cleanup_exc = exc
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900193 extra_sleep = EXTRA_DELAY.get(server_class, 0)
194 if extra_sleep > 0 and verbose > 0:
195 print('Giving %s (proto=%s,zlib=%s,ssl=%s) an extra %d seconds for child'
196 'processes to terminate via alarm'
197 % (server_class, proto, use_zlib, use_ssl, extra_sleep))
198 time.sleep(extra_sleep)
Nobuaki Sukegawa06e8fd42016-02-28 12:50:03 +0900199 sig = signal.SIGKILL if platform.system() != 'Windows' else signal.SIGABRT
Gregg Donovan62ec9292026-01-29 16:51:37 -0500200 try:
201 if platform.system() == 'Windows':
202 os.kill(serverproc.pid, sig)
203 else:
204 # POSIX: kill the whole process group to reap forked children.
205 os.killpg(serverproc.pid, sig)
206 except OSError:
207 pass
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900208 serverproc.wait()
Gregg Donovan62ec9292026-01-29 16:51:37 -0500209 if cleanup_exc:
210 raise cleanup_exc
David Reissbcaa2ad2008-06-10 22:55:26 +0000211
Roger Meier76150722014-05-31 22:22:07 +0200212
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900213class TestCases(object):
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900214 def __init__(self, genbase, libdir, port, gendirs, servers, verbose):
215 self.genbase = genbase
216 self.libdir = libdir
217 self.port = port
218 self.verbose = verbose
219 self.gendirs = gendirs
220 self.servers = servers
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900221
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900222 def default_conf(self):
223 return {
224 'gendir': self.gendirs[0],
225 'server': self.servers[0],
226 'proto': PROTOS[0],
227 'zlib': False,
228 'ssl': False,
229 }
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900230
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900231 def run(self, conf, test_count):
232 with_zlib = conf['zlib']
233 with_ssl = conf['ssl']
234 try_server = conf['server']
235 try_proto = conf['proto']
236 genpydir = conf['gendir']
237 # skip any servers that don't work with the Zlib transport
238 if with_zlib and try_server in SKIP_ZLIB:
239 return False
240 # skip any servers that don't work with SSL
241 if with_ssl and try_server in SKIP_SSL:
242 return False
Carel Combrink9f96b482025-11-11 13:36:06 +0000243 # Skip SSL issues -> See THRIFT-5901
244 if with_ssl:
245 print('Skipping \'with_ssl\' tests')
Carele720e6f2025-11-07 10:48:27 +0200246 return False
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900247 if self.verbose > 0:
248 print('\nTest run #%d: (includes %s) Server=%s, Proto=%s, zlib=%s, SSL=%s'
249 % (test_count, genpydir, try_server, try_proto, with_zlib, with_ssl))
Gregg Donovan62ec9292026-01-29 16:51:37 -0500250 port = self.port if self.port else pick_unused_port()
251 runServiceTest(self.libdir, self.genbase, genpydir, try_server, try_proto, port, with_zlib, with_ssl, self.verbose)
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900252 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 True
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900256
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900257 def test_feature(self, name, values):
258 test_count = 0
259 conf = self.default_conf()
260 for try_server in values:
261 conf[name] = try_server
262 if self.run(conf, test_count):
263 test_count += 1
264 return test_count
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900265
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900266 def run_all_tests(self):
267 test_count = 0
268 for try_server in self.servers:
269 for genpydir in self.gendirs:
270 for try_proto in PROTOS:
271 for with_zlib in (False, True):
272 # skip any servers that don't work with the Zlib transport
273 if with_zlib and try_server in SKIP_ZLIB:
274 continue
275 for with_ssl in (False, True):
276 # skip any servers that don't work with SSL
277 if with_ssl and try_server in SKIP_SSL:
278 continue
Carel Combrink9f96b482025-11-11 13:36:06 +0000279 # Skip SSL issues -> See THRIFT-5901
280 if with_ssl:
281 print('Skipping \'with_ssl\' tests')
Carele720e6f2025-11-07 10:48:27 +0200282 continue
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900283 test_count += 1
284 if self.verbose > 0:
285 print('\nTest run #%d: (includes %s) Server=%s, Proto=%s, zlib=%s, SSL=%s'
286 % (test_count, genpydir, try_server, try_proto, with_zlib, with_ssl))
Gregg Donovan62ec9292026-01-29 16:51:37 -0500287 port = self.port if self.port else pick_unused_port()
288 runServiceTest(self.libdir, self.genbase, genpydir, try_server, try_proto, port, with_zlib, with_ssl, self.verbose)
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900289 if self.verbose > 0:
290 print('OK: Finished (includes %s) %s / %s proto / zlib=%s / SSL=%s. %d combinations tested.'
291 % (genpydir, try_server, try_proto, with_zlib, with_ssl, test_count))
292 return test_count
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900293
294
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900295def main():
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900296 parser = OptionParser()
297 parser.add_option('--all', action="store_true", dest='all')
298 parser.add_option('--genpydirs', type='string', dest='genpydirs',
arkuhn12b01162024-02-23 20:18:55 -0500299 default='default,slots,oldstyle,no_utf8strings,dynamic,dynamicslots,enum,type_hints',
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900300 help='directory extensions for generated code, used as suffixes for \"gen-py-*\" added sys.path for individual tests')
Gregg Donovan62ec9292026-01-29 16:51:37 -0500301 parser.add_option("--port", type="int", dest="port", default=0,
302 help="port number for server to listen on (0 = auto)")
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900303 parser.add_option('-v', '--verbose', action="store_const",
304 dest="verbose", const=2,
305 help="verbose output")
306 parser.add_option('-q', '--quiet', action="store_const",
307 dest="verbose", const=0,
308 help="minimal output")
Nobuaki Sukegawa7af189a2016-02-11 16:21:01 +0900309 parser.add_option('-L', '--libdir', dest="libdir", default=local_libpath(),
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900310 help="directory path that contains Thrift Python library")
311 parser.add_option('--gen-base', dest="gen_base", default=SCRIPT_DIR,
312 help="directory path that contains Thrift Python library")
313 parser.set_defaults(verbose=1)
314 options, args = parser.parse_args()
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900315
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900316 generated_dirs = []
317 for gp_dir in options.genpydirs.split(','):
Carel Combrinkdee782f2025-11-10 13:18:53 +0100318 if gp_dir == 'type_hints' and (sys.version_info < (3,7)):
319 print('Skipping \'type_hints\' test since python 3.7 or later is required')
Carele720e6f2025-11-07 10:48:27 +0200320 continue
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900321 generated_dirs.append('gen-py-%s' % (gp_dir))
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900322
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900323 # commandline permits a single class name to be specified to override SERVERS=[...]
Nobuaki Sukegawa06e8fd42016-02-28 12:50:03 +0900324 servers = default_servers()
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900325 if len(args) == 1:
Nobuaki Sukegawa06e8fd42016-02-28 12:50:03 +0900326 if args[0] in servers:
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900327 servers = args
328 else:
329 print('Unavailable server type "%s", please choose one of: %s' % (args[0], servers))
330 sys.exit(0)
331
332 tests = TestCases(options.gen_base, options.libdir, options.port, generated_dirs, servers, options.verbose)
333
334 # run tests without a client/server first
335 print('----------------')
336 print(' Executing individual test scripts with various generated code directories')
337 print(' Directories to be tested: ' + ', '.join(generated_dirs))
338 print(' Scripts to be tested: ' + ', '.join(SCRIPTS))
339 print('----------------')
340 for genpydir in generated_dirs:
341 for script in SCRIPTS:
342 runScriptTest(options.libdir, options.gen_base, genpydir, script)
343
344 print('----------------')
345 print(' Executing Client/Server tests with various generated code directories')
346 print(' Servers to be tested: ' + ', '.join(servers))
347 print(' Directories to be tested: ' + ', '.join(generated_dirs))
348 print(' Protocols to be tested: ' + ', '.join(PROTOS))
349 print(' Options to be tested: ZLIB(yes/no), SSL(yes/no)')
350 print('----------------')
351
352 if options.all:
353 tests.run_all_tests()
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900354 else:
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900355 tests.test_feature('gendir', generated_dirs)
356 tests.test_feature('server', servers)
357 tests.test_feature('proto', PROTOS)
358 tests.test_feature('zlib', [False, True])
359 tests.test_feature('ssl', [False, True])
Nobuaki Sukegawacacce2f2015-11-08 23:43:55 +0900360
361
362if __name__ == '__main__':
Nobuaki Sukegawa10308cb2016-02-03 01:57:03 +0900363 sys.exit(main())