diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:45:59 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:45:59 +0000 |
commit | 19fcec84d8d7d21e796c7624e521b60d28ee21ed (patch) | |
tree | 42d26aa27d1e3f7c0b8bd3fd14e7d7082f5008dc /src/jaegertracing/thrift/test/py | |
parent | Initial commit. (diff) | |
download | ceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.tar.xz ceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.zip |
Adding upstream version 16.2.11+ds.upstream/16.2.11+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/jaegertracing/thrift/test/py')
20 files changed, 2907 insertions, 0 deletions
diff --git a/src/jaegertracing/thrift/test/py/CMakeLists.txt b/src/jaegertracing/thrift/test/py/CMakeLists.txt new file mode 100644 index 000000000..fbc221738 --- /dev/null +++ b/src/jaegertracing/thrift/test/py/CMakeLists.txt @@ -0,0 +1,34 @@ +# +# 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. +# + +add_test(NAME python_test_generate + COMMAND ${CMAKE_COMMAND} + -DTHRIFTCOMPILER=$<TARGET_FILE:thrift-compiler> + -DMY_PROJECT_DIR=${PROJECT_SOURCE_DIR} + -DMY_CURRENT_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR} + -DMY_CURRENT_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR} + -P ${CMAKE_CURRENT_SOURCE_DIR}/generate.cmake +) + +add_test(NAME python_test + COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/RunClientServer.py --gen-base=${CMAKE_CURRENT_BINARY_DIR} + DEPENDS python_test_generate + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +) + diff --git a/src/jaegertracing/thrift/test/py/FastbinaryTest.py b/src/jaegertracing/thrift/test/py/FastbinaryTest.py new file mode 100755 index 000000000..05c0bb6d1 --- /dev/null +++ b/src/jaegertracing/thrift/test/py/FastbinaryTest.py @@ -0,0 +1,256 @@ +#!/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. +# + +r""" +PYTHONPATH=./gen-py:../../lib/py/build/lib... ./FastbinaryTest.py +""" + +# TODO(dreiss): Test error cases. Check for memory leaks. + +from __future__ import print_function + +import math +import os +import sys +import timeit + +from copy import deepcopy +from pprint import pprint + +from thrift.transport import TTransport +from thrift.protocol.TBinaryProtocol import TBinaryProtocol, TBinaryProtocolAccelerated +from thrift.protocol.TCompactProtocol import TCompactProtocol, TCompactProtocolAccelerated + +from DebugProtoTest import Srv +from DebugProtoTest.ttypes import Backwards, Bonk, Empty, HolyMoley, OneOfEach, RandomStuff, Wrapper + + +class TDevNullTransport(TTransport.TTransportBase): + def __init__(self): + pass + + def isOpen(self): + return True + + +ooe1 = OneOfEach() +ooe1.im_true = True +ooe1.im_false = False +ooe1.a_bite = 0xd6 +ooe1.integer16 = 27000 +ooe1.integer32 = 1 << 24 +ooe1.integer64 = 6000 * 1000 * 1000 +ooe1.double_precision = math.pi +ooe1.some_characters = "Debug THIS!" +ooe1.zomg_unicode = u"\xd7\n\a\t" + +ooe2 = OneOfEach() +ooe2.integer16 = 16 +ooe2.integer32 = 32 +ooe2.integer64 = 64 +ooe2.double_precision = (math.sqrt(5) + 1) / 2 +ooe2.some_characters = ":R (me going \"rrrr\")" +ooe2.zomg_unicode = u"\xd3\x80\xe2\x85\xae\xce\x9d\x20"\ + u"\xd0\x9d\xce\xbf\xe2\x85\xbf\xd0\xbe"\ + u"\xc9\xa1\xd0\xb3\xd0\xb0\xcf\x81\xe2\x84\x8e"\ + u"\x20\xce\x91\x74\x74\xce\xb1\xe2\x85\xbd\xce\xba"\ + u"\xc7\x83\xe2\x80\xbc" + +if sys.version_info[0] == 2 and os.environ.get('THRIFT_TEST_PY_NO_UTF8STRINGS'): + ooe1.zomg_unicode = ooe1.zomg_unicode.encode('utf8') + ooe2.zomg_unicode = ooe2.zomg_unicode.encode('utf8') + +hm = HolyMoley(**{"big": [], "contain": set(), "bonks": {}}) +hm.big.append(ooe1) +hm.big.append(ooe2) +hm.big[0].a_bite = 0x22 +hm.big[1].a_bite = 0x22 + +hm.contain.add(("and a one", "and a two")) +hm.contain.add(("then a one, two", "three!", "FOUR!")) +if sys.version_info[0] == 2 and os.environ.get('THRIFT_TEST_PY_NO_UTF8STRINGS'): + hm.contain.add((u"\xd7\n\a\t".encode('utf8'),)) +else: + hm.contain.add((u"\xd7\n\a\t",)) +hm.contain.add(()) + +hm.bonks["nothing"] = [] +hm.bonks["something"] = [ + Bonk(**{"type": 1, "message": "Wait."}), + Bonk(**{"type": 2, "message": "What?"}), +] +hm.bonks["poe"] = [ + Bonk(**{"type": 3, "message": "quoth"}), + Bonk(**{"type": 4, "message": "the raven"}), + Bonk(**{"type": 5, "message": "nevermore"}), +] + +rs = RandomStuff() +rs.a = 1 +rs.b = 2 +rs.c = 3 +rs.myintlist = list(range(20)) +rs.maps = {1: Wrapper(**{"foo": Empty()}), 2: Wrapper(**{"foo": Empty()})} +rs.bigint = 124523452435 +rs.triple = 3.14 + +# make sure this splits two buffers in a buffered protocol +rshuge = RandomStuff() +rshuge.myintlist = list(range(10000)) + +my_zero = Srv.Janky_result(**{"success": 5}) + + +class Test(object): + def __init__(self, fast, slow): + self._fast = fast + self._slow = slow + + def _check_write(self, o): + trans_fast = TTransport.TMemoryBuffer() + trans_slow = TTransport.TMemoryBuffer() + prot_fast = self._fast(trans_fast, fallback=False) + prot_slow = self._slow(trans_slow) + + o.write(prot_fast) + o.write(prot_slow) + ORIG = trans_slow.getvalue() + MINE = trans_fast.getvalue() + if ORIG != MINE: + print("actual : %s\nexpected: %s" % (repr(MINE), repr(ORIG))) + raise Exception('write value mismatch') + + def _check_read(self, o): + prot = self._slow(TTransport.TMemoryBuffer()) + o.write(prot) + + slow_version_binary = prot.trans.getvalue() + + prot = self._fast( + TTransport.TMemoryBuffer(slow_version_binary), fallback=False) + c = o.__class__() + c.read(prot) + if c != o: + print("actual : ") + pprint(repr(c)) + print("expected: ") + pprint(repr(o)) + raise Exception('read value mismatch') + + prot = self._fast( + TTransport.TBufferedTransport( + TTransport.TMemoryBuffer(slow_version_binary)), fallback=False) + c = o.__class__() + c.read(prot) + if c != o: + print("actual : ") + pprint(repr(c)) + print("expected: ") + pprint(repr(o)) + raise Exception('read value mismatch') + + def do_test(self): + self._check_write(HolyMoley()) + self._check_read(HolyMoley()) + + self._check_write(hm) + no_set = deepcopy(hm) + no_set.contain = set() + self._check_read(no_set) + self._check_read(hm) + + self._check_write(rs) + self._check_read(rs) + + self._check_write(rshuge) + self._check_read(rshuge) + + self._check_write(my_zero) + self._check_read(my_zero) + + self._check_read(Backwards(**{"first_tag2": 4, "second_tag1": 2})) + + # One case where the serialized form changes, but only superficially. + o = Backwards(**{"first_tag2": 4, "second_tag1": 2}) + trans_fast = TTransport.TMemoryBuffer() + trans_slow = TTransport.TMemoryBuffer() + prot_fast = self._fast(trans_fast, fallback=False) + prot_slow = self._slow(trans_slow) + + o.write(prot_fast) + o.write(prot_slow) + ORIG = trans_slow.getvalue() + MINE = trans_fast.getvalue() + assert id(ORIG) != id(MINE) + + prot = self._fast(TTransport.TMemoryBuffer(), fallback=False) + o.write(prot) + prot = self._slow( + TTransport.TMemoryBuffer(prot.trans.getvalue())) + c = o.__class__() + c.read(prot) + if c != o: + print("copy: ") + pprint(repr(c)) + print("orig: ") + pprint(repr(o)) + + +def do_test(fast, slow): + Test(fast, slow).do_test() + + +def do_benchmark(protocol, iters=5000, skip_slow=False): + setup = """ +from __main__ import hm, rs, TDevNullTransport +from thrift.protocol.{0} import {0}{1} +trans = TDevNullTransport() +prot = {0}{1}(trans{2}) +""" + + setup_fast = setup.format(protocol, 'Accelerated', ', fallback=False') + if not skip_slow: + setup_slow = setup.format(protocol, '', '') + + print("Starting Benchmarks") + + if not skip_slow: + print("HolyMoley Standard = %f" % + timeit.Timer('hm.write(prot)', setup_slow).timeit(number=iters)) + + print("HolyMoley Acceler. = %f" % + timeit.Timer('hm.write(prot)', setup_fast).timeit(number=iters)) + + if not skip_slow: + print("FastStruct Standard = %f" % + timeit.Timer('rs.write(prot)', setup_slow).timeit(number=iters)) + + print("FastStruct Acceler. = %f" % + timeit.Timer('rs.write(prot)', setup_fast).timeit(number=iters)) + + +if __name__ == '__main__': + print('Testing TBinaryAccelerated') + do_test(TBinaryProtocolAccelerated, TBinaryProtocol) + do_benchmark('TBinaryProtocol') + print('Testing TCompactAccelerated') + do_test(TCompactProtocolAccelerated, TCompactProtocol) + do_benchmark('TCompactProtocol') diff --git a/src/jaegertracing/thrift/test/py/Makefile.am b/src/jaegertracing/thrift/test/py/Makefile.am new file mode 100644 index 000000000..9433e5907 --- /dev/null +++ b/src/jaegertracing/thrift/test/py/Makefile.am @@ -0,0 +1,105 @@ +# +# 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. +# +AUTOMAKE_OPTIONS = serial-tests + +py_unit_tests = RunClientServer.py + +thrift_gen = \ + gen-py/ThriftTest/__init__.py \ + gen-py/DebugProtoTest/__init__.py \ + gen-py/DoubleConstantsTest/__init__.py \ + gen-py/Recursive/__init__.py \ + gen-py-default/ThriftTest/__init__.py \ + gen-py-default/DebugProtoTest/__init__.py \ + gen-py-default/DoubleConstantsTest/__init__.py \ + gen-py-default/Recursive/__init__.py \ + gen-py-slots/ThriftTest/__init__.py \ + gen-py-slots/DebugProtoTest/__init__.py \ + gen-py-slots/DoubleConstantsTest/__init__.py \ + gen-py-slots/Recursive/__init__.py \ + gen-py-oldstyle/ThriftTest/__init__.py \ + gen-py-oldstyle/DebugProtoTest/__init__.py \ + gen-py-oldstyle/DoubleConstantsTest/__init__.py \ + gen-py-oldstyle/Recursive/__init__.py \ + gen-py-no_utf8strings/ThriftTest/__init__.py \ + gen-py-no_utf8strings/DebugProtoTest/__init__.py \ + gen-py-no_utf8strings/DoubleConstantsTest/__init__.py \ + gen-py-no_utf8strings/Recursive/__init__.py \ + gen-py-dynamic/ThriftTest/__init__.py \ + gen-py-dynamic/DebugProtoTest/__init__.py \ + gen-py-dynamic/DoubleConstantsTest/__init__.py \ + gen-py-dynamic/Recursive/__init__.py \ + gen-py-dynamicslots/ThriftTest/__init__.py \ + gen-py-dynamicslots/DebugProtoTest/__init__.py \ + gen-py-dynamicslots/DoubleConstantsTest/__init__.py \ + gen-py-dynamicslots/Recursive/__init__.py + + +precross: $(thrift_gen) +BUILT_SOURCES = $(thrift_gen) + +helper_scripts= \ + TestClient.py \ + TestServer.py + +check_SCRIPTS= \ + $(thrift_gen) \ + $(py_unit_tests) \ + $(helper_scripts) + +TESTS= $(py_unit_tests) + + +gen-py/%/__init__.py: ../%.thrift $(THRIFT) + $(THRIFT) --gen py $< + +gen-py-default/%/__init__.py: ../%.thrift $(THRIFT) + test -d gen-py-default || $(MKDIR_P) gen-py-default + $(THRIFT) --gen py -out gen-py-default $< + +gen-py-slots/%/__init__.py: ../%.thrift $(THRIFT) + test -d gen-py-slots || $(MKDIR_P) gen-py-slots + $(THRIFT) --gen py:slots -out gen-py-slots $< + +gen-py-oldstyle/%/__init__.py: ../%.thrift $(THRIFT) + test -d gen-py-oldstyle || $(MKDIR_P) gen-py-oldstyle + $(THRIFT) --gen py:old_style -out gen-py-oldstyle $< + +gen-py-no_utf8strings/%/__init__.py: ../%.thrift $(THRIFT) + test -d gen-py-no_utf8strings || $(MKDIR_P) gen-py-no_utf8strings + $(THRIFT) --gen py:no_utf8strings -out gen-py-no_utf8strings $< + +gen-py-dynamic/%/__init__.py: ../%.thrift $(THRIFT) + test -d gen-py-dynamic || $(MKDIR_P) gen-py-dynamic + $(THRIFT) --gen py:dynamic -out gen-py-dynamic $< + +gen-py-dynamicslots/%/__init__.py: ../%.thrift $(THRIFT) + test -d gen-py-dynamicslots || $(MKDIR_P) gen-py-dynamicslots + $(THRIFT) --gen py:dynamic,slots -out gen-py-dynamicslots $< + +clean-local: + $(RM) -r build + find . -type f \( -iname "*.pyc" \) | xargs rm -f + find . -type d \( -iname "__pycache__" -or -iname "_trial_temp" \) | xargs rm -rf + $(RM) -r gen-py*/ + +dist-hook: + find $(distdir) -type f \( -iname "*.pyc" \) | xargs rm -f + find $(distdir) -type d \( -iname "__pycache__" -or -iname "_trial_temp" \) | xargs rm -rf + $(RM) -r $(distdir)/gen-py*/ diff --git a/src/jaegertracing/thrift/test/py/RunClientServer.py b/src/jaegertracing/thrift/test/py/RunClientServer.py new file mode 100755 index 000000000..56a408e60 --- /dev/null +++ b/src/jaegertracing/thrift/test/py/RunClientServer.py @@ -0,0 +1,323 @@ +#!/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', + 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()) diff --git a/src/jaegertracing/thrift/test/py/SerializationTest.py b/src/jaegertracing/thrift/test/py/SerializationTest.py new file mode 100755 index 000000000..ef7983568 --- /dev/null +++ b/src/jaegertracing/thrift/test/py/SerializationTest.py @@ -0,0 +1,457 @@ +#!/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 ThriftTest.ttypes import ( + Bonk, + Bools, + LargeDeltas, + ListBonks, + NestedListsBonk, + NestedListsI32x2, + NestedListsI32x3, + NestedMixedx2, + Numberz, + VersioningTestV1, + VersioningTestV2, + Xtruct, + Xtruct2, +) + +from Recursive.ttypes import RecTree +from Recursive.ttypes import RecList +from Recursive.ttypes import CoRec +from Recursive.ttypes import CoRec2 +from Recursive.ttypes import VectorTest +from DebugProtoTest.ttypes import CompactProtoTestStruct, Empty +from thrift.transport import TTransport +from thrift.protocol import TBinaryProtocol, TCompactProtocol, TJSONProtocol +from thrift.TSerialization import serialize, deserialize +import sys +import unittest + + +class AbstractTest(unittest.TestCase): + + def setUp(self): + self.v1obj = VersioningTestV1( + begin_in_both=12345, + old_string='aaa', + end_in_both=54321, + ) + + self.v2obj = VersioningTestV2( + begin_in_both=12345, + newint=1, + newbyte=2, + newshort=3, + newlong=4, + newdouble=5.0, + newstruct=Bonk(message="Hello!", type=123), + newlist=[7, 8, 9], + newset=set([42, 1, 8]), + newmap={1: 2, 2: 3}, + newstring="Hola!", + end_in_both=54321, + ) + + self.bools = Bools(im_true=True, im_false=False) + self.bools_flipped = Bools(im_true=False, im_false=True) + + self.large_deltas = LargeDeltas( + b1=self.bools, + b10=self.bools_flipped, + b100=self.bools, + check_true=True, + b1000=self.bools_flipped, + check_false=False, + vertwo2000=VersioningTestV2(newstruct=Bonk(message='World!', type=314)), + a_set2500=set(['lazy', 'brown', 'cow']), + vertwo3000=VersioningTestV2(newset=set([2, 3, 5, 7, 11])), + big_numbers=[2 ** 8, 2 ** 16, 2 ** 31 - 1, -(2 ** 31 - 1)] + ) + + self.compact_struct = CompactProtoTestStruct( + a_byte=127, + a_i16=32000, + a_i32=1000000000, + a_i64=0xffffffffff, + a_double=5.6789, + a_string="my string", + true_field=True, + false_field=False, + empty_struct_field=Empty(), + byte_list=[-127, -1, 0, 1, 127], + i16_list=[-1, 0, 1, 0x7fff], + i32_list=[-1, 0, 0xff, 0xffff, 0xffffff, 0x7fffffff], + i64_list=[-1, 0, 0xff, 0xffff, 0xffffff, 0xffffffff, 0xffffffffff, 0xffffffffffff, 0xffffffffffffff, 0x7fffffffffffffff], + double_list=[0.1, 0.2, 0.3], + string_list=["first", "second", "third"], + boolean_list=[True, True, True, False, False, False], + struct_list=[Empty(), Empty()], + byte_set=set([-127, -1, 0, 1, 127]), + i16_set=set([-1, 0, 1, 0x7fff]), + i32_set=set([1, 2, 3]), + i64_set=set([-1, 0, 0xff, 0xffff, 0xffffff, 0xffffffff, 0xffffffffff, 0xffffffffffff, 0xffffffffffffff, 0x7fffffffffffffff]), + double_set=set([0.1, 0.2, 0.3]), + string_set=set(["first", "second", "third"]), + boolean_set=set([True, False]), + # struct_set=set([Empty()]), # unhashable instance + byte_byte_map={1: 2}, + i16_byte_map={1: 1, -1: 1, 0x7fff: 1}, + i32_byte_map={1: 1, -1: 1, 0x7fffffff: 1}, + i64_byte_map={0: 1, 1: 1, -1: 1, 0x7fffffffffffffff: 1}, + double_byte_map={-1.1: 1, 1.1: 1}, + string_byte_map={"first": 1, "second": 2, "third": 3, "": 0}, + boolean_byte_map={True: 1, False: 0}, + byte_i16_map={1: 1, 2: -1, 3: 0x7fff}, + byte_i32_map={1: 1, 2: -1, 3: 0x7fffffff}, + byte_i64_map={1: 1, 2: -1, 3: 0x7fffffffffffffff}, + byte_double_map={1: 0.1, 2: -0.1, 3: 1000000.1}, + byte_string_map={1: "", 2: "blah", 3: "loooooooooooooong string"}, + byte_boolean_map={1: True, 2: False}, + # list_byte_map # unhashable + # set_byte_map={set([1, 2, 3]) : 1, set([0, 1]) : 2, set([]) : 0}, # unhashable + # map_byte_map # unhashable + byte_map_map={0: {}, 1: {1: 1}, 2: {1: 1, 2: 2}}, + byte_set_map={0: set([]), 1: set([1]), 2: set([1, 2])}, + byte_list_map={0: [], 1: [1], 2: [1, 2]}, + ) + + self.nested_lists_i32x2 = NestedListsI32x2( + [ + [1, 1, 2], + [2, 7, 9], + [3, 5, 8] + ] + ) + + self.nested_lists_i32x3 = NestedListsI32x3( + [ + [ + [2, 7, 9], + [3, 5, 8] + ], + [ + [1, 1, 2], + [1, 4, 9] + ] + ] + ) + + self.nested_mixedx2 = NestedMixedx2(int_set_list=[ + set([1, 2, 3]), + set([1, 4, 9]), + set([1, 2, 3, 5, 8, 13, 21]), + set([-1, 0, 1]) + ], + # note, the sets below are sets of chars, since the strings are iterated + map_int_strset={10: set('abc'), 20: set('def'), 30: set('GHI')}, + map_int_strset_list=[ + {10: set('abc'), 20: set('def'), 30: set('GHI')}, + {100: set('lmn'), 200: set('opq'), 300: set('RST')}, + {1000: set('uvw'), 2000: set('wxy'), 3000: set('XYZ')}] + ) + + self.nested_lists_bonk = NestedListsBonk( + [ + [ + [ + Bonk(message='inner A first', type=1), + Bonk(message='inner A second', type=1) + ], + [ + Bonk(message='inner B first', type=2), + Bonk(message='inner B second', type=2) + ] + ] + ] + ) + + self.list_bonks = ListBonks( + [ + Bonk(message='inner A', type=1), + Bonk(message='inner B', type=2), + Bonk(message='inner C', type=0) + ] + ) + + def _serialize(self, obj): + trans = TTransport.TMemoryBuffer() + prot = self.protocol_factory.getProtocol(trans) + obj.write(prot) + return trans.getvalue() + + def _deserialize(self, objtype, data): + prot = self.protocol_factory.getProtocol(TTransport.TMemoryBuffer(data)) + ret = objtype() + ret.read(prot) + return ret + + def testForwards(self): + obj = self._deserialize(VersioningTestV2, self._serialize(self.v1obj)) + self.assertEquals(obj.begin_in_both, self.v1obj.begin_in_both) + self.assertEquals(obj.end_in_both, self.v1obj.end_in_both) + + def testBackwards(self): + obj = self._deserialize(VersioningTestV1, self._serialize(self.v2obj)) + self.assertEquals(obj.begin_in_both, self.v2obj.begin_in_both) + self.assertEquals(obj.end_in_both, self.v2obj.end_in_both) + + def testSerializeV1(self): + obj = self._deserialize(VersioningTestV1, self._serialize(self.v1obj)) + self.assertEquals(obj, self.v1obj) + + def testSerializeV2(self): + obj = self._deserialize(VersioningTestV2, self._serialize(self.v2obj)) + self.assertEquals(obj, self.v2obj) + + def testBools(self): + self.assertNotEquals(self.bools, self.bools_flipped) + self.assertNotEquals(self.bools, self.v1obj) + obj = self._deserialize(Bools, self._serialize(self.bools)) + self.assertEquals(obj, self.bools) + obj = self._deserialize(Bools, self._serialize(self.bools_flipped)) + self.assertEquals(obj, self.bools_flipped) + rep = repr(self.bools) + self.assertTrue(len(rep) > 0) + + def testLargeDeltas(self): + # test large field deltas (meaningful in CompactProto only) + obj = self._deserialize(LargeDeltas, self._serialize(self.large_deltas)) + self.assertEquals(obj, self.large_deltas) + rep = repr(self.large_deltas) + self.assertTrue(len(rep) > 0) + + def testNestedListsI32x2(self): + obj = self._deserialize(NestedListsI32x2, self._serialize(self.nested_lists_i32x2)) + self.assertEquals(obj, self.nested_lists_i32x2) + rep = repr(self.nested_lists_i32x2) + self.assertTrue(len(rep) > 0) + + def testNestedListsI32x3(self): + obj = self._deserialize(NestedListsI32x3, self._serialize(self.nested_lists_i32x3)) + self.assertEquals(obj, self.nested_lists_i32x3) + rep = repr(self.nested_lists_i32x3) + self.assertTrue(len(rep) > 0) + + def testNestedMixedx2(self): + obj = self._deserialize(NestedMixedx2, self._serialize(self.nested_mixedx2)) + self.assertEquals(obj, self.nested_mixedx2) + rep = repr(self.nested_mixedx2) + self.assertTrue(len(rep) > 0) + + def testNestedListsBonk(self): + obj = self._deserialize(NestedListsBonk, self._serialize(self.nested_lists_bonk)) + self.assertEquals(obj, self.nested_lists_bonk) + rep = repr(self.nested_lists_bonk) + self.assertTrue(len(rep) > 0) + + def testListBonks(self): + obj = self._deserialize(ListBonks, self._serialize(self.list_bonks)) + self.assertEquals(obj, self.list_bonks) + rep = repr(self.list_bonks) + self.assertTrue(len(rep) > 0) + + def testCompactStruct(self): + # test large field deltas (meaningful in CompactProto only) + obj = self._deserialize(CompactProtoTestStruct, self._serialize(self.compact_struct)) + self.assertEquals(obj, self.compact_struct) + rep = repr(self.compact_struct) + self.assertTrue(len(rep) > 0) + + def testIntegerLimits(self): + if (sys.version_info[0] == 2 and sys.version_info[1] <= 6): + print('Skipping testIntegerLimits for Python 2.6') + return + bad_values = [CompactProtoTestStruct(a_byte=128), CompactProtoTestStruct(a_byte=-129), + CompactProtoTestStruct(a_i16=32768), CompactProtoTestStruct(a_i16=-32769), + CompactProtoTestStruct(a_i32=2147483648), CompactProtoTestStruct(a_i32=-2147483649), + CompactProtoTestStruct(a_i64=9223372036854775808), CompactProtoTestStruct(a_i64=-9223372036854775809) + ] + + for value in bad_values: + self.assertRaises(Exception, self._serialize, value) + + def testRecTree(self): + """Ensure recursive tree node can be created.""" + children = [] + for idx in range(1, 5): + node = RecTree(item=idx, children=None) + children.append(node) + + parent = RecTree(item=0, children=children) + serde_parent = self._deserialize(RecTree, self._serialize(parent)) + self.assertEquals(0, serde_parent.item) + self.assertEquals(4, len(serde_parent.children)) + for child in serde_parent.children: + # Cannot use assertIsInstance in python 2.6? + self.assertTrue(isinstance(child, RecTree)) + + def _buildLinkedList(self): + head = cur = RecList(item=0) + for idx in range(1, 5): + node = RecList(item=idx) + cur.nextitem = node + cur = node + return head + + def _collapseLinkedList(self, head): + out_list = [] + cur = head + while cur is not None: + out_list.append(cur.item) + cur = cur.nextitem + return out_list + + def testRecList(self): + """Ensure recursive linked list can be created.""" + rec_list = self._buildLinkedList() + serde_list = self._deserialize(RecList, self._serialize(rec_list)) + out_list = self._collapseLinkedList(serde_list) + self.assertEquals([0, 1, 2, 3, 4], out_list) + + def testCoRec(self): + """Ensure co-recursive structures can be created.""" + item1 = CoRec() + item2 = CoRec2() + + item1.other = item2 + item2.other = item1 + + # NOTE [econner724,2017-06-21]: These objects cannot be serialized as serialization + # results in an infinite loop. fbthrift also suffers from this + # problem. + + def testRecVector(self): + """Ensure a list of recursive nodes can be created.""" + mylist = [self._buildLinkedList(), self._buildLinkedList()] + myvec = VectorTest(lister=mylist) + + serde_vec = self._deserialize(VectorTest, self._serialize(myvec)) + golden_list = [0, 1, 2, 3, 4] + for cur_list in serde_vec.lister: + out_list = self._collapseLinkedList(cur_list) + self.assertEqual(golden_list, out_list) + + +class NormalBinaryTest(AbstractTest): + protocol_factory = TBinaryProtocol.TBinaryProtocolFactory() + + +class AcceleratedBinaryTest(AbstractTest): + protocol_factory = TBinaryProtocol.TBinaryProtocolAcceleratedFactory(fallback=False) + + +class CompactProtocolTest(AbstractTest): + protocol_factory = TCompactProtocol.TCompactProtocolFactory() + + +class AcceleratedCompactTest(AbstractTest): + protocol_factory = TCompactProtocol.TCompactProtocolAcceleratedFactory(fallback=False) + + +class JSONProtocolTest(AbstractTest): + protocol_factory = TJSONProtocol.TJSONProtocolFactory() + + +class AcceleratedFramedTest(unittest.TestCase): + def testSplit(self): + """Test FramedTransport and BinaryProtocolAccelerated + + Tests that TBinaryProtocolAccelerated and TFramedTransport + play nicely together when a read spans a frame""" + + protocol_factory = TBinaryProtocol.TBinaryProtocolAcceleratedFactory() + bigstring = "".join(chr(byte) for byte in range(ord("a"), ord("z") + 1)) + + databuf = TTransport.TMemoryBuffer() + prot = protocol_factory.getProtocol(databuf) + prot.writeI32(42) + prot.writeString(bigstring) + prot.writeI16(24) + data = databuf.getvalue() + cutpoint = len(data) // 2 + parts = [data[:cutpoint], data[cutpoint:]] + + framed_buffer = TTransport.TMemoryBuffer() + framed_writer = TTransport.TFramedTransport(framed_buffer) + for part in parts: + framed_writer.write(part) + framed_writer.flush() + self.assertEquals(len(framed_buffer.getvalue()), len(data) + 8) + + # Recreate framed_buffer so we can read from it. + framed_buffer = TTransport.TMemoryBuffer(framed_buffer.getvalue()) + framed_reader = TTransport.TFramedTransport(framed_buffer) + prot = protocol_factory.getProtocol(framed_reader) + self.assertEqual(prot.readI32(), 42) + self.assertEqual(prot.readString(), bigstring) + self.assertEqual(prot.readI16(), 24) + + +class SerializersTest(unittest.TestCase): + + def testSerializeThenDeserialize(self): + obj = Xtruct2(i32_thing=1, + struct_thing=Xtruct(string_thing="foo")) + + s1 = serialize(obj) + for i in range(10): + self.assertEquals(s1, serialize(obj)) + objcopy = Xtruct2() + deserialize(objcopy, serialize(obj)) + self.assertEquals(obj, objcopy) + + obj = Xtruct(string_thing="bar") + objcopy = Xtruct() + deserialize(objcopy, serialize(obj)) + self.assertEquals(obj, objcopy) + + # test booleans + obj = Bools(im_true=True, im_false=False) + objcopy = Bools() + deserialize(objcopy, serialize(obj)) + self.assertEquals(obj, objcopy) + + # test enums + for num, name in Numberz._VALUES_TO_NAMES.items(): + obj = Bonk(message='enum Numberz value %d is string %s' % (num, name), type=num) + objcopy = Bonk() + deserialize(objcopy, serialize(obj)) + self.assertEquals(obj, objcopy) + + +def suite(): + suite = unittest.TestSuite() + loader = unittest.TestLoader() + + suite.addTest(loader.loadTestsFromTestCase(NormalBinaryTest)) + suite.addTest(loader.loadTestsFromTestCase(AcceleratedBinaryTest)) + suite.addTest(loader.loadTestsFromTestCase(AcceleratedCompactTest)) + suite.addTest(loader.loadTestsFromTestCase(CompactProtocolTest)) + suite.addTest(loader.loadTestsFromTestCase(JSONProtocolTest)) + suite.addTest(loader.loadTestsFromTestCase(AcceleratedFramedTest)) + suite.addTest(loader.loadTestsFromTestCase(SerializersTest)) + return suite + + +if __name__ == "__main__": + unittest.main(defaultTest="suite", testRunner=unittest.TextTestRunner(verbosity=2)) diff --git a/src/jaegertracing/thrift/test/py/TSimpleJSONProtocolTest.py b/src/jaegertracing/thrift/test/py/TSimpleJSONProtocolTest.py new file mode 100644 index 000000000..72987602b --- /dev/null +++ b/src/jaegertracing/thrift/test/py/TSimpleJSONProtocolTest.py @@ -0,0 +1,108 @@ +#!/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 ThriftTest.ttypes import Bonk, VersioningTestV1, VersioningTestV2 +from thrift.protocol import TJSONProtocol +from thrift.transport import TTransport + +import json +import unittest + + +class SimpleJSONProtocolTest(unittest.TestCase): + protocol_factory = TJSONProtocol.TSimpleJSONProtocolFactory() + + def _assertDictEqual(self, a, b, msg=None): + if hasattr(self, 'assertDictEqual'): + # assertDictEqual only in Python 2.7. Depends on your machine. + self.assertDictEqual(a, b, msg) + return + + # Substitute implementation not as good as unittest library's + self.assertEquals(len(a), len(b), msg) + for k, v in a.iteritems(): + self.assertTrue(k in b, msg) + self.assertEquals(b.get(k), v, msg) + + def _serialize(self, obj): + trans = TTransport.TMemoryBuffer() + prot = self.protocol_factory.getProtocol(trans) + obj.write(prot) + return trans.getvalue() + + def _deserialize(self, objtype, data): + prot = self.protocol_factory.getProtocol(TTransport.TMemoryBuffer(data)) + ret = objtype() + ret.read(prot) + return ret + + def testWriteOnly(self): + self.assertRaises(NotImplementedError, + self._deserialize, VersioningTestV1, b'{}') + + def testSimpleMessage(self): + v1obj = VersioningTestV1( + begin_in_both=12345, + old_string='aaa', + end_in_both=54321) + expected = dict(begin_in_both=v1obj.begin_in_both, + old_string=v1obj.old_string, + end_in_both=v1obj.end_in_both) + actual = json.loads(self._serialize(v1obj).decode('ascii')) + + self._assertDictEqual(expected, actual) + + def testComplicated(self): + v2obj = VersioningTestV2( + begin_in_both=12345, + newint=1, + newbyte=2, + newshort=3, + newlong=4, + newdouble=5.0, + newstruct=Bonk(message="Hello!", type=123), + newlist=[7, 8, 9], + newset=set([42, 1, 8]), + newmap={1: 2, 2: 3}, + newstring="Hola!", + end_in_both=54321) + expected = dict(begin_in_both=v2obj.begin_in_both, + newint=v2obj.newint, + newbyte=v2obj.newbyte, + newshort=v2obj.newshort, + newlong=v2obj.newlong, + newdouble=v2obj.newdouble, + newstruct=dict(message=v2obj.newstruct.message, + type=v2obj.newstruct.type), + newlist=v2obj.newlist, + newset=list(v2obj.newset), + newmap=v2obj.newmap, + newstring=v2obj.newstring, + end_in_both=v2obj.end_in_both) + + # Need to load/dump because map keys get escaped. + expected = json.loads(json.dumps(expected)) + actual = json.loads(self._serialize(v2obj).decode('ascii')) + self._assertDictEqual(expected, actual) + + +if __name__ == '__main__': + unittest.main() diff --git a/src/jaegertracing/thrift/test/py/TestClient.py b/src/jaegertracing/thrift/test/py/TestClient.py new file mode 100755 index 000000000..e7a9a1a0e --- /dev/null +++ b/src/jaegertracing/thrift/test/py/TestClient.py @@ -0,0 +1,500 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# 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. +# + +import os +import sys +import time +import unittest + +from optparse import OptionParser +from util import local_libpath +sys.path.insert(0, local_libpath()) +from thrift.protocol import TProtocol, TProtocolDecorator + +SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__)) + + +class AbstractTest(unittest.TestCase): + def setUp(self): + if options.trans == 'http': + uri = '{0}://{1}:{2}{3}'.format(('https' if options.ssl else 'http'), + options.host, + options.port, + (options.http_path if options.http_path else '/')) + if options.ssl: + __cafile = os.path.join(os.path.dirname(SCRIPT_DIR), "keys", "CA.pem") + __certfile = os.path.join(os.path.dirname(SCRIPT_DIR), "keys", "client.crt") + __keyfile = os.path.join(os.path.dirname(SCRIPT_DIR), "keys", "client.key") + self.transport = THttpClient.THttpClient(uri, cafile=__cafile, cert_file=__certfile, key_file=__keyfile) + else: + self.transport = THttpClient.THttpClient(uri) + else: + if options.ssl: + from thrift.transport import TSSLSocket + socket = TSSLSocket.TSSLSocket(options.host, options.port, validate=False) + else: + socket = TSocket.TSocket(options.host, options.port) + # frame or buffer depending upon args + self.transport = TTransport.TBufferedTransport(socket) + if options.trans == 'framed': + self.transport = TTransport.TFramedTransport(socket) + elif options.trans == 'buffered': + self.transport = TTransport.TBufferedTransport(socket) + elif options.trans == '': + raise AssertionError('Unknown --transport option: %s' % options.trans) + if options.zlib: + self.transport = TZlibTransport.TZlibTransport(self.transport, 9) + self.transport.open() + protocol = self.get_protocol(self.transport) + self.client = ThriftTest.Client(protocol) + # for multiplexed services: + protocol2 = self.get_protocol2(self.transport) + self.client2 = SecondService.Client(protocol2) if protocol2 is not None else None + + def tearDown(self): + self.transport.close() + + def testVoid(self): + print('testVoid') + self.client.testVoid() + + def testString(self): + print('testString') + self.assertEqual(self.client.testString('Python' * 20), 'Python' * 20) + self.assertEqual(self.client.testString(''), '') + s1 = u'\b\t\n/\\\\\r{}:パイソン"' + s2 = u"""Afrikaans, Alemannisch, Aragonés, العربية, مصرى, + Asturianu, Aymar aru, Azərbaycan, Башҡорт, Boarisch, Žemaitėška, + Беларуская, Беларуская (тарашкевіца), Български, Bamanankan, + বাংলা, Brezhoneg, Bosanski, Català, Mìng-dĕ̤ng-ngṳ̄, Нохчийн, + Cebuano, ᏣᎳᎩ, Česky, Словѣ́ньскъ / ⰔⰎⰑⰂⰡⰐⰠⰔⰍⰟ, Чӑвашла, Cymraeg, + Dansk, Zazaki, ދިވެހިބަސް, Ελληνικά, Emiliàn e rumagnòl, English, + Esperanto, Español, Eesti, Euskara, فارسی, Suomi, Võro, Føroyskt, + Français, Arpetan, Furlan, Frysk, Gaeilge, 贛語, Gàidhlig, Galego, + Avañe'ẽ, ગુજરાતી, Gaelg, עברית, हिन्दी, Fiji Hindi, Hrvatski, + Kreyòl ayisyen, Magyar, Հայերեն, Interlingua, Bahasa Indonesia, + Ilokano, Ido, Íslenska, Italiano, 日本語, Lojban, Basa Jawa, + ქართული, Kongo, Kalaallisut, ಕನ್ನಡ, 한국어, Къарачай-Малкъар, + Ripoarisch, Kurdî, Коми, Kernewek, Кыргызча, Latina, Ladino, + Lëtzebuergesch, Limburgs, Lingála, ລາວ, Lietuvių, Latviešu, Basa + Banyumasan, Malagasy, Македонски, മലയാളം, मराठी, مازِرونی, Bahasa + Melayu, Nnapulitano, Nedersaksisch, नेपाल भाषा, Nederlands, + Norsk (nynorsk), Norsk (bokmål), Nouormand, Diné bizaad, + Occitan, Иронау, Papiamentu, Deitsch, Polski, پنجابی, پښتو, + Norfuk / Pitkern, Português, Runa Simi, Rumantsch, Romani, Română, + Русский, Саха тыла, Sardu, Sicilianu, Scots, Sámegiella, Simple + English, Slovenčina, Slovenščina, Српски / Srpski, Seeltersk, + Svenska, Kiswahili, தமிழ், తెలుగు, Тоҷикӣ, ไทย, Türkmençe, Tagalog, + Türkçe, Татарча/Tatarça, Українська, اردو, Tiếng Việt, Volapük, + Walon, Winaray, 吴语, isiXhosa, ייִדיש, Yorùbá, Zeêuws, 中文, + Bân-lâm-gú, 粵語""" + if sys.version_info[0] == 2 and os.environ.get('THRIFT_TEST_PY_NO_UTF8STRINGS'): + s1 = s1.encode('utf8') + s2 = s2.encode('utf8') + self.assertEqual(self.client.testString(s1), s1) + self.assertEqual(self.client.testString(s2), s2) + + def testMultiplexed(self): + if self.client2 is not None: + print('testMultiplexed') + self.assertEqual(self.client2.secondtestString('foobar'), 'testString("foobar")') + + def testBool(self): + print('testBool') + self.assertEqual(self.client.testBool(True), True) + self.assertEqual(self.client.testBool(False), False) + + def testByte(self): + print('testByte') + self.assertEqual(self.client.testByte(63), 63) + self.assertEqual(self.client.testByte(-127), -127) + + def testI32(self): + print('testI32') + self.assertEqual(self.client.testI32(-1), -1) + self.assertEqual(self.client.testI32(0), 0) + + def testI64(self): + print('testI64') + self.assertEqual(self.client.testI64(1), 1) + self.assertEqual(self.client.testI64(-34359738368), -34359738368) + + def testDouble(self): + print('testDouble') + self.assertEqual(self.client.testDouble(-5.235098235), -5.235098235) + self.assertEqual(self.client.testDouble(0), 0) + self.assertEqual(self.client.testDouble(-1), -1) + self.assertEqual(self.client.testDouble(-0.000341012439638598279), -0.000341012439638598279) + + def testBinary(self): + print('testBinary') + val = bytearray([i for i in range(0, 256)]) + self.assertEqual(bytearray(self.client.testBinary(bytes(val))), val) + + def testStruct(self): + print('testStruct') + x = Xtruct() + x.string_thing = "Zero" + x.byte_thing = 1 + x.i32_thing = -3 + x.i64_thing = -5 + y = self.client.testStruct(x) + self.assertEqual(y, x) + + def testNest(self): + print('testNest') + inner = Xtruct(string_thing="Zero", byte_thing=1, i32_thing=-3, i64_thing=-5) + x = Xtruct2(struct_thing=inner, byte_thing=0, i32_thing=0) + y = self.client.testNest(x) + self.assertEqual(y, x) + + def testMap(self): + print('testMap') + x = {0: 1, 1: 2, 2: 3, 3: 4, -1: -2} + y = self.client.testMap(x) + self.assertEqual(y, x) + + def testSet(self): + print('testSet') + x = set([8, 1, 42]) + y = self.client.testSet(x) + self.assertEqual(y, x) + + def testList(self): + print('testList') + x = [1, 4, 9, -42] + y = self.client.testList(x) + self.assertEqual(y, x) + + def testEnum(self): + print('testEnum') + x = Numberz.FIVE + y = self.client.testEnum(x) + self.assertEqual(y, x) + + def testTypedef(self): + print('testTypedef') + x = 0xffffffffffffff # 7 bytes of 0xff + y = self.client.testTypedef(x) + self.assertEqual(y, x) + + def testMapMap(self): + print('testMapMap') + x = { + -4: {-4: -4, -3: -3, -2: -2, -1: -1}, + 4: {4: 4, 3: 3, 2: 2, 1: 1}, + } + y = self.client.testMapMap(42) + self.assertEqual(y, x) + + def testMulti(self): + print('testMulti') + xpected = Xtruct(string_thing='Hello2', byte_thing=74, i32_thing=0xff00ff, i64_thing=0xffffffffd0d0) + y = self.client.testMulti(xpected.byte_thing, + xpected.i32_thing, + xpected.i64_thing, + {0: 'abc'}, + Numberz.FIVE, + 0xf0f0f0) + self.assertEqual(y, xpected) + + def testException(self): + print('testException') + self.client.testException('Safe') + try: + self.client.testException('Xception') + self.fail("should have gotten exception") + except Xception as x: + self.assertEqual(x.errorCode, 1001) + self.assertEqual(x.message, 'Xception') + # TODO ensure same behavior for repr within generated python variants + # ensure exception's repr method works + # x_repr = repr(x) + # self.assertEqual(x_repr, 'Xception(errorCode=1001, message=\'Xception\')') + + try: + self.client.testException('TException') + self.fail("should have gotten exception") + except TException as x: + pass + + # Should not throw + self.client.testException('success') + + def testMultiException(self): + print('testMultiException') + try: + self.client.testMultiException('Xception', 'ignore') + except Xception as ex: + self.assertEqual(ex.errorCode, 1001) + self.assertEqual(ex.message, 'This is an Xception') + + try: + self.client.testMultiException('Xception2', 'ignore') + except Xception2 as ex: + self.assertEqual(ex.errorCode, 2002) + self.assertEqual(ex.struct_thing.string_thing, 'This is an Xception2') + + y = self.client.testMultiException('success', 'foobar') + self.assertEqual(y.string_thing, 'foobar') + + def testOneway(self): + print('testOneway') + start = time.time() + self.client.testOneway(1) # type is int, not float + end = time.time() + self.assertTrue(end - start < 3, + "oneway sleep took %f sec" % (end - start)) + + def testOnewayThenNormal(self): + print('testOnewayThenNormal') + self.client.testOneway(1) # type is int, not float + self.assertEqual(self.client.testString('Python'), 'Python') + + +# LAST_SEQID is a global because we have one transport and multiple protocols +# running on it (when multiplexed) +LAST_SEQID = None + + +class TPedanticSequenceIdProtocolWrapper(TProtocolDecorator.TProtocolDecorator): + """ + Wraps any protocol with sequence ID checking: looks for outbound + uniqueness as well as request/response alignment. + """ + def __init__(self, protocol): + # TProtocolDecorator.__new__ does all the heavy lifting + pass + + def writeMessageBegin(self, name, type, seqid): + global LAST_SEQID + if LAST_SEQID and LAST_SEQID == seqid: + raise TProtocol.TProtocolException( + TProtocol.TProtocolException.INVALID_DATA, + "Python client reused sequence ID {0}".format(seqid)) + LAST_SEQID = seqid + super(TPedanticSequenceIdProtocolWrapper, self).writeMessageBegin( + name, type, seqid) + + def readMessageBegin(self): + global LAST_SEQID + (name, type, seqid) =\ + super(TPedanticSequenceIdProtocolWrapper, self).readMessageBegin() + if LAST_SEQID != seqid: + raise TProtocol.TProtocolException( + TProtocol.TProtocolException.INVALID_DATA, + "We sent seqid {0} and server returned seqid {1}".format( + self.last, seqid)) + return (name, type, seqid) + + +def make_pedantic(proto): + """ Wrap a protocol in the pedantic sequence ID wrapper. """ + return TPedanticSequenceIdProtocolWrapper(proto) + + +class MultiplexedOptionalTest(AbstractTest): + def get_protocol2(self, transport): + return None + + +class BinaryTest(MultiplexedOptionalTest): + def get_protocol(self, transport): + return make_pedantic(TBinaryProtocol.TBinaryProtocolFactory().getProtocol(transport)) + + +class MultiplexedBinaryTest(MultiplexedOptionalTest): + def get_protocol(self, transport): + wrapped_proto = make_pedantic(TBinaryProtocol.TBinaryProtocolFactory().getProtocol(transport)) + return TMultiplexedProtocol.TMultiplexedProtocol(wrapped_proto, "ThriftTest") + + def get_protocol2(self, transport): + wrapped_proto = make_pedantic(TBinaryProtocol.TBinaryProtocolFactory().getProtocol(transport)) + return TMultiplexedProtocol.TMultiplexedProtocol(wrapped_proto, "SecondService") + + +class AcceleratedBinaryTest(MultiplexedOptionalTest): + def get_protocol(self, transport): + return make_pedantic(TBinaryProtocol.TBinaryProtocolAcceleratedFactory(fallback=False).getProtocol(transport)) + + +class MultiplexedAcceleratedBinaryTest(MultiplexedOptionalTest): + def get_protocol(self, transport): + wrapped_proto = make_pedantic(TBinaryProtocol.TBinaryProtocolAcceleratedFactory(fallback=False).getProtocol(transport)) + return TMultiplexedProtocol.TMultiplexedProtocol(wrapped_proto, "ThriftTest") + + def get_protocol2(self, transport): + wrapped_proto = make_pedantic(TBinaryProtocol.TBinaryProtocolAcceleratedFactory(fallback=False).getProtocol(transport)) + return TMultiplexedProtocol.TMultiplexedProtocol(wrapped_proto, "SecondService") + + +class CompactTest(MultiplexedOptionalTest): + def get_protocol(self, transport): + return make_pedantic(TCompactProtocol.TCompactProtocolFactory().getProtocol(transport)) + + +class MultiplexedCompactTest(MultiplexedOptionalTest): + def get_protocol(self, transport): + wrapped_proto = make_pedantic(TCompactProtocol.TCompactProtocolFactory().getProtocol(transport)) + return TMultiplexedProtocol.TMultiplexedProtocol(wrapped_proto, "ThriftTest") + + def get_protocol2(self, transport): + wrapped_proto = make_pedantic(TCompactProtocol.TCompactProtocolFactory().getProtocol(transport)) + return TMultiplexedProtocol.TMultiplexedProtocol(wrapped_proto, "SecondService") + + +class AcceleratedCompactTest(MultiplexedOptionalTest): + def get_protocol(self, transport): + return make_pedantic(TCompactProtocol.TCompactProtocolAcceleratedFactory(fallback=False).getProtocol(transport)) + + +class MultiplexedAcceleratedCompactTest(MultiplexedOptionalTest): + def get_protocol(self, transport): + wrapped_proto = make_pedantic(TCompactProtocol.TCompactProtocolAcceleratedFactory(fallback=False).getProtocol(transport)) + return TMultiplexedProtocol.TMultiplexedProtocol(wrapped_proto, "ThriftTest") + + def get_protocol2(self, transport): + wrapped_proto = make_pedantic(TCompactProtocol.TCompactProtocolAcceleratedFactory(fallback=False).getProtocol(transport)) + return TMultiplexedProtocol.TMultiplexedProtocol(wrapped_proto, "SecondService") + + +class JSONTest(MultiplexedOptionalTest): + def get_protocol(self, transport): + return make_pedantic(TJSONProtocol.TJSONProtocolFactory().getProtocol(transport)) + + +class MultiplexedJSONTest(MultiplexedOptionalTest): + def get_protocol(self, transport): + wrapped_proto = make_pedantic(TJSONProtocol.TJSONProtocolFactory().getProtocol(transport)) + return TMultiplexedProtocol.TMultiplexedProtocol(wrapped_proto, "ThriftTest") + + def get_protocol2(self, transport): + wrapped_proto = make_pedantic(TJSONProtocol.TJSONProtocolFactory().getProtocol(transport)) + return TMultiplexedProtocol.TMultiplexedProtocol(wrapped_proto, "SecondService") + + +class HeaderTest(MultiplexedOptionalTest): + def get_protocol(self, transport): + factory = THeaderProtocol.THeaderProtocolFactory() + return make_pedantic(factory.getProtocol(transport)) + + +class MultiplexedHeaderTest(MultiplexedOptionalTest): + def get_protocol(self, transport): + wrapped_proto = make_pedantic(THeaderProtocol.THeaderProtocolFactory().getProtocol(transport)) + return TMultiplexedProtocol.TMultiplexedProtocol(wrapped_proto, "ThriftTest") + + def get_protocol2(self, transport): + wrapped_proto = make_pedantic(THeaderProtocol.THeaderProtocolFactory().getProtocol(transport)) + return TMultiplexedProtocol.TMultiplexedProtocol(wrapped_proto, "SecondService") + + +def suite(): + suite = unittest.TestSuite() + loader = unittest.TestLoader() + if options.proto == 'binary': # look for --proto on cmdline + suite.addTest(loader.loadTestsFromTestCase(BinaryTest)) + elif options.proto == 'accel': + suite.addTest(loader.loadTestsFromTestCase(AcceleratedBinaryTest)) + elif options.proto == 'accelc': + suite.addTest(loader.loadTestsFromTestCase(AcceleratedCompactTest)) + elif options.proto == 'compact': + suite.addTest(loader.loadTestsFromTestCase(CompactTest)) + elif options.proto == 'header': + suite.addTest(loader.loadTestsFromTestCase(HeaderTest)) + elif options.proto == 'json': + suite.addTest(loader.loadTestsFromTestCase(JSONTest)) + elif options.proto == 'multi': + suite.addTest(loader.loadTestsFromTestCase(MultiplexedBinaryTest)) + elif options.proto == 'multia': + suite.addTest(loader.loadTestsFromTestCase(MultiplexedAcceleratedBinaryTest)) + elif options.proto == 'multiac': + suite.addTest(loader.loadTestsFromTestCase(MultiplexedAcceleratedCompactTest)) + elif options.proto == 'multic': + suite.addTest(loader.loadTestsFromTestCase(MultiplexedCompactTest)) + elif options.proto == 'multih': + suite.addTest(loader.loadTestsFromTestCase(MultiplexedHeaderTest)) + elif options.proto == 'multij': + suite.addTest(loader.loadTestsFromTestCase(MultiplexedJSONTest)) + else: + raise AssertionError('Unknown protocol given with --protocol: %s' % options.proto) + return suite + + +class OwnArgsTestProgram(unittest.TestProgram): + def parseArgs(self, argv): + if args: + self.testNames = args + else: + self.testNames = ([self.defaultTest]) + self.createTests() + + +if __name__ == "__main__": + parser = OptionParser() + parser.add_option('--libpydir', type='string', dest='libpydir', + help='include this directory in sys.path for locating library code') + parser.add_option('--genpydir', type='string', dest='genpydir', + help='include this directory in sys.path for locating generated code') + parser.add_option("--port", type="int", dest="port", + help="connect to server at port") + parser.add_option("--host", type="string", dest="host", + help="connect to server") + parser.add_option("--zlib", action="store_true", dest="zlib", + help="use zlib wrapper for compressed transport") + parser.add_option("--ssl", action="store_true", dest="ssl", + help="use SSL for encrypted transport") + parser.add_option("--http", dest="http_path", + help="Use the HTTP transport with the specified path") + 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('--protocol', dest="proto", type="string", + help="protocol to use, one of: accel, accelc, binary, compact, header, json, multi, multia, multiac, multic, multih, multij") + parser.add_option('--transport', dest="trans", type="string", + help="transport to use, one of: buffered, framed, http") + parser.set_defaults(framed=False, http_path=None, verbose=1, host='localhost', port=9090, proto='binary') + options, args = parser.parse_args() + + if options.genpydir: + sys.path.insert(0, os.path.join(SCRIPT_DIR, options.genpydir)) + + if options.http_path: + options.trans = 'http' + + from ThriftTest import SecondService + from ThriftTest import ThriftTest + from ThriftTest.ttypes import Xtruct, Xtruct2, Numberz, Xception, Xception2 + from thrift.Thrift import TException + from thrift.transport import TTransport + from thrift.transport import TSocket + from thrift.transport import THttpClient + from thrift.transport import TZlibTransport + from thrift.protocol import TBinaryProtocol + from thrift.protocol import TCompactProtocol + from thrift.protocol import THeaderProtocol + from thrift.protocol import TJSONProtocol + from thrift.protocol import TMultiplexedProtocol + + OwnArgsTestProgram(defaultTest="suite", testRunner=unittest.TextTestRunner(verbosity=1)) diff --git a/src/jaegertracing/thrift/test/py/TestEof.py b/src/jaegertracing/thrift/test/py/TestEof.py new file mode 100755 index 000000000..0b4a82960 --- /dev/null +++ b/src/jaegertracing/thrift/test/py/TestEof.py @@ -0,0 +1,132 @@ +#!/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 ThriftTest.ttypes import Xtruct +from thrift.transport import TTransport +from thrift.protocol import TBinaryProtocol +from thrift.protocol import TCompactProtocol +import unittest + + +class TestEof(unittest.TestCase): + + def make_data(self, pfactory=None): + trans = TTransport.TMemoryBuffer() + if pfactory: + prot = pfactory.getProtocol(trans) + else: + prot = TBinaryProtocol.TBinaryProtocol(trans) + + x = Xtruct() + x.string_thing = "Zero" + x.byte_thing = 0 + + x.write(prot) + + x = Xtruct() + x.string_thing = "One" + x.byte_thing = 1 + + x.write(prot) + + return trans.getvalue() + + def testTransportReadAll(self): + """Test that readAll on any type of transport throws an EOFError""" + trans = TTransport.TMemoryBuffer(self.make_data()) + trans.readAll(1) + + try: + trans.readAll(10000) + except EOFError: + return + + self.fail("Should have gotten EOFError") + + def eofTestHelper(self, pfactory): + trans = TTransport.TMemoryBuffer(self.make_data(pfactory)) + prot = pfactory.getProtocol(trans) + + x = Xtruct() + x.read(prot) + self.assertEqual(x.string_thing, "Zero") + self.assertEqual(x.byte_thing, 0) + + x = Xtruct() + x.read(prot) + self.assertEqual(x.string_thing, "One") + self.assertEqual(x.byte_thing, 1) + + try: + x = Xtruct() + x.read(prot) + except EOFError: + return + + self.fail("Should have gotten EOFError") + + def eofTestHelperStress(self, pfactory): + """Test the ability of TBinaryProtocol to deal with the removal of every byte in the file""" + # TODO: we should make sure this covers more of the code paths + + data = self.make_data(pfactory) + for i in range(0, len(data) + 1): + trans = TTransport.TMemoryBuffer(data[0:i]) + prot = pfactory.getProtocol(trans) + try: + x = Xtruct() + x.read(prot) + x.read(prot) + x.read(prot) + except EOFError: + continue + self.fail("Should have gotten an EOFError") + + def testBinaryProtocolEof(self): + """Test that TBinaryProtocol throws an EOFError when it reaches the end of the stream""" + self.eofTestHelper(TBinaryProtocol.TBinaryProtocolFactory()) + self.eofTestHelperStress(TBinaryProtocol.TBinaryProtocolFactory()) + + def testBinaryProtocolAcceleratedBinaryEof(self): + """Test that TBinaryProtocolAccelerated throws an EOFError when it reaches the end of the stream""" + self.eofTestHelper(TBinaryProtocol.TBinaryProtocolAcceleratedFactory(fallback=False)) + self.eofTestHelperStress(TBinaryProtocol.TBinaryProtocolAcceleratedFactory(fallback=False)) + + def testCompactProtocolEof(self): + """Test that TCompactProtocol throws an EOFError when it reaches the end of the stream""" + self.eofTestHelper(TCompactProtocol.TCompactProtocolFactory()) + self.eofTestHelperStress(TCompactProtocol.TCompactProtocolFactory()) + + def testCompactProtocolAcceleratedCompactEof(self): + """Test that TCompactProtocolAccelerated throws an EOFError when it reaches the end of the stream""" + self.eofTestHelper(TCompactProtocol.TCompactProtocolAcceleratedFactory(fallback=False)) + self.eofTestHelperStress(TCompactProtocol.TCompactProtocolAcceleratedFactory(fallback=False)) + + +def suite(): + suite = unittest.TestSuite() + loader = unittest.TestLoader() + suite.addTest(loader.loadTestsFromTestCase(TestEof)) + return suite + + +if __name__ == "__main__": + unittest.main(defaultTest="suite", testRunner=unittest.TextTestRunner(verbosity=2)) diff --git a/src/jaegertracing/thrift/test/py/TestFrozen.py b/src/jaegertracing/thrift/test/py/TestFrozen.py new file mode 100755 index 000000000..6d2595cf2 --- /dev/null +++ b/src/jaegertracing/thrift/test/py/TestFrozen.py @@ -0,0 +1,123 @@ +#!/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 DebugProtoTest.ttypes import CompactProtoTestStruct, Empty, Wrapper +from thrift.Thrift import TFrozenDict +from thrift.transport import TTransport +from thrift.protocol import TBinaryProtocol, TCompactProtocol +import collections +import unittest + + +class TestFrozenBase(unittest.TestCase): + def _roundtrip(self, src, dst): + otrans = TTransport.TMemoryBuffer() + optoro = self.protocol(otrans) + src.write(optoro) + itrans = TTransport.TMemoryBuffer(otrans.getvalue()) + iproto = self.protocol(itrans) + return dst.read(iproto) or dst + + def test_dict_is_hashable_only_after_frozen(self): + d0 = {} + self.assertFalse(isinstance(d0, collections.Hashable)) + d1 = TFrozenDict(d0) + self.assertTrue(isinstance(d1, collections.Hashable)) + + def test_struct_with_collection_fields(self): + pass + + def test_set(self): + """Test that annotated set field can be serialized and deserialized""" + x = CompactProtoTestStruct(set_byte_map={ + frozenset([42, 100, -100]): 99, + frozenset([0]): 100, + frozenset([]): 0, + }) + x2 = self._roundtrip(x, CompactProtoTestStruct()) + self.assertEqual(x2.set_byte_map[frozenset([42, 100, -100])], 99) + self.assertEqual(x2.set_byte_map[frozenset([0])], 100) + self.assertEqual(x2.set_byte_map[frozenset([])], 0) + + def test_map(self): + """Test that annotated map field can be serialized and deserialized""" + x = CompactProtoTestStruct(map_byte_map={ + TFrozenDict({42: 42, 100: -100}): 99, + TFrozenDict({0: 0}): 100, + TFrozenDict({}): 0, + }) + x2 = self._roundtrip(x, CompactProtoTestStruct()) + self.assertEqual(x2.map_byte_map[TFrozenDict({42: 42, 100: -100})], 99) + self.assertEqual(x2.map_byte_map[TFrozenDict({0: 0})], 100) + self.assertEqual(x2.map_byte_map[TFrozenDict({})], 0) + + def test_list(self): + """Test that annotated list field can be serialized and deserialized""" + x = CompactProtoTestStruct(list_byte_map={ + (42, 100, -100): 99, + (0,): 100, + (): 0, + }) + x2 = self._roundtrip(x, CompactProtoTestStruct()) + self.assertEqual(x2.list_byte_map[(42, 100, -100)], 99) + self.assertEqual(x2.list_byte_map[(0,)], 100) + self.assertEqual(x2.list_byte_map[()], 0) + + def test_empty_struct(self): + """Test that annotated empty struct can be serialized and deserialized""" + x = CompactProtoTestStruct(empty_struct_field=Empty()) + x2 = self._roundtrip(x, CompactProtoTestStruct()) + self.assertEqual(x2.empty_struct_field, Empty()) + + def test_struct(self): + """Test that annotated struct can be serialized and deserialized""" + x = Wrapper(foo=Empty()) + self.assertEqual(x.foo, Empty()) + x2 = self._roundtrip(x, Wrapper) + self.assertEqual(x2.foo, Empty()) + + +class TestFrozen(TestFrozenBase): + def protocol(self, trans): + return TBinaryProtocol.TBinaryProtocolFactory().getProtocol(trans) + + +class TestFrozenAcceleratedBinary(TestFrozenBase): + def protocol(self, trans): + return TBinaryProtocol.TBinaryProtocolAcceleratedFactory(fallback=False).getProtocol(trans) + + +class TestFrozenAcceleratedCompact(TestFrozenBase): + def protocol(self, trans): + return TCompactProtocol.TCompactProtocolAcceleratedFactory(fallback=False).getProtocol(trans) + + +def suite(): + suite = unittest.TestSuite() + loader = unittest.TestLoader() + suite.addTest(loader.loadTestsFromTestCase(TestFrozen)) + suite.addTest(loader.loadTestsFromTestCase(TestFrozenAcceleratedBinary)) + suite.addTest(loader.loadTestsFromTestCase(TestFrozenAcceleratedCompact)) + return suite + + +if __name__ == "__main__": + unittest.main(defaultTest="suite", testRunner=unittest.TextTestRunner(verbosity=2)) diff --git a/src/jaegertracing/thrift/test/py/TestRenderedDoubleConstants.py b/src/jaegertracing/thrift/test/py/TestRenderedDoubleConstants.py new file mode 100644 index 000000000..20903d81b --- /dev/null +++ b/src/jaegertracing/thrift/test/py/TestRenderedDoubleConstants.py @@ -0,0 +1,177 @@ +# +# 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. +# + +import unittest + +from DoubleConstantsTest import constants + +# +# In order to run the test under Windows. We need to create symbolic link +# name 'thrift' to '../src' folder by using: +# +# mklink /D thrift ..\src +# + + +class TestRenderedDoubleConstants(unittest.TestCase): + ASSERTION_MESSAGE_FOR_RENDERED_DOUBLE_CONSTANTS_TEST = \ + "failed to verify a double constant generated by Thrift (expected = %f, got = %f)" + ASSERTION_MESSAGE_FOR_RENDERED_DOUBLE_LIST_TEST =\ + "failed to verify a list item by Thrift (expected = %f, got = %f)" + ASSERTION_MESSAGE_FOR_TYPE_CHECKS = "the rendered variable with name %s is not of double type" + + # to make sure the variables inside Thrift files are generated correctly + def test_rendered_double_constants(self): + EXPECTED_DOUBLE_ASSIGNED_TO_INT_CONSTANT = 1.0 + EXPECTED_DOUBLE_ASSIGNED_TO_NEGATIVE_INT_CONSTANT = -100.0 + EXPECTED_DOUBLE_ASSIGNED_TO_LARGEST_INT_CONSTANT = 9223372036854775807.0 + EXPECTED_DOUBLE_ASSIGNED_TO_SMALLEST_INT_CONSTANT = -9223372036854775807.0 + EXPECTED_DOUBLE_ASSIGNED_TO_DOUBLE_WITH_MANY_DECIMALS = 3.14159265359 + EXPECTED_DOUBLE_ASSIGNED_TO_FRACTIONAL_DOUBLE = 1000000.1 + EXPECTED_DOUBLE_ASSIGNED_TO_NEGATIVE_FRACTIONAL_DOUBLE = -1000000.1 + EXPECTED_DOUBLE_ASSIGNED_TO_LARGE_DOUBLE = 1.7e+308 + EXPECTED_DOUBLE_ASSIGNED_TO_LARGE_FRACTIONAL_DOUBLE = 9223372036854775816.43 + EXPECTED_DOUBLE_ASSIGNED_TO_SMALL_DOUBLE = -1.7e+308 + EXPECTED_DOUBLE_ASSIGNED_TO_NEGATIVE_BUT_LARGE_FRACTIONAL_DOUBLE = -9223372036854775816.43 + self.assertAlmostEqual( + constants.DOUBLE_ASSIGNED_TO_INT_CONSTANT_TEST, EXPECTED_DOUBLE_ASSIGNED_TO_INT_CONSTANT, places=7, + msg=TestRenderedDoubleConstants.ASSERTION_MESSAGE_FOR_RENDERED_DOUBLE_CONSTANTS_TEST % ( + EXPECTED_DOUBLE_ASSIGNED_TO_INT_CONSTANT, constants.DOUBLE_ASSIGNED_TO_INT_CONSTANT_TEST)) + self.assertAlmostEqual( + constants.DOUBLE_ASSIGNED_TO_NEGATIVE_INT_CONSTANT_TEST, EXPECTED_DOUBLE_ASSIGNED_TO_NEGATIVE_INT_CONSTANT, + places=7, + msg=TestRenderedDoubleConstants.ASSERTION_MESSAGE_FOR_RENDERED_DOUBLE_CONSTANTS_TEST % ( + EXPECTED_DOUBLE_ASSIGNED_TO_NEGATIVE_INT_CONSTANT, + constants.DOUBLE_ASSIGNED_TO_NEGATIVE_INT_CONSTANT_TEST)) + self.assertAlmostEqual( + constants.DOUBLE_ASSIGNED_TO_LARGEST_INT_CONSTANT_TEST, EXPECTED_DOUBLE_ASSIGNED_TO_LARGEST_INT_CONSTANT, + places=7, + msg=TestRenderedDoubleConstants.ASSERTION_MESSAGE_FOR_RENDERED_DOUBLE_CONSTANTS_TEST % ( + EXPECTED_DOUBLE_ASSIGNED_TO_LARGEST_INT_CONSTANT, + constants.DOUBLE_ASSIGNED_TO_LARGEST_INT_CONSTANT_TEST)) + self.assertAlmostEqual( + constants.DOUBLE_ASSIGNED_TO_SMALLEST_INT_CONSTANT_TEST, EXPECTED_DOUBLE_ASSIGNED_TO_SMALLEST_INT_CONSTANT, + places=7, + msg=TestRenderedDoubleConstants.ASSERTION_MESSAGE_FOR_RENDERED_DOUBLE_CONSTANTS_TEST % ( + EXPECTED_DOUBLE_ASSIGNED_TO_SMALLEST_INT_CONSTANT, + constants.DOUBLE_ASSIGNED_TO_SMALLEST_INT_CONSTANT_TEST)) + self.assertAlmostEqual( + constants.DOUBLE_ASSIGNED_TO_DOUBLE_WITH_MANY_DECIMALS_TEST, + EXPECTED_DOUBLE_ASSIGNED_TO_DOUBLE_WITH_MANY_DECIMALS, places=7, + msg=TestRenderedDoubleConstants.ASSERTION_MESSAGE_FOR_RENDERED_DOUBLE_CONSTANTS_TEST % ( + EXPECTED_DOUBLE_ASSIGNED_TO_DOUBLE_WITH_MANY_DECIMALS, + constants.DOUBLE_ASSIGNED_TO_DOUBLE_WITH_MANY_DECIMALS_TEST)) + self.assertAlmostEqual( + constants.DOUBLE_ASSIGNED_TO_FRACTIONAL_DOUBLE_TEST, EXPECTED_DOUBLE_ASSIGNED_TO_FRACTIONAL_DOUBLE, + places=7, + msg=TestRenderedDoubleConstants.ASSERTION_MESSAGE_FOR_RENDERED_DOUBLE_CONSTANTS_TEST % ( + EXPECTED_DOUBLE_ASSIGNED_TO_FRACTIONAL_DOUBLE, + constants.DOUBLE_ASSIGNED_TO_FRACTIONAL_DOUBLE_TEST)) + self.assertAlmostEqual( + constants.DOUBLE_ASSIGNED_TO_NEGATIVE_FRACTIONAL_DOUBLE_TEST, + EXPECTED_DOUBLE_ASSIGNED_TO_NEGATIVE_FRACTIONAL_DOUBLE, places=7, + msg=TestRenderedDoubleConstants.ASSERTION_MESSAGE_FOR_RENDERED_DOUBLE_CONSTANTS_TEST % ( + EXPECTED_DOUBLE_ASSIGNED_TO_NEGATIVE_FRACTIONAL_DOUBLE, + constants.DOUBLE_ASSIGNED_TO_NEGATIVE_FRACTIONAL_DOUBLE_TEST)) + self.assertAlmostEqual( + constants.DOUBLE_ASSIGNED_TO_LARGE_DOUBLE_TEST, EXPECTED_DOUBLE_ASSIGNED_TO_LARGE_DOUBLE, places=7, + msg=TestRenderedDoubleConstants.ASSERTION_MESSAGE_FOR_RENDERED_DOUBLE_CONSTANTS_TEST % ( + EXPECTED_DOUBLE_ASSIGNED_TO_LARGE_DOUBLE, + constants.DOUBLE_ASSIGNED_TO_LARGE_DOUBLE_TEST)) + self.assertAlmostEqual( + constants.DOUBLE_ASSIGNED_TO_LARGE_FRACTIONAL_DOUBLE_TEST, + EXPECTED_DOUBLE_ASSIGNED_TO_LARGE_FRACTIONAL_DOUBLE, places=7, + msg=TestRenderedDoubleConstants.ASSERTION_MESSAGE_FOR_RENDERED_DOUBLE_CONSTANTS_TEST % ( + EXPECTED_DOUBLE_ASSIGNED_TO_LARGE_FRACTIONAL_DOUBLE, + constants.DOUBLE_ASSIGNED_TO_LARGE_FRACTIONAL_DOUBLE_TEST)) + self.assertAlmostEqual( + constants.DOUBLE_ASSIGNED_TO_SMALL_DOUBLE_TEST, EXPECTED_DOUBLE_ASSIGNED_TO_SMALL_DOUBLE, places=7, + msg=TestRenderedDoubleConstants.ASSERTION_MESSAGE_FOR_RENDERED_DOUBLE_CONSTANTS_TEST % ( + EXPECTED_DOUBLE_ASSIGNED_TO_SMALL_DOUBLE, + constants.DOUBLE_ASSIGNED_TO_SMALL_DOUBLE_TEST)) + self.assertAlmostEqual( + constants.DOUBLE_ASSIGNED_TO_NEGATIVE_BUT_LARGE_FRACTIONAL_DOUBLE_TEST, + EXPECTED_DOUBLE_ASSIGNED_TO_NEGATIVE_BUT_LARGE_FRACTIONAL_DOUBLE, places=7, + msg=TestRenderedDoubleConstants.ASSERTION_MESSAGE_FOR_RENDERED_DOUBLE_CONSTANTS_TEST % ( + EXPECTED_DOUBLE_ASSIGNED_TO_NEGATIVE_BUT_LARGE_FRACTIONAL_DOUBLE, + constants.DOUBLE_ASSIGNED_TO_NEGATIVE_BUT_LARGE_FRACTIONAL_DOUBLE_TEST)) + self.assertTrue( + isinstance(constants.DOUBLE_ASSIGNED_TO_INT_CONSTANT_TEST, float), + msg=TestRenderedDoubleConstants.ASSERTION_MESSAGE_FOR_TYPE_CHECKS % + "DOUBLE_ASSIGNED_TO_INT_CONSTANT_TEST") + self.assertTrue( + isinstance(constants.DOUBLE_ASSIGNED_TO_NEGATIVE_INT_CONSTANT_TEST, float), + msg=TestRenderedDoubleConstants.ASSERTION_MESSAGE_FOR_TYPE_CHECKS % + "DOUBLE_ASSIGNED_TO_NEGATIVE_INT_CONSTANT_TEST") + self.assertTrue( + isinstance(constants.DOUBLE_ASSIGNED_TO_LARGEST_INT_CONSTANT_TEST, float), + msg=TestRenderedDoubleConstants.ASSERTION_MESSAGE_FOR_TYPE_CHECKS % + "DOUBLE_ASSIGNED_TO_LARGEST_INT_CONSTANT_TEST") + self.assertTrue( + isinstance(constants.DOUBLE_ASSIGNED_TO_SMALLEST_INT_CONSTANT_TEST, float), + msg=TestRenderedDoubleConstants.ASSERTION_MESSAGE_FOR_TYPE_CHECKS % + "DOUBLE_ASSIGNED_TO_SMALLEST_INT_CONSTANT_TEST") + self.assertTrue( + isinstance(constants.DOUBLE_ASSIGNED_TO_DOUBLE_WITH_MANY_DECIMALS_TEST, float), + msg=TestRenderedDoubleConstants.ASSERTION_MESSAGE_FOR_TYPE_CHECKS % + "DOUBLE_ASSIGNED_TO_DOUBLE_WITH_MANY_DECIMALS_TEST") + self.assertTrue( + isinstance(constants.DOUBLE_ASSIGNED_TO_FRACTIONAL_DOUBLE_TEST, float), + msg=TestRenderedDoubleConstants.ASSERTION_MESSAGE_FOR_TYPE_CHECKS % + "DOUBLE_ASSIGNED_TO_FRACTIONAL_DOUBLE_TEST") + self.assertTrue( + isinstance(constants.DOUBLE_ASSIGNED_TO_NEGATIVE_FRACTIONAL_DOUBLE_TEST, float), + msg=TestRenderedDoubleConstants.ASSERTION_MESSAGE_FOR_TYPE_CHECKS % + "DOUBLE_ASSIGNED_TO_NEGATIVE_FRACTIONAL_DOUBLE_TEST") + self.assertTrue( + isinstance(constants.DOUBLE_ASSIGNED_TO_LARGE_DOUBLE_TEST, float), + msg=TestRenderedDoubleConstants.ASSERTION_MESSAGE_FOR_TYPE_CHECKS % + "DOUBLE_ASSIGNED_TO_LARGE_DOUBLE_TEST") + self.assertTrue( + isinstance(constants.DOUBLE_ASSIGNED_TO_LARGE_FRACTIONAL_DOUBLE_TEST, float), + msg=TestRenderedDoubleConstants.ASSERTION_MESSAGE_FOR_TYPE_CHECKS % + "DOUBLE_ASSIGNED_TO_LARGE_FRACTIONAL_DOUBLE_TEST") + self.assertTrue( + isinstance(constants.DOUBLE_ASSIGNED_TO_SMALL_DOUBLE_TEST, float), + msg=TestRenderedDoubleConstants.ASSERTION_MESSAGE_FOR_TYPE_CHECKS % + "DOUBLE_ASSIGNED_TO_SMALL_DOUBLE_TEST") + self.assertTrue( + isinstance(constants.DOUBLE_ASSIGNED_TO_NEGATIVE_BUT_LARGE_FRACTIONAL_DOUBLE_TEST, float), + msg=TestRenderedDoubleConstants.ASSERTION_MESSAGE_FOR_TYPE_CHECKS % + "DOUBLE_ASSIGNED_TO_NEGATIVE_BUT_LARGE_FRACTIONAL_DOUBLE_TEST") + + # to make sure the variables inside Thrift files are generated correctly + def test_rendered_double_list(self): + EXPECTED_DOUBLE_LIST = [1.0, -100.0, 100.0, 9223372036854775807.0, -9223372036854775807.0, 3.14159265359, + 1000000.1, -1000000.1, 1.7e+308, -1.7e+308, 9223372036854775816.43, + -9223372036854775816.43] + self.assertEqual(len(constants.DOUBLE_LIST_TEST), len(EXPECTED_DOUBLE_LIST)) + for i, expectedValue in enumerate(EXPECTED_DOUBLE_LIST): + self.assertAlmostEqual(constants.DOUBLE_LIST_TEST[i], expectedValue, places=7) + + +def suite(): + suite = unittest.TestSuite() + loader = unittest.TestLoader() + suite.addTest(loader.loadTestsFromTestCase(TestRenderedDoubleConstants)) + return suite + + +if __name__ == "__main__": + unittest.main(defaultTest="suite", testRunner=unittest.TextTestRunner(verbosity=2)) diff --git a/src/jaegertracing/thrift/test/py/TestServer.py b/src/jaegertracing/thrift/test/py/TestServer.py new file mode 100755 index 000000000..d0a13e5f7 --- /dev/null +++ b/src/jaegertracing/thrift/test/py/TestServer.py @@ -0,0 +1,412 @@ +#!/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 +import logging +import os +import signal +import sys +import time +from optparse import OptionParser + +from util import local_libpath +sys.path.insert(0, local_libpath()) +from thrift.protocol import TProtocol, TProtocolDecorator + +SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__)) + + +class TestHandler(object): + def testVoid(self): + if options.verbose > 1: + logging.info('testVoid()') + + def testString(self, str): + if options.verbose > 1: + logging.info('testString(%s)' % str) + return str + + def testBool(self, boolean): + if options.verbose > 1: + logging.info('testBool(%s)' % str(boolean).lower()) + return boolean + + def testByte(self, byte): + if options.verbose > 1: + logging.info('testByte(%d)' % byte) + return byte + + def testI16(self, i16): + if options.verbose > 1: + logging.info('testI16(%d)' % i16) + return i16 + + def testI32(self, i32): + if options.verbose > 1: + logging.info('testI32(%d)' % i32) + return i32 + + def testI64(self, i64): + if options.verbose > 1: + logging.info('testI64(%d)' % i64) + return i64 + + def testDouble(self, dub): + if options.verbose > 1: + logging.info('testDouble(%f)' % dub) + return dub + + def testBinary(self, thing): + if options.verbose > 1: + logging.info('testBinary()') # TODO: hex output + return thing + + def testStruct(self, thing): + if options.verbose > 1: + logging.info('testStruct({%s, %s, %s, %s})' % (thing.string_thing, thing.byte_thing, thing.i32_thing, thing.i64_thing)) + return thing + + def testException(self, arg): + # if options.verbose > 1: + logging.info('testException(%s)' % arg) + if arg == 'Xception': + raise Xception(errorCode=1001, message=arg) + elif arg == 'TException': + raise TException(message='This is a TException') + + def testMultiException(self, arg0, arg1): + if options.verbose > 1: + logging.info('testMultiException(%s, %s)' % (arg0, arg1)) + if arg0 == 'Xception': + raise Xception(errorCode=1001, message='This is an Xception') + elif arg0 == 'Xception2': + raise Xception2( + errorCode=2002, + struct_thing=Xtruct(string_thing='This is an Xception2')) + return Xtruct(string_thing=arg1) + + def testOneway(self, seconds): + if options.verbose > 1: + logging.info('testOneway(%d) => sleeping...' % seconds) + time.sleep(seconds / 3) # be quick + if options.verbose > 1: + logging.info('done sleeping') + + def testNest(self, thing): + if options.verbose > 1: + logging.info('testNest(%s)' % thing) + return thing + + def testMap(self, thing): + if options.verbose > 1: + logging.info('testMap(%s)' % thing) + return thing + + def testStringMap(self, thing): + if options.verbose > 1: + logging.info('testStringMap(%s)' % thing) + return thing + + def testSet(self, thing): + if options.verbose > 1: + logging.info('testSet(%s)' % thing) + return thing + + def testList(self, thing): + if options.verbose > 1: + logging.info('testList(%s)' % thing) + return thing + + def testEnum(self, thing): + if options.verbose > 1: + logging.info('testEnum(%s)' % thing) + return thing + + def testTypedef(self, thing): + if options.verbose > 1: + logging.info('testTypedef(%s)' % thing) + return thing + + def testMapMap(self, thing): + if options.verbose > 1: + logging.info('testMapMap(%s)' % thing) + return { + -4: { + -4: -4, + -3: -3, + -2: -2, + -1: -1, + }, + 4: { + 4: 4, + 3: 3, + 2: 2, + 1: 1, + }, + } + + def testInsanity(self, argument): + if options.verbose > 1: + logging.info('testInsanity(%s)' % argument) + return { + 1: { + 2: argument, + 3: argument, + }, + 2: {6: Insanity()}, + } + + def testMulti(self, arg0, arg1, arg2, arg3, arg4, arg5): + if options.verbose > 1: + logging.info('testMulti(%s)' % [arg0, arg1, arg2, arg3, arg4, arg5]) + return Xtruct(string_thing='Hello2', + byte_thing=arg0, i32_thing=arg1, i64_thing=arg2) + + +class SecondHandler(object): + def secondtestString(self, argument): + return "testString(\"" + argument + "\")" + + +# LAST_SEQID is a global because we have one transport and multiple protocols +# running on it (when multiplexed) +LAST_SEQID = None + + +class TPedanticSequenceIdProtocolWrapper(TProtocolDecorator.TProtocolDecorator): + """ + Wraps any protocol with sequence ID checking: looks for outbound + uniqueness as well as request/response alignment. + """ + def __init__(self, protocol): + # TProtocolDecorator.__new__ does all the heavy lifting + pass + + def readMessageBegin(self): + global LAST_SEQID + (name, type, seqid) =\ + super(TPedanticSequenceIdProtocolWrapper, self).readMessageBegin() + if LAST_SEQID is not None and LAST_SEQID == seqid: + raise TProtocol.TProtocolException( + TProtocol.TProtocolException.INVALID_DATA, + "We received the same seqid {0} twice in a row".format(seqid)) + LAST_SEQID = seqid + return (name, type, seqid) + + +def make_pedantic(proto): + """ Wrap a protocol in the pedantic sequence ID wrapper. """ + # NOTE: this is disabled for now as many clients send seqid + # of zero and that is okay, need a way to identify + # clients that MUST send seqid unique to function right + # or just force all implementations to send unique seqids (preferred) + return proto # TPedanticSequenceIdProtocolWrapper(proto) + + +class TPedanticSequenceIdProtocolFactory(TProtocol.TProtocolFactory): + def __init__(self, encapsulated): + super(TPedanticSequenceIdProtocolFactory, self).__init__() + self.encapsulated = encapsulated + + def getProtocol(self, trans): + return make_pedantic(self.encapsulated.getProtocol(trans)) + + +def main(options): + # common header allowed client types + allowed_client_types = [ + THeaderTransport.THeaderClientType.HEADERS, + THeaderTransport.THeaderClientType.FRAMED_BINARY, + THeaderTransport.THeaderClientType.UNFRAMED_BINARY, + THeaderTransport.THeaderClientType.FRAMED_COMPACT, + THeaderTransport.THeaderClientType.UNFRAMED_COMPACT, + ] + + # set up the protocol factory form the --protocol option + prot_factories = { + 'accel': TBinaryProtocol.TBinaryProtocolAcceleratedFactory(), + 'multia': TBinaryProtocol.TBinaryProtocolAcceleratedFactory(), + 'accelc': TCompactProtocol.TCompactProtocolAcceleratedFactory(), + 'multiac': TCompactProtocol.TCompactProtocolAcceleratedFactory(), + 'binary': TPedanticSequenceIdProtocolFactory(TBinaryProtocol.TBinaryProtocolFactory()), + 'multi': TPedanticSequenceIdProtocolFactory(TBinaryProtocol.TBinaryProtocolFactory()), + 'compact': TCompactProtocol.TCompactProtocolFactory(), + 'multic': TCompactProtocol.TCompactProtocolFactory(), + 'header': THeaderProtocol.THeaderProtocolFactory(allowed_client_types), + 'multih': THeaderProtocol.THeaderProtocolFactory(allowed_client_types), + 'json': TJSONProtocol.TJSONProtocolFactory(), + 'multij': TJSONProtocol.TJSONProtocolFactory(), + } + pfactory = prot_factories.get(options.proto, None) + if pfactory is None: + raise AssertionError('Unknown --protocol option: %s' % options.proto) + try: + pfactory.string_length_limit = options.string_limit + pfactory.container_length_limit = options.container_limit + except Exception: + # Ignore errors for those protocols that does not support length limit + pass + + # get the server type (TSimpleServer, TNonblockingServer, etc...) + if len(args) > 1: + raise AssertionError('Only one server type may be specified, not multiple types.') + server_type = args[0] + if options.trans == 'http': + server_type = 'THttpServer' + + # Set up the handler and processor objects + handler = TestHandler() + processor = ThriftTest.Processor(handler) + + if options.proto.startswith('multi'): + secondHandler = SecondHandler() + secondProcessor = SecondService.Processor(secondHandler) + + multiplexedProcessor = TMultiplexedProcessor() + multiplexedProcessor.registerDefault(processor) + multiplexedProcessor.registerProcessor('ThriftTest', processor) + multiplexedProcessor.registerProcessor('SecondService', secondProcessor) + processor = multiplexedProcessor + + global server + + # Handle THttpServer as a special case + if server_type == 'THttpServer': + if options.ssl: + __certfile = os.path.join(os.path.dirname(SCRIPT_DIR), "keys", "server.crt") + __keyfile = os.path.join(os.path.dirname(SCRIPT_DIR), "keys", "server.key") + server = THttpServer.THttpServer(processor, ('', options.port), pfactory, cert_file=__certfile, key_file=__keyfile) + else: + server = THttpServer.THttpServer(processor, ('', options.port), pfactory) + server.serve() + sys.exit(0) + + # set up server transport and transport factory + + abs_key_path = os.path.join(os.path.dirname(SCRIPT_DIR), 'keys', 'server.pem') + + host = None + if options.ssl: + from thrift.transport import TSSLSocket + transport = TSSLSocket.TSSLServerSocket(host, options.port, certfile=abs_key_path) + else: + transport = TSocket.TServerSocket(host, options.port) + tfactory = TTransport.TBufferedTransportFactory() + if options.trans == 'buffered': + tfactory = TTransport.TBufferedTransportFactory() + elif options.trans == 'framed': + tfactory = TTransport.TFramedTransportFactory() + elif options.trans == '': + raise AssertionError('Unknown --transport option: %s' % options.trans) + else: + tfactory = TTransport.TBufferedTransportFactory() + # if --zlib, then wrap server transport, and use a different transport factory + if options.zlib: + transport = TZlibTransport.TZlibTransport(transport) # wrap with zlib + tfactory = TZlibTransport.TZlibTransportFactory() + + # do server-specific setup here: + if server_type == "TNonblockingServer": + server = TNonblockingServer.TNonblockingServer(processor, transport, inputProtocolFactory=pfactory) + elif server_type == "TProcessPoolServer": + import signal + from thrift.server import TProcessPoolServer + server = TProcessPoolServer.TProcessPoolServer(processor, transport, tfactory, pfactory) + server.setNumWorkers(5) + + def set_alarm(): + def clean_shutdown(signum, frame): + for worker in server.workers: + if options.verbose > 0: + logging.info('Terminating worker: %s' % worker) + worker.terminate() + if options.verbose > 0: + logging.info('Requesting server to stop()') + try: + server.stop() + except Exception: + pass + signal.signal(signal.SIGALRM, clean_shutdown) + signal.alarm(4) + set_alarm() + else: + # look up server class dynamically to instantiate server + ServerClass = getattr(TServer, server_type) + server = ServerClass(processor, transport, tfactory, pfactory) + # enter server main loop + server.serve() + + +def exit_gracefully(signum, frame): + print("SIGINT received\n") + server.shutdown() # doesn't work properly, yet + sys.exit(0) + + +if __name__ == '__main__': + signal.signal(signal.SIGINT, exit_gracefully) + + parser = OptionParser() + parser.add_option('--libpydir', type='string', dest='libpydir', + help='include this directory to sys.path for locating library code') + parser.add_option('--genpydir', type='string', dest='genpydir', + default='gen-py', + help='include this directory to sys.path for locating generated code') + parser.add_option("--port", type="int", dest="port", + help="port number for server to listen on") + parser.add_option("--zlib", action="store_true", dest="zlib", + help="use zlib wrapper for compressed transport") + parser.add_option("--ssl", action="store_true", dest="ssl", + help="use SSL for encrypted transport") + 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('--protocol', dest="proto", type="string", + help="protocol to use, one of: accel, accelc, binary, compact, json, multi, multia, multiac, multic, multih, multij") + parser.add_option('--transport', dest="trans", type="string", + help="transport to use, one of: buffered, framed, http") + parser.add_option('--container-limit', dest='container_limit', type='int', default=None) + parser.add_option('--string-limit', dest='string_limit', type='int', default=None) + parser.set_defaults(port=9090, verbose=1, proto='binary', transport='buffered') + options, args = parser.parse_args() + + # Print TServer log to stdout so that the test-runner can redirect it to log files + logging.basicConfig(level=options.verbose) + + sys.path.insert(0, os.path.join(SCRIPT_DIR, options.genpydir)) + + from ThriftTest import ThriftTest, SecondService + from ThriftTest.ttypes import Xtruct, Xception, Xception2, Insanity + from thrift.Thrift import TException + from thrift.TMultiplexedProcessor import TMultiplexedProcessor + from thrift.transport import THeaderTransport + from thrift.transport import TTransport + from thrift.transport import TSocket + from thrift.transport import TZlibTransport + from thrift.protocol import TBinaryProtocol + from thrift.protocol import TCompactProtocol + from thrift.protocol import THeaderProtocol + from thrift.protocol import TJSONProtocol + from thrift.server import TServer, TNonblockingServer, THttpServer + + sys.exit(main(options)) diff --git a/src/jaegertracing/thrift/test/py/TestSocket.py b/src/jaegertracing/thrift/test/py/TestSocket.py new file mode 100755 index 000000000..619eb1002 --- /dev/null +++ b/src/jaegertracing/thrift/test/py/TestSocket.py @@ -0,0 +1,78 @@ +#!/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 thrift.transport import TSocket +import unittest +import time +import socket +import random + + +class TimeoutTest(unittest.TestCase): + def setUp(self): + for i in range(50): + try: + # find a port we can use + self.listen_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.port = random.randint(10000, 30000) + self.listen_sock.bind(('localhost', self.port)) + self.listen_sock.listen(5) + break + except Exception: + if i == 49: + raise + + def testConnectTimeout(self): + starttime = time.time() + + try: + leaky = [] + for i in range(100): + socket = TSocket.TSocket('localhost', self.port) + socket.setTimeout(10) + socket.open() + leaky.append(socket) + except Exception: + self.assert_(time.time() - starttime < 5.0) + + def testWriteTimeout(self): + starttime = time.time() + + try: + socket = TSocket.TSocket('localhost', self.port) + socket.setTimeout(10) + socket.open() + lsock = self.listen_sock.accept() + while True: + lsock.write("hi" * 100) + + except Exception: + self.assert_(time.time() - starttime < 5.0) + + +if __name__ == '__main__': + suite = unittest.TestSuite() + loader = unittest.TestLoader() + + suite.addTest(loader.loadTestsFromTestCase(TimeoutTest)) + + testRunner = unittest.TextTestRunner(verbosity=2) + testRunner.run(suite) diff --git a/src/jaegertracing/thrift/test/py/TestSyntax.py b/src/jaegertracing/thrift/test/py/TestSyntax.py new file mode 100755 index 000000000..dbe7975e2 --- /dev/null +++ b/src/jaegertracing/thrift/test/py/TestSyntax.py @@ -0,0 +1,24 @@ +#!/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. +# + +# Just import these generated files to make sure they are syntactically valid +from DebugProtoTest import EmptyService # noqa +from DebugProtoTest import Inherited # noqa diff --git a/src/jaegertracing/thrift/test/py/explicit_module/runtest.sh b/src/jaegertracing/thrift/test/py/explicit_module/runtest.sh new file mode 100755 index 000000000..6d7346283 --- /dev/null +++ b/src/jaegertracing/thrift/test/py/explicit_module/runtest.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# +# 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. +# + +rm -rf gen-py +../../../compiler/cpp/thrift --gen py test1.thrift || exit 1 +../../../compiler/cpp/thrift --gen py test2.thrift || exit 1 +../../../compiler/cpp/thrift --gen py test3.thrift && exit 1 # Fail since test3.thrift has python keywords +PYTHONPATH=./gen-py python -c 'import foo.bar.baz' || exit 1 +PYTHONPATH=./gen-py python -c 'import test2' || exit 1 +PYTHONPATH=./gen-py python -c 'import test1' &>/dev/null && exit 1 # Should fail. +cp -r gen-py simple +../../../compiler/cpp/thrift -r --gen py test2.thrift || exit 1 +PYTHONPATH=./gen-py python -c 'import test2' || exit 1 +diff -ur simple gen-py > thediffs +file thediffs | grep -s -q empty || exit 1 +rm -rf simple thediffs +echo 'All tests pass!' diff --git a/src/jaegertracing/thrift/test/py/explicit_module/test1.thrift b/src/jaegertracing/thrift/test/py/explicit_module/test1.thrift new file mode 100644 index 000000000..ec600d7d4 --- /dev/null +++ b/src/jaegertracing/thrift/test/py/explicit_module/test1.thrift @@ -0,0 +1,24 @@ +/* + * 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. + */ + +namespace py foo.bar.baz + +struct astruct { + 1: i32 how_unoriginal; +} diff --git a/src/jaegertracing/thrift/test/py/explicit_module/test2.thrift b/src/jaegertracing/thrift/test/py/explicit_module/test2.thrift new file mode 100644 index 000000000..68f9da4dd --- /dev/null +++ b/src/jaegertracing/thrift/test/py/explicit_module/test2.thrift @@ -0,0 +1,24 @@ +/* + * 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. + */ + +include "test1.thrift" + +struct another { + 1: test1.astruct something; +} diff --git a/src/jaegertracing/thrift/test/py/explicit_module/test3.thrift b/src/jaegertracing/thrift/test/py/explicit_module/test3.thrift new file mode 100644 index 000000000..154786bf8 --- /dev/null +++ b/src/jaegertracing/thrift/test/py/explicit_module/test3.thrift @@ -0,0 +1,25 @@ +/* + * 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. + */ + +namespace py validations + +struct from { + 1: i32 def; +} + diff --git a/src/jaegertracing/thrift/test/py/generate.cmake b/src/jaegertracing/thrift/test/py/generate.cmake new file mode 100644 index 000000000..4ed14cc52 --- /dev/null +++ b/src/jaegertracing/thrift/test/py/generate.cmake @@ -0,0 +1,36 @@ +macro(GENERATE FILENAME GENERATOR OUTPUTDIR) + file(MAKE_DIRECTORY ${MY_CURRENT_BINARY_DIR}/${OUTPUTDIR}) + execute_process(COMMAND ${THRIFTCOMPILER} --gen ${GENERATOR} -out ${MY_CURRENT_BINARY_DIR}/${OUTPUTDIR} ${FILENAME} + RESULT_VARIABLE CMD_RESULT) + if(CMD_RESULT) + message(FATAL_ERROR "Error generating ${FILENAME} with generator ${GENERATOR}") + endif() +endmacro(GENERATE) + +generate(${MY_PROJECT_DIR}/test/ThriftTest.thrift py gen-py-default) +generate(${MY_PROJECT_DIR}/test/ThriftTest.thrift py:slots gen-py-slots) +generate(${MY_PROJECT_DIR}/test/ThriftTest.thrift py:old_style gen-py-oldstyle) +generate(${MY_PROJECT_DIR}/test/ThriftTest.thrift py:no_utf8strings gen-py-no_utf8strings) +generate(${MY_PROJECT_DIR}/test/ThriftTest.thrift py:dynamic gen-py-dynamic) +generate(${MY_PROJECT_DIR}/test/ThriftTest.thrift py:dynamic,slots gen-py-dynamicslots) + +generate(${MY_PROJECT_DIR}/test/DebugProtoTest.thrift py gen-py-default) +generate(${MY_PROJECT_DIR}/test/DebugProtoTest.thrift py:slots gen-py-slots) +generate(${MY_PROJECT_DIR}/test/DebugProtoTest.thrift py:old_style gen-py-oldstyle) +generate(${MY_PROJECT_DIR}/test/DebugProtoTest.thrift py:no_utf8strings gen-py-no_utf8strings) +generate(${MY_PROJECT_DIR}/test/DebugProtoTest.thrift py:dynamic gen-py-dynamic) +generate(${MY_PROJECT_DIR}/test/DebugProtoTest.thrift py:dynamic,slots gen-py-dynamicslots) + +generate(${MY_PROJECT_DIR}/test/DoubleConstantsTest.thrift py gen-py-default) +generate(${MY_PROJECT_DIR}/test/DoubleConstantsTest.thrift py:slots gen-py-slots) +generate(${MY_PROJECT_DIR}/test/DoubleConstantsTest.thrift py:old_style gen-py-oldstyle) +generate(${MY_PROJECT_DIR}/test/DoubleConstantsTest.thrift py:no_utf8strings gen-py-no_utf8strings) +generate(${MY_PROJECT_DIR}/test/DoubleConstantsTest.thrift py:dynamic gen-py-dynamic) +generate(${MY_PROJECT_DIR}/test/DoubleConstantsTest.thrift py:dynamic,slots gen-py-dynamicslots) + +generate(${MY_PROJECT_DIR}/test/Recursive.thrift py gen-py-default) +generate(${MY_PROJECT_DIR}/test/Recursive.thrift py:slots gen-py-slots) +generate(${MY_PROJECT_DIR}/test/Recursive.thrift py:old_style gen-py-oldstyle) +generate(${MY_PROJECT_DIR}/test/Recursive.thrift py:no_utf8strings gen-py-no_utf8strings) +generate(${MY_PROJECT_DIR}/test/Recursive.thrift py:dynamic gen-py-dynamic) +generate(${MY_PROJECT_DIR}/test/Recursive.thrift py:dynamic,slots gen-py-dynamicslots) diff --git a/src/jaegertracing/thrift/test/py/setup.cfg b/src/jaegertracing/thrift/test/py/setup.cfg new file mode 100644 index 000000000..7da1f9608 --- /dev/null +++ b/src/jaegertracing/thrift/test/py/setup.cfg @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 100 diff --git a/src/jaegertracing/thrift/test/py/util.py b/src/jaegertracing/thrift/test/py/util.py new file mode 100644 index 000000000..c2b3f5cba --- /dev/null +++ b/src/jaegertracing/thrift/test/py/util.py @@ -0,0 +1,32 @@ +# +# 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. +# + +import glob +import os +import sys + +_SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__)) +_ROOT_DIR = os.path.dirname(os.path.dirname(_SCRIPT_DIR)) + + +def local_libpath(): + globdir = os.path.join(_ROOT_DIR, 'lib', 'py', 'build', 'lib.*') + for libpath in glob.glob(globdir): + if libpath.endswith('-%d.%d' % (sys.version_info[0], sys.version_info[1])): + return libpath |