http://git-wip-us.apache.org/repos/asf/qpid-interop-test/blob/514bac75/src/python/qpid-interop-test/jms_messages_test.py ---------------------------------------------------------------------- diff --git a/src/python/qpid-interop-test/jms_messages_test.py b/src/python/qpid-interop-test/jms_messages_test.py deleted file mode 100755 index 56cb203..0000000 --- a/src/python/qpid-interop-test/jms_messages_test.py +++ /dev/null @@ -1,511 +0,0 @@ -#!/usr/bin/env python - -""" -Module to test JMS message types across different APIs -""" - -# -# 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 argparse -import sys -import unittest - -from itertools import product -from json import dumps -from os import getenv, path -from sys import stdout - -import broker_properties -import shims -from proton import symbol -from test_type_map import TestTypeMap - - -# TODO: propose a sensible default when installation details are worked out -QPID_INTEROP_TEST_HOME = getenv('QPID_INTEROP_TEST_HOME') -if QPID_INTEROP_TEST_HOME is None: - print 'ERROR: Environment variable QPID_INTEROP_TEST_HOME is not set' - sys.exit(1) -MAVEN_REPO_PATH = getenv('MAVEN_REPO_PATH', path.join(getenv('HOME'), '.m2', 'repository')) - -class JmsMessageTypes(TestTypeMap): - """ - Class which contains all the described JMS message types and the test values to be used in testing. - """ - - COMMON_SUBMAP = { - 'boolean': ['True', - 'False'], - 'byte': ['-0x80', - '-0x1', - '0x0', - '0x7f'], - 'double': ['0x0000000000000000', # 0.0 - '0x8000000000000000', # -0.0 - '0x400921fb54442eea', # pi (3.14159265359) positive decimal - '0xc005bf0a8b145fcf', # -e (-2.71828182846) negative decimal - '0x0000000000000001', # Smallest positive denormalized number - '0x8000000000000001', # Smallest negative denormalized number - '0x000fffffffffffff', # Largest positive denormalized number - '0x8010000000000000', # Largest negative denormalized number - '0x7fefffffffffffff', # Largest positive normalized number - '0xffefffffffffffff', # Largest negative normalized number - '0x7ff0000000000000', # +Infinity - '0xfff0000000000000', # -Infinity - '0x7ff8000000000000'], # +NaN - 'float': ['0x00000000', # 0.0 - '0x80000000', # -0.0 - '0x40490fdb', # pi (3.14159265359) positive decimal - '0xc02df854', # -e (-2.71828182846) negative decimal - '0x00000001', # Smallest positive denormalized number - '0x80000001', # Smallest negative denormalized number - '0x007fffff', # Largest positive denormalized number - '0x807fffff', # Largest negative denormalized number - '0x00800000', # Smallest positive normalized number - '0x80800000', # Smallest negative normalized number - '0x7f7fffff', # Largest positive normalized number - '0xff7fffff', # Largest negative normalized number - #'0x7f800000', # +Infinity # PROTON-1149 - fails on RHEL7 - #'0xff800000', # -Infinity # PROTON-1149 - fails on RHEL7 - '0x7fc00000'], # +NaN - 'int': ['-0x80000000', - '-0x81', - '-0x80', - '-0x1', - '0x0', - '0x7f', - '0x80', - '0x7fffffff'], - 'long': ['-0x8000000000000000', - '-0x81', - '-0x80', - '-0x1', - '0x0', - '0x7f', - '0x80', - '0x7fffffffffffffff'], - 'short': ['-0x8000', - '-0x1', - '0x0', - '0x7fff'], - 'string': ['', - 'Hello, world', - '"Hello, world"', - "Charlie's \"peach\"", - 'Charlie\'s "peach"', - 'The quick brown fox jumped over the lazy dog 0123456789.'# * 100] - ] - } - - TYPE_ADDITIONAL_SUBMAP = { - 'bytes': [b'', - b'12345', - b'Hello, world', - b'\\x01\\x02\\x03\\x04\\x05abcde\\x80\\x81\\xfe\\xff', - b'The quick brown fox jumped over the lazy dog 0123456789.' #* 100], - ], - 'char': ['a', - 'Z', - '\x01', - '\x7f'], - } - - # The TYPE_SUBMAP defines test values for JMS message types that allow typed message content. Note that the - # types defined here are understood to be *Java* types and the stringified values are to be interpreted - # as the appropriate Java type by the send shim. - TYPE_SUBMAP = TestTypeMap.merge_dicts(COMMON_SUBMAP, TYPE_ADDITIONAL_SUBMAP) - - # Defines JMS headers that should be set by the send or publish API call of the client - HEADERS_PUBLISH_LIST = [ - 'JMS_DESTINATION', - 'JMS_DELIVERY_MODE', - 'JMS_EXPIRATION', - 'JMS_PRIORITY', - 'JMS_MESSAGEID', - 'JMS_TIMESTAMP', - ] - - # Defines JMS headers that are modified by the broker when he message is consumed - HEADERS_BROKER_LIST = [ - 'JMS_REDELIVERED', - ] - - # JMS headers that can be set by the client prior to send / publish, and that should be preserved byt he broker - HEADERS_MAP = { - 'JMS_CORRELATIONID_HEADER': {'string': ['Hello, world', - '"Hello, world"', - "Charlie's \"peach\"", - 'Charlie\'s "peach"', - 'The quick brown fox jumped over the lazy dog 0123456789.' * 10, - #'', # TODO: Re-enable when PROTON-1288 is fixed - ], - 'bytes': [b'12345\\x006789', - b'Hello, world', - b'"Hello, world"', - b'\\x01\\x02\\x03\\x04\\x05abcde\\x80\\x81\\xfe\\xff', - b'The quick brown fox jumped over the lazy dog 0123456789.' * 10, - #b'', # TODO: Re-enable when PROTON-1288 is fixed - ], - }, - 'JMS_REPLYTO_HEADER': {'queue': ['q_aaa', 'q_bbb'], - 'topic': ['t_aaa', 't_bbb'], - }, - 'JMS_TYPE_HEADER': {'string': ['Hello, world', - '"Hello, world"', - "Charlie's \"peach\"", - 'Charlie\'s "peach"', - 'The quick brown fox jumped over the lazy dog 0123456789.' * 10, - #'', # TODO: Re-enable when PROTON-1288 is fixed - ], - }, - } - - PROPERTIES_MAP = COMMON_SUBMAP # disabled until PROTON-1284 is fixed - - TYPE_MAP = { - 'JMS_MESSAGE_TYPE': {'none': [None]}, - 'JMS_BYTESMESSAGE_TYPE': TYPE_SUBMAP, - 'JMS_MAPMESSAGE_TYPE': TYPE_SUBMAP, - 'JMS_STREAMMESSAGE_TYPE': TYPE_SUBMAP, - 'JMS_TEXTMESSAGE_TYPE': {'text': ['', - 'Hello, world', - '"Hello, world"', - "Charlie's \"peach\"", - 'Charlie\'s "peach"', - 'The quick brown fox jumped over the lazy dog 0123456789.' * 10 - ] - }, - # TODO: Add Object messages when other (non-JMS clients) can generate Java class strings used in this message - # type - #'JMS_OBJECTMESSAGE_TYPE': { - # 'java.lang.Boolean': ['true', - # 'false'], - # 'java.lang.Byte': ['-128', - # '0', - # '127'], - # 'java.lang.Character': [u'a', - # u'Z'], - # 'java.lang.Double': ['0.0', - # '3.141592654', - # '-2.71828182846'], - # 'java.lang.Float': ['0.0', - # '3.14159', - # '-2.71828'], - # 'java.lang.Integer': ['-2147483648', - # '-129', - # '-128', - # '-1', - # '0', - # '127', - # '128', - # '2147483647'], - # 'java.lang.Long' : ['-9223372036854775808', - # '-129', - # '-128', - # '-1', - # '0', - # '127', - # '128', - # '9223372036854775807'], - # 'java.lang.Short': ['-32768', - # '-129', - # '-128', - # '-1', - # '0', - # '127', - # '128', - # '32767'], - # 'java.lang.String': [u'', - # u'Hello, world', - # u'"Hello, world"', - # u"Charlie's \"peach\"", - # u'Charlie\'s "peach"'] - # }, - } - - BROKER_SKIP = {} - - -class JmsMessageTypeTestCase(unittest.TestCase): - """ - Abstract base class for JMS message type test cases - """ - - def run_test(self, broker_addr, jms_message_type, test_values, msg_hdrs, msg_props, send_shim, receive_shim): - """ - Run this test by invoking the shim send method to send the test values, followed by the shim receive method - to receive the values. Finally, compare the sent values with the received values. - """ - queue_name = 'jms.queue.qpid-interop.jms_message_type_tests.%s.%s.%s' % (jms_message_type, send_shim.NAME, - receive_shim.NAME) - - # First create a map containing the numbers of expected mesasges for each JMS message type - num_test_values_map = {} - if len(test_values) > 0: - for index in test_values.keys(): - num_test_values_map[index] = len(test_values[index]) - # Create a map of flags which indicate to the receiver the details of some of the messages so that it can - # be correctly handled (as these require some prior knowledge) - flags_map = {} - if 'JMS_CORRELATIONID_HEADER' in msg_hdrs and 'bytes' in msg_hdrs['JMS_CORRELATIONID_HEADER']: - flags_map['JMS_CORRELATIONID_AS_BYTES'] = True - if 'JMS_REPLYTO_HEADER' in msg_hdrs and 'topic' in msg_hdrs['JMS_REPLYTO_HEADER']: - flags_map['JMS_REPLYTO_AS_TOPIC'] = True - # Start the receiver shim - receiver = receive_shim.create_receiver(broker_addr, queue_name, jms_message_type, - dumps([num_test_values_map, flags_map])) - receiver.start() - - # Start the send shim - sender = send_shim.create_sender(broker_addr, queue_name, jms_message_type, - dumps([test_values, msg_hdrs, msg_props])) - sender.start() - - # Wait for both shims to finish - sender.join_or_kill(shims.THREAD_TIMEOUT) - receiver.join_or_kill(shims.THREAD_TIMEOUT) - - # Process return string from sender - send_obj = sender.get_return_object() - if send_obj is not None: - if isinstance(send_obj, str) and len(send_obj) > 0: - self.fail('Send shim \'%s\':\n%s' % (send_shim.NAME, send_obj)) - else: - self.fail('Send shim \'%s\':\n%s' % str(send_obj)) - - # Process return string from receiver - receive_obj = receiver.get_return_object() - if receive_obj is None: - self.fail('JmsReceiver shim returned None') - else: - if isinstance(receive_obj, tuple): - if len(receive_obj) == 4: - return_jms_message_type, return_test_values, return_msg_hdrs, return_msg_props = receive_obj - self.assertEqual(return_jms_message_type, jms_message_type, - msg='JMS message type error:\n\n sent:%s\n\n received:%s' % \ - (jms_message_type, return_jms_message_type)) - self.assertEqual(return_test_values, test_values, - msg='JMS message body error:\n\n sent:%s\n\n received:%s' % \ - (test_values, return_test_values)) - self.assertEqual(return_msg_hdrs, msg_hdrs, - msg='JMS message headers error:\n\n sent:%s\n\n received:%s' % \ - (msg_hdrs, return_msg_hdrs)) - self.assertEqual(return_msg_props, msg_props, - msg='JMS message properties error:\n\n sent:%s\n\n received:%s' % \ - (msg_props, return_msg_props)) - else: - self.fail(str(receive_obj)) - - -def create_testcase_class(broker_name, types, broker_addr, jms_message_type, shim_product): - """ - Class factory function which creates new subclasses to JmsMessageTypeTestCase. Each call creates a single new - test case named and based on the parameters supplied to the method - """ - - def __repr__(self): - """Print the class name""" - return self.__class__.__name__ - - def add_test_method(cls, hdrs, props, send_shim, receive_shim): - """Function which creates a new test method in class cls""" - - @unittest.skipIf(types.skip_test(jms_message_type, broker_name), - types.skip_test_message(jms_message_type, broker_name)) - def inner_test_method(self): - self.run_test(self.broker_addr, - self.jms_message_type, - self.test_values, - hdrs[1], - props[1], - send_shim, - receive_shim) - - inner_test_method.__name__ = 'test_%s%s%s_%s->%s' % (jms_message_type[4:-5], hdrs[0], props[0], send_shim.NAME, - receive_shim.NAME) - setattr(cls, inner_test_method.__name__, inner_test_method) - - class_name = jms_message_type[4:-5].title() + 'TestCase' - class_dict = {'__name__': class_name, - '__repr__': __repr__, - '__doc__': 'Test case for JMS message type \'%s\'' % jms_message_type, - 'jms_message_type': jms_message_type, - 'broker_addr': broker_addr, - 'test_values': types.get_test_values(jms_message_type)} # tuple (tot_size, {...} - new_class = type(class_name, (JmsMessageTypeTestCase,), class_dict) - for send_shim, receive_shim in shim_product: - # Message without any headers or properties - add_test_method(new_class, ('', {}), ('', {}), send_shim, receive_shim) - - # Iterate through message headers, add one test per header value, no combinations - # Structure: {HEADER_NAME_1; {header_type_1: [val_1_1, val_1_2, val_1_3, ...], - # header_type_2: [val_2_1, val_2_2, val_2_3, ...], - # ... - # }, - # ... - # } - for msg_header, header_type_dict in types.HEADERS_MAP.iteritems(): - for header_type, header_val_list in header_type_dict.iteritems(): - hdr_val_cnt = 0 - for header_val in header_val_list: - hdr_val_cnt += 1 - test_name = '_hdr.%s.%s.%02d' % (msg_header[4:-7], header_type, hdr_val_cnt) - add_test_method(new_class, - (test_name, {msg_header: {header_type: header_val}}), - ('', {}), - send_shim, - receive_shim) - - # One message with all the headers together using type[0] and val[0] - all_hdrs = {} - for msg_header in types.HEADERS_MAP.iterkeys(): - header_type_dict = types.HEADERS_MAP[msg_header] - header_type, header_val_list = header_type_dict.iteritems().next() - header_val = header_val_list[0] - all_hdrs[msg_header] = {header_type: header_val} - add_test_method(new_class, ('_hdrs', all_hdrs), ('', {}), send_shim, receive_shim) - - # Properties tests disabled until PROTON-1284 fixed - ## Iterate through properties - ## Structure: {prop_type_1: [val_1_1, val_1_2, ...], - ## prop_type_2: [val_2_1, val_2_2, ...], - ## ... - ## } - #all_props = {} - #for prop_type, prop_val_list in types.PROPERTIES_MAP.iteritems(): - # prop_val_cnt = 0 - # for prop_val in prop_val_list: - # prop_val_cnt += 1 - # all_props['%s_%02d' % (prop_type, prop_val_cnt)] = {prop_type: prop_val} - - ## One message with all properties together - #add_test_method(new_class, ('', {}), ('_props', all_props), send_shim, receive_shim) - - ## One message with all headers and all properties together - #add_test_method(new_class, ('_hdrs', all_hdrs), ('_props', all_props), send_shim, receive_shim) - - return new_class - - -PROTON_CPP_RECEIVER_SHIM = path.join(QPID_INTEROP_TEST_HOME, 'shims', 'qpid-proton-cpp', 'build', 'jms_messages_test', - 'Receiver') -PROTON_CPP_SENDER_SHIM = path.join(QPID_INTEROP_TEST_HOME, 'shims', 'qpid-proton-cpp', 'build', 'jms_messages_test', - 'Sender') -PROTON_PYTHON_RECEIVER_SHIM = path.join(QPID_INTEROP_TEST_HOME, 'shims', 'qpid-proton-python', 'src', 'jms_messages_test', - 'Receiver.py') -PROTON_PYTHON_SENDER_SHIM = path.join(QPID_INTEROP_TEST_HOME, 'shims', 'qpid-proton-python', 'src', 'jms_messages_test', - 'Sender.py') -QIT_JMS_CLASSPATH_FILE = path.join(QPID_INTEROP_TEST_HOME, 'shims', 'qpid-jms', 'cp.txt') -with open(QIT_JMS_CLASSPATH_FILE, 'r') as classpath_file: - QIT_JMS_CLASSPATH = classpath_file.read() -QPID_JMS_RECEIVER_SHIM = 'org.apache.qpid.interop_test.jms_messages_test.Receiver' -QPID_JMS_SENDER_SHIM = 'org.apache.qpid.interop_test.jms_messages_test.Sender' - -# SHIM_MAP contains an instance of each client language shim that is to be tested as a part of this test. For -# every shim in this list, a test is dynamically constructed which tests it against itself as well as every -# other shim in the list. -# -# As new shims are added, add them into this map to have them included in the test cases. -SHIM_MAP = {shims.ProtonCppShim.NAME: shims.ProtonCppShim(PROTON_CPP_SENDER_SHIM, PROTON_CPP_RECEIVER_SHIM), - shims.ProtonPythonShim.NAME: shims.ProtonPythonShim(PROTON_PYTHON_SENDER_SHIM, PROTON_PYTHON_RECEIVER_SHIM), - shims.QpidJmsShim.NAME: shims.QpidJmsShim(QIT_JMS_CLASSPATH, QPID_JMS_SENDER_SHIM, QPID_JMS_RECEIVER_SHIM), - } - -# TODO: Complete the test options to give fine control over running tests -class TestOptions(object): - """ - Class controlling command-line arguments used to control the test. - """ - def __init__(self,): - parser = argparse.ArgumentParser(description='Qpid-interop AMQP client interoparability test suite ' - 'for JMS message types') - parser.add_argument('--broker', action='store', default='localhost:5672', metavar='BROKER:PORT', - help='Broker against which to run test suite.') -# test_group = parser.add_mutually_exclusive_group() -# test_group.add_argument('--include-test', action='append', metavar='TEST-NAME', -# help='Name of test to include') -# test_group.add_argument('--exclude-test', action='append', metavar='TEST-NAME', -# help='Name of test to exclude') -# type_group = test_group.add_mutually_exclusive_group() -# type_group.add_argument('--include-type', action='append', metavar='AMQP-TYPE', -# help='Name of AMQP type to include. Supported types:\n%s' % -# sorted(JmsMessageTypes.TYPE_MAP.keys())) - parser.add_argument('--exclude-type', action='append', metavar='JMS-MESSAGE-TYPE', - help='Name of JMS message type to exclude. Supported types:\n%s' % - sorted(JmsMessageTypes.TYPE_MAP.keys())) -# shim_group = test_group.add_mutually_exclusive_group() -# shim_group.add_argument('--include-shim', action='append', metavar='SHIM-NAME', -# help='Name of shim to include. Supported shims:\n%s' % sorted(SHIM_MAP.keys())) - parser.add_argument('--exclude-shim', action='append', metavar='SHIM-NAME', - help='Name of shim to exclude. Supported shims:\n%s' % sorted(SHIM_MAP.keys())) - self.args = parser.parse_args() - - -#--- Main program start --- - -if __name__ == '__main__': - ARGS = TestOptions().args - #print 'ARGS:', ARGS # debug - - # Connect to broker to find broker type - CONNECTION_PROPS = broker_properties.getBrokerProperties(ARGS.broker) - if CONNECTION_PROPS is None: - print 'WARNING: Unable to get connection properties - unknown broker' - BROKER = 'unknown' - else: - BROKER = CONNECTION_PROPS[symbol(u'product')] if symbol(u'product') in CONNECTION_PROPS \ - else '<product not found>' - BROKER_VERSION = CONNECTION_PROPS[symbol(u'version')] if symbol(u'version') in CONNECTION_PROPS \ - else '<version not found>' - BROKER_PLATFORM = CONNECTION_PROPS[symbol(u'platform')] if symbol(u'platform') in CONNECTION_PROPS \ - else '<platform not found>' - print 'Test Broker: %s v.%s on %s' % (BROKER, BROKER_VERSION, BROKER_PLATFORM) - print - stdout.flush() - - TYPES = JmsMessageTypes() - - # TEST_CASE_CLASSES is a list that collects all the test classes that are constructed. One class is constructed - # per AMQP type used as the key in map JmsMessageTypes.TYPE_MAP. - TEST_CASE_CLASSES = [] - - # TEST_SUITE is the final suite of tests that will be run and which contains all the dynamically created - # type classes, each of which contains a test for the combinations of client shims - TEST_SUITE = unittest.TestSuite() - - # Remove shims excluded from the command-line - if ARGS.exclude_shim is not None: - for shim in ARGS.exclude_shim: - SHIM_MAP.pop(shim) - # Create test classes dynamically - for jmt in sorted(TYPES.get_type_list()): - if ARGS.exclude_type is None or jmt not in ARGS.exclude_type: - test_case_class = create_testcase_class(BROKER, - TYPES, - ARGS.broker, - jmt, - product(SHIM_MAP.values(), repeat=2)) - TEST_CASE_CLASSES.append(test_case_class) - TEST_SUITE.addTest(unittest.makeSuite(test_case_class)) - - # Finally, run all the dynamically created tests - RES = unittest.TextTestRunner(verbosity=2).run(TEST_SUITE) - if not RES.wasSuccessful(): - sys.exit(1)
http://git-wip-us.apache.org/repos/asf/qpid-interop-test/blob/514bac75/src/python/qpid-interop-test/shims.py ---------------------------------------------------------------------- diff --git a/src/python/qpid-interop-test/shims.py b/src/python/qpid-interop-test/shims.py deleted file mode 100644 index 5e94d2d..0000000 --- a/src/python/qpid-interop-test/shims.py +++ /dev/null @@ -1,229 +0,0 @@ -""" -Module containing worker thread classes and shims -""" -# -# 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 json import loads -from os import getenv, getpgid, killpg, path, setsid -from signal import SIGKILL, SIGTERM -from subprocess import Popen, PIPE, CalledProcessError -from sys import stdout -from threading import Thread -from time import sleep - - -THREAD_TIMEOUT = 10.0 # seconds to complete before join is forced - - -class ShimWorkerThread(Thread): - """Parent class for shim worker threads and return a string once the thread has ended""" - def __init__(self, thread_name): - super(ShimWorkerThread, self).__init__(name=thread_name) - self.arg_list = [] - self.return_obj = None - self.proc = None - - def get_return_object(self): - """Get the return object from the completed thread""" - return self.return_obj - - def join_or_kill(self, timeout): - """ - Wait for thread to join after timeout (seconds). If still alive, it is then terminated, then if still alive, - killed - """ - self.join(timeout) - if self.is_alive(): - if self.proc is not None: - if self._terminate_pg_loop(): - if self._kill_pg_loop(): - print '\n ERROR: Thread %s (pid=%d) alive after kill' % (self.name, self.proc.pid) - else: - print 'Killed' - stdout.flush() - else: - print 'Terminated' - stdout.flush() - else: - print 'ERROR: shims.join_or_kill(): Process joined and is alive, yet proc is None.' - - def _terminate_pg_loop(self, num_attempts=2, wait_time=2): - cnt = 0 - while cnt < num_attempts and self.is_alive(): - cnt += 1 - print '\n Thread %s (pid=%d) alive after timeout, terminating (try #%d)...' % (self.name, self.proc.pid, - cnt), - stdout.flush() - killpg(getpgid(self.proc.pid), SIGTERM) - sleep(wait_time) - return self.is_alive() - - def _kill_pg_loop(self, num_attempts=2, wait_time=5): - cnt = 0 - while cnt < num_attempts and self.is_alive(): - cnt += 1 - print '\n Thread %s (pid=%d) alive after terminate, killing (try #%d)...' % (self.name, self.proc.pid, - cnt), - stdout.flush() - killpg(getpgid(self.proc.pid), SIGKILL) - sleep(wait_time) - return self.is_alive() - - -class Sender(ShimWorkerThread): - """Sender class for multi-threaded send""" - def __init__(self, use_shell_flag, send_shim_args, broker_addr, queue_name, msg_type, json_test_str): - super(Sender, self).__init__('sender_thread_%s' % queue_name) - if send_shim_args is None: - print 'ERROR: Sender: send_shim_args == None' - self.use_shell_flag = use_shell_flag - self.arg_list.extend(send_shim_args) - self.arg_list.extend([broker_addr, queue_name, msg_type, json_test_str]) - - def run(self): - """Thread starts here""" - try: - #print '\n>>>', self.arg_list # DEBUG - useful to see command-line sent to shim - self.proc = Popen(self.arg_list, stdout=PIPE, stderr=PIPE, shell=self.use_shell_flag, preexec_fn=setsid) - (stdoutdata, stderrdata) = self.proc.communicate() - if len(stdoutdata) > 0 or len(stderrdata) > 0: - self.return_obj = (stdoutdata, stderrdata) - except CalledProcessError as exc: - self.return_obj = str(exc) + '\n\nOutput:\n' + exc.output - - -class Receiver(ShimWorkerThread): - """Receiver class for multi-threaded receive""" - def __init__(self, receive_shim_args, broker_addr, queue_name, msg_type, json_test_str): - super(Receiver, self).__init__('receiver_thread_%s' % queue_name) - if receive_shim_args is None: - print 'ERROR: Receiver: receive_shim_args == None' - self.arg_list.extend(receive_shim_args) - self.arg_list.extend([broker_addr, queue_name, msg_type, json_test_str]) - - def run(self): - """Thread starts here""" - try: - #print '\n>>>', self.arg_list # DEBUG - useful to see command-line sent to shim - self.proc = Popen(self.arg_list, stdout=PIPE, stderr=PIPE, preexec_fn=setsid) - (stdoutdata, stderrdata) = self.proc.communicate() - if len(stderrdata) > 0: - self.return_obj = (stdoutdata, stderrdata) - else: - #print '<<<', stdoutdata # DEBUG - useful to see text received from shim - str_tvl = stdoutdata.split('\n')[0:-1] # remove trailing \n - #if len(str_tvl) == 1: - # self.return_obj = output - if len(str_tvl) == 2: # AMQP type test return - self.return_obj = loads(str_tvl[1]) - elif len(str_tvl) == 4: # JMS test return - self.return_obj = (str_tvl[0], loads(str_tvl[1]), loads(str_tvl[2]), loads(str_tvl[3])) - else: # Make a single line of all the bits and return that - #self.return_obj = loads("".join(str_tvl[1:])) - self.return_obj = stdoutdata - except CalledProcessError as exc: - self.return_obj = str(exc) + '\n\n' + exc.output - -class Shim(object): - """Abstract shim class, parent of all shims.""" - NAME = None - def __init__(self, sender_shim, receiver_shim): - self.sender_shim = sender_shim - self.receiver_shim = receiver_shim - self.send_params = None - self.receive_params = None - self.use_shell_flag = False - - def create_sender(self, broker_addr, queue_name, msg_type, json_test_str): - """Create a new sender instance""" - return Sender(self.use_shell_flag, self.send_params, broker_addr, queue_name, msg_type, json_test_str) - - def create_receiver(self, broker_addr, queue_name, msg_type, json_test_str): - """Create a new receiver instance""" - return Receiver(self.receive_params, broker_addr, queue_name, msg_type, json_test_str) - -class ProtonPythonShim(Shim): - """Shim for qpid-proton Python client""" - NAME = 'ProtonPython' - def __init__(self, sender_shim, receiver_shim): - super(ProtonPythonShim, self).__init__(sender_shim, receiver_shim) - self.send_params = [self.sender_shim] - self.receive_params = [self.receiver_shim] - - -class ProtonCppShim(Shim): - """Shim for qpid-proton C++ client""" - NAME = 'ProtonCpp' - def __init__(self, sender_shim, receiver_shim): - super(ProtonCppShim, self).__init__(sender_shim, receiver_shim) - self.send_params = [self.sender_shim] - self.receive_params = [self.receiver_shim] - - -class QpidJmsShim(Shim): - """Shim for qpid-jms JMS client""" - NAME = 'QpidJms' - - # Installed versions - # TODO: Automate this - it gets out of date quickly - # Maven works out all the deps, should use that - QPID_JMS_SHIM_VER = '0.1.0-SNAPSHOT' - QPID_JMS_VER = '0.20.0-SNAPSHOT' - QPID_PROTON_J_VER = '0.15.0-SNAPSHOT' - JMS_API_VER = '1.1.1' - LOGGER_API_VER = '1.7.21' - LOGGER_IMPL_VER = '1.7.21' - NETTY_VER = '4.0.40.Final' - - # Classpath components - #QPID_INTEROP_TEST_SHIM_JAR = path.join(QPID_INTEROP_TEST_HOME, 'shims', 'qpid-jms', 'target', 'qpid-jms-shim.jar') - MAVEN_REPO_PATH = path.join(getenv('HOME'), '.m2', 'repository') - JMS_API_JAR = path.join(MAVEN_REPO_PATH, 'org', 'apache', 'geronimo', 'specs', 'geronimo-jms_1.1_spec', JMS_API_VER, - 'geronimo-jms_1.1_spec-%s.jar' % JMS_API_VER) - JMS_IMPL_JAR = path.join(MAVEN_REPO_PATH, 'org', 'apache', 'qpid', 'qpid-jms-client', QPID_JMS_VER, - 'qpid-jms-client-%s.jar' % QPID_JMS_VER) - LOGGER_API_JAR = path.join(MAVEN_REPO_PATH, 'org', 'slf4j', 'slf4j-api', LOGGER_API_VER, - 'slf4j-api-%s.jar' % LOGGER_API_VER) - LOGGER_IMPL_JAR = path.join(MAVEN_REPO_PATH, 'org', 'slf4j', 'slf4j-nop', LOGGER_IMPL_VER, - 'slf4j-nop-%s.jar' % LOGGER_IMPL_VER) - PROTON_J_JAR = path.join(MAVEN_REPO_PATH, 'org', 'apache', 'qpid', 'proton-j', QPID_PROTON_J_VER, - 'proton-j-%s.jar' % QPID_PROTON_J_VER) - NETTY_JAR = path.join(MAVEN_REPO_PATH, 'io', 'netty', 'netty-all', NETTY_VER, 'netty-all-%s.jar' % NETTY_VER) - QPID_JMS_SHIM_JAR = path.join(MAVEN_REPO_PATH, 'org', 'apache', 'qpid', 'qpid-interop-test-jms-shim', - QPID_JMS_SHIM_VER, 'qpid-interop-test-jms-shim-%s.jar' % QPID_JMS_SHIM_VER) - - JAVA_HOME = getenv('JAVA_HOME', '/usr/bin') # Default only works in Linux - JAVA_EXEC = path.join(JAVA_HOME, 'java') - - def __init__(self, dependency_class_path, sender_shim, receiver_shim): - super(QpidJmsShim, self).__init__(sender_shim, receiver_shim) - self.dependency_class_path = dependency_class_path - self.send_params = [self.JAVA_EXEC, '-cp', self.get_java_class_path(), self.sender_shim] - self.receive_params = [self.JAVA_EXEC, '-cp', self.get_java_class_path(), self.receiver_shim] - - def get_java_class_path(self): - """Method to construct and return the Java class path necessary to run the shim""" - return ':'.join([self.QPID_JMS_SHIM_JAR, self.dependency_class_path]) - # self.JMS_API_JAR, - # self.JMS_IMPL_JAR, - # self.LOGGER_API_JAR, - # self.LOGGER_IMPL_JAR, - # self.PROTON_J_JAR, - # self.NETTY_JAR]) http://git-wip-us.apache.org/repos/asf/qpid-interop-test/blob/514bac75/src/python/qpid-interop-test/test_type_map.py ---------------------------------------------------------------------- diff --git a/src/python/qpid-interop-test/test_type_map.py b/src/python/qpid-interop-test/test_type_map.py deleted file mode 100644 index b571ac8..0000000 --- a/src/python/qpid-interop-test/test_type_map.py +++ /dev/null @@ -1,85 +0,0 @@ -""" -Module containing Error classes for interop testing -""" -# -# 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. -# - -class TestTypeMap(object): - """ - Class which contains all the described types and the test values to be used in testing against those types. - """ - - # TYPE_MAP: Map containing all described types as the indecies, and a list of values to be used in testing - # that type as a list of values. - # - # Format: {'type_1' : [val_1_1, val_1_2, ...], - # 'type_2' : [val_2_1, val_2_2, ...], - # ... - # } - TYPE_MAP = {} - - # BROKER_SKIP: For know broker issues where a type would cause a test to fail or hang, - # entries in BROKER_SKIP will cause the test to be skipped with a message. - # This is a map containing AMQP types as a key, and a list of brokers for which this - # type should be skipped. - # Format: {'jms_msg_type_1' : {'broker_1' : 'skip msg for broker_1', - # 'broker_2' : 'skip msg for broker_2', - # ... - # }, - # 'jms_msg_type_2' : {'broker_1' : 'skip msg for broker_1', - # 'broker_2' : 'skip msg for broker_2', - # ... - # }, - # ... - # } - # where broker_1, broker_2, ... are broker product names as defined by the - # connection property string it returns. - BROKER_SKIP = {} - - def __init__(self): - pass - - def get_type_list(self): - """Return a list of types which this test suite supports""" - return self.TYPE_MAP.keys() - - def get_test_values(self, test_type): - """Return test values to use when testing the supplied type.""" - if test_type not in self.TYPE_MAP.keys(): - return None - return self.TYPE_MAP[test_type] - - def skip_test_message(self, test_type, broker_name): - """Return the message to use if a test is skipped""" - if test_type in self.BROKER_SKIP.keys(): - if broker_name in self.BROKER_SKIP[test_type]: - return str(self.BROKER_SKIP[test_type][broker_name]) - return None - - def skip_test(self, test_type, broker_name): - """Return boolean True if test should be skipped""" - return test_type in self.BROKER_SKIP.keys() and \ - broker_name in self.BROKER_SKIP[test_type] - - @staticmethod - def merge_dicts(*dict_args): - res = {} - for d in dict_args: - res.update(d) - return res http://git-wip-us.apache.org/repos/asf/qpid-interop-test/blob/514bac75/src/python/qpid_interop_test/.gitignore ---------------------------------------------------------------------- diff --git a/src/python/qpid_interop_test/.gitignore b/src/python/qpid_interop_test/.gitignore new file mode 100644 index 0000000..835fca3 --- /dev/null +++ b/src/python/qpid_interop_test/.gitignore @@ -0,0 +1,4 @@ +/interop_test_errors.pyc +/shim_utils.pyc +/broker_properties.pyc +/test_type_map.pyc http://git-wip-us.apache.org/repos/asf/qpid-interop-test/blob/514bac75/src/python/qpid_interop_test/__init__.py ---------------------------------------------------------------------- diff --git a/src/python/qpid_interop_test/__init__.py b/src/python/qpid_interop_test/__init__.py new file mode 100644 index 0000000..70204e4 --- /dev/null +++ b/src/python/qpid_interop_test/__init__.py @@ -0,0 +1,23 @@ +# +# 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 broker_properties +import interop_test_errors +import shims +import test_type_map http://git-wip-us.apache.org/repos/asf/qpid-interop-test/blob/514bac75/src/python/qpid_interop_test/amqp_types_test.py ---------------------------------------------------------------------- diff --git a/src/python/qpid_interop_test/amqp_types_test.py b/src/python/qpid_interop_test/amqp_types_test.py new file mode 100755 index 0000000..bde21f3 --- /dev/null +++ b/src/python/qpid_interop_test/amqp_types_test.py @@ -0,0 +1,443 @@ +#!/usr/bin/env python + +""" +Module to test AMQP primitive types across different APIs +""" + +# +# 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 argparse +import sys +import unittest + +from itertools import product +from json import dumps +from os import getenv, path +from time import mktime, time +from uuid import UUID, uuid4 + +from proton import symbol +import qpid_interop_test.broker_properties +import qpid_interop_test.shims +from qpid_interop_test.test_type_map import TestTypeMap + +# TODO: propose a sensible default when installation details are worked out +QPID_INTEROP_TEST_HOME = getenv('QPID_INTEROP_TEST_HOME') +if QPID_INTEROP_TEST_HOME is None: + print 'ERROR: Environment variable QPID_INTEROP_TEST_HOME is not set' + sys.exit(1) + + +class AmqpPrimitiveTypes(TestTypeMap): + """ + Class which contains all the described AMQP primitive types and the test values to be used in testing. + """ + + TYPE_MAP = { + 'null': ['None'], + 'boolean': ['True', + 'False'], + 'ubyte': ['0x0', + '0x7f', + '0x80', + '0xff'], + 'ushort': ['0x0', + '0x7fff', + '0x8000', + '0xffff'], + 'uint': ['0x0', + '0x7fffffff', + '0x80000000', + '0xffffffff'], + 'ulong': ['0x0', + '0x1', + '0xff', + '0x100', + '0x7fffffffffffffff', + '0x8000000000000000', + '0xffffffffffffffff'], + 'byte': ['-0x80', + '-0x1', + '0x0', + '0x7f'], + 'short': ['-0x8000', + '-0x1', + '0x0', + '0x7fff'], + 'int': ['-0x80000000', + '-0x1', + '0x0', + '0x7fffffff'], + 'long': ['-0x8000000000000000', + '-0x81', + '-0x80', + '-0x1', + '0x0', + '0x7f', + '0x80', + '0x7fffffffffffffff'], + # float and double: Because of difficulty with rounding of floating point numbers, we use the binary + # representation instead which should be exact when comparing sent and received values. + 'float': ['0x00000000', # 0.0 + '0x80000000', # -0.0 + '0x40490fdb', # pi (3.14159265359) positive decimal + '0xc02df854', # -e (-2.71828182846) negative decimal + '0x00000001', # Smallest positive denormalized number + '0x80000001', # Smallest negative denormalized number + '0x007fffff', # Largest positive denormalized number + '0x807fffff', # Largest negative denormalized number + '0x00800000', # Smallest positive normalized number + '0x80800000', # Smallest negative normalized number + '0x7f7fffff', # Largest positive normalized number + '0xff7fffff', # Largest negative normalized number + #'0x7f800000', # +Infinity # PROTON-1149 - fails on RHEL7 + #'0xff800000', # -Infinity # PROTON-1149 - fails on RHEL7 + '0x7fc00000', # +NaN + '0xffc00000'], # -NaN + 'double': ['0x0000000000000000', # 0.0 + '0x8000000000000000', # -0.0 + '0x400921fb54442eea', # pi (3.14159265359) positive decimal + '0xc005bf0a8b145fcf', # -e (-2.71828182846) negative decimal + '0x0000000000000001', # Smallest positive denormalized number + '0x8000000000000001', # Smallest negative denormalized number + '0x000fffffffffffff', # Largest positive denormalized number + '0x800fffffffffffff', # Largest negative denormalized number + '0x0010000000000000', # Smallest positive normalized number + '0x8010000000000000', # Smallest negative normalized number + '0x7fefffffffffffff', # Largest positive normalized number + '0xffefffffffffffff', # Largest negative normalized number + '0x7ff0000000000000', # +Infinity + '0xfff0000000000000', # -Infinity + '0x7ff8000000000000', # +NaN + '0xfff8000000000000'], # -NaN + # decimal32, decimal64, decimal128: + # Until more formal support for decimal32, decimal64 and decimal128 are included in Python, we use + # a hex format for basic tests, and treat the data as a binary blob. + 'decimal32': ['0x00000000', + '0x40490fdb', + '0xc02df854', + '0xff7fffff'], + 'decimal64': ['0x0000000000000000', + '0x400921fb54442eea', + '0xc005bf0a8b145fcf', + '0xffefffffffffffff'], + 'decimal128': ['0x00000000000000000000000000000000', + '0xff0102030405060708090a0b0c0d0e0f'], + 'char': [u'a', + u'Z', + u'0x1', + u'0x7f', + u'0x16b5', # Rune 'G' + u'0x10ffff'], + # timestamp: Must be in milliseconds since the Unix epoch + 'timestamp': ['0x0', + '0x%x' % int(mktime((2000, 1, 1, 0, 0, 0, 5, 1, 0))*1000), + '0x%x' % int(time()*1000) + ], + 'uuid': [str(UUID(int=0x0)), + str(UUID('00010203-0405-0607-0809-0a0b0c0d0e0f')), + str(uuid4())], + 'binary': [bytes(), + bytes(12345), + b'Hello, world!', + b'\\x01\\x02\\x03\\x04\\x05abcde\\x80\\x81\\xfe\\xff', + b'The quick brown fox jumped over the lazy dog 0123456789.' * 100 + ], + # strings must be unicode to comply with AMQP spec + 'string': [u'', + u'Hello, world!', + u'"Hello, world!"', + u"Charlie's peach", + u'The quick brown fox jumped over the lazy dog 0123456789.' * 100 + ], + 'symbol': ['', + 'myDomain.123', + 'domain.0123456789.' * 100], + 'list': [[], + ['ubyte:1', 'int:-2', 'float:3.14'], + ['string:a', 'string:b', 'string:c'], + ['ulong:12345', + 'timestamp:%d' % (time()*1000), + 'short:-2500', + 'uuid:%s' % uuid4(), + 'symbol:a.b.c', + 'none:', + 'decimal64:0x400921fb54442eea' + ], + [[], + 'none', + ['ubyte:1', 'ubyte:2', 'ubyte:3'], + 'boolean:True', + 'boolean:False', + {'string:hello': 'long:1234', 'string:goodbye': 'boolean:True'} + ], + [[], [[], [[], [], []], []], []], + ['short:0', + 'short:1', + 'short:2', + 'short:3', + 'short:4', + 'short:5', + 'short:6', + 'short:7', + 'short:8', + 'short:9'] * 10 + ], + 'map': [ + # Enpty map + {}, + # Map with string keys + {'string:one': 'ubyte:1', + 'string:two': 'ushort:2'}, + # Map with other AMQP simple types as keys + {'none:': 'string:None', + 'string:None': 'none:', + 'string:One': 'long:-1234567890', + 'short:2': 'int:2', + 'boolean:True': 'string:True', + 'string:False': 'boolean:False', + #['string:AAA', 'ushort:5951']: 'string:list value', + #{'byte:-55': 'ubyte:200', + # 'boolean:True': 'string:Hello, world!'}: 'symbol:map.value', + #'string:list': [], + 'string:map': {'char:A': 'int:1', + 'char:B': 'int:2'}}, + ], + # TODO: Support all AMQP types in array (including keys) + #'array': [[], + # [1, 2, 3], + # ['Hello', 'world'], + # [[1, 2, 3], + # ['a', 'b', 'c'], + # [2.3, 3.4, 4,5], + # [True, False, True, True]] + # ] + } + + # This section contains tests that should be skipped because of know issues that would cause the test to fail. + # As the issues are resolved, these should be removed. + BROKER_SKIP = {'null': {'ActiveMQ': 'Null type not sent in Proton Python binding: PROTON-1091', + 'qpid-cpp': 'Null type not sent in Proton Python binding: PROTON-1091',}, + 'decimal32': {'ActiveMQ': 'decimal32 and decimal64 sent byte reversed: PROTON-1160', + 'qpid-cpp': 'decimal32 not supported on qpid-cpp broker: QPIDIT-5, QPID-6328', + 'apache-activemq-artemis': 'decimal32 and decimal64 sent byte reversed: PROTON-1160', + 'qpid-dispatch-router': 'decimal32 and decimal64 sent byte reversed: PROTON-1160'}, + 'decimal64': {'ActiveMQ': 'decimal32 and decimal64 sent byte reversed: PROTON-1160', + 'qpid-cpp': 'decimal64 not supported on qpid-cpp broker: QPIDIT-6, QPID-6328', + 'apache-activemq-artemis': 'decimal32 and decimal64 sent byte reversed: PROTON-1160', + 'qpid-dispatch-router': 'decimal32 and decimal64 sent byte reversed: PROTON-1160'}, + 'decimal128': {'qpid-cpp': 'decimal128 not supported on qpid-cpp broker: QPIDIT-3, QPID-6328',}, + 'char': {'qpid-cpp': 'char not supported on qpid-cpp broker: QPIDIT-4, QPID-6328', + 'apache-activemq-artemis': 'char types > 16 bits truncated on Artemis: ENTMQ-1685'}, + 'float': {'apache-activemq-artemis': '-NaN is stripped of its sign: ENTMQ-1686'}, + 'double': {'apache-activemq-artemis': '-NaN is stripped of its sign: ENTMQ-1686'}, + } + + +class AmqpTypeTestCase(unittest.TestCase): + """ + Abstract base class for AMQP Type test cases + """ + + def run_test(self, broker_addr, amqp_type, test_value_list, send_shim, receive_shim): + """ + Run this test by invoking the shim send method to send the test values, followed by the shim receive method + to receive the values. Finally, compare the sent values with the received values. + """ + if len(test_value_list) > 0: + # TODO: When Artemis can support it (in the next release), revert the queue name back to 'qpid-interop...' + # Currently, Artemis only supports auto-create queues for JMS, and the queue name must be prefixed by + # 'jms.queue.' + #queue_name = 'qpid-interop.simple_type_tests.%s.%s.%s' % (amqp_type, send_shim.NAME, receive_shim.NAME) + queue_name = 'jms.queue.qpid-interop.simple_type_tests.%s.%s.%s' % \ + (amqp_type, send_shim.NAME, receive_shim.NAME) + + # Start the receive shim first (for queueless brokers/dispatch) + receiver = receive_shim.create_receiver(broker_addr, queue_name, amqp_type, + str(len(test_value_list))) + receiver.start() + + # Start the send shim + sender = send_shim.create_sender(broker_addr, queue_name, amqp_type, + dumps(test_value_list)) + sender.start() + + # Wait for both shims to finish + sender.join_or_kill(qpid_interop_test.shims.THREAD_TIMEOUT) + receiver.join_or_kill(qpid_interop_test.shims.THREAD_TIMEOUT) + + # Process return string from sender + send_obj = sender.get_return_object() + if send_obj is not None: + if isinstance(send_obj, str) and len(send_obj) > 0: + self.fail('Send shim \'%s\':\n%s' % (send_shim.NAME, send_obj)) + else: + self.fail('Sender error: %s' % str(send_obj)) + + # Process return string from receiver + receive_obj = receiver.get_return_object() + if isinstance(receive_obj, tuple): + if len(receive_obj) == 2: + return_amqp_type, return_test_value_list = receive_obj + self.assertEqual(return_amqp_type, amqp_type, + msg='AMQP type error:\n\n sent:%s\n\n received:%s' % \ + (amqp_type, return_amqp_type)) + self.assertEqual(return_test_value_list, test_value_list, msg='\n sent:%s\nreceived:%s' % \ + (test_value_list, return_test_value_list)) + else: + self.fail('Received incorrect tuple format: %s' % str(receive_obj)) + else: + self.fail('Received non-tuple: %s' % str(receive_obj)) + +def create_testcase_class(broker_name, types, broker_addr, amqp_type, shim_product): + """ + Class factory function which creates new subclasses to AmqpTypeTestCase. + """ + + def __repr__(self): + """Print the class name""" + return self.__class__.__name__ + + def add_test_method(cls, send_shim, receive_shim): + """Function which creates a new test method in class cls""" + + @unittest.skipIf(types.skip_test(amqp_type, broker_name), + types.skip_test_message(amqp_type, broker_name)) + def inner_test_method(self): + self.run_test(self.broker_addr, self.amqp_type, self.test_value_list, send_shim, receive_shim) + + inner_test_method.__name__ = 'test_%s_%s->%s' % (amqp_type, send_shim.NAME, receive_shim.NAME) + setattr(cls, inner_test_method.__name__, inner_test_method) + + class_name = amqp_type.title() + 'TestCase' + class_dict = {'__name__': class_name, + '__repr__': __repr__, + '__doc__': 'Test case for AMQP 1.0 simple type \'%s\'' % amqp_type, + 'amqp_type': amqp_type, + 'broker_addr': broker_addr, + 'test_value_list': types.get_test_values(amqp_type)} + new_class = type(class_name, (AmqpTypeTestCase,), class_dict) + for send_shim, receive_shim in shim_product: + add_test_method(new_class, send_shim, receive_shim) + return new_class + + +# SHIM_MAP contains an instance of each client language shim that is to be tested as a part of this test. For +# every shim in this list, a test is dynamically constructed which tests it against itself as well as every +# other shim in the list. +# +# As new shims are added, add them into this map to have them included in the test cases. +PROTON_CPP_RECEIVER_SHIM = path.join(QPID_INTEROP_TEST_HOME, 'shims', 'qpid-proton-cpp', 'build', 'amqp_types_test', + 'Receiver') +PROTON_CPP_SENDER_SHIM = path.join(QPID_INTEROP_TEST_HOME, 'shims', 'qpid-proton-cpp', 'build', 'amqp_types_test', + 'Sender') +PROTON_PYTHON_RECEIVER_SHIM = path.join(QPID_INTEROP_TEST_HOME, 'shims', 'qpid-proton-python', 'src', 'amqp_types_test', + 'Receiver.py') +PROTON_PYTHON_SENDER_SHIM = path.join(QPID_INTEROP_TEST_HOME, 'shims', 'qpid-proton-python', 'src', 'amqp_types_test', + 'Sender.py') + +SHIM_MAP = {qpid_interop_test.shims.ProtonCppShim.NAME: \ + qpid_interop_test.shims.ProtonCppShim(PROTON_CPP_SENDER_SHIM, PROTON_CPP_RECEIVER_SHIM), + qpid_interop_test.shims.ProtonPythonShim.NAME: \ + qpid_interop_test.shims.ProtonPythonShim(PROTON_PYTHON_SENDER_SHIM, PROTON_PYTHON_RECEIVER_SHIM), + } + + +class TestOptions(object): + """ + Class controlling command-line arguments used to control the test. + """ + def __init__(self): + parser = argparse.ArgumentParser(description='Qpid-interop AMQP client interoparability test suite ' + 'for AMQP simple types') + parser.add_argument('--broker', action='store', default='localhost:5672', metavar='BROKER:PORT', + help='Broker against which to run test suite.') +# test_group = parser.add_mutually_exclusive_group() +# test_group.add_argument('--include-test', action='append', metavar='TEST-NAME', +# help='Name of test to include') +# test_group.add_argument('--exclude-test', action='append', metavar='TEST-NAME', +# help='Name of test to exclude') +# type_group = test_group.add_mutually_exclusive_group() +# type_group.add_argument('--include-type', action='append', metavar='AMQP-TYPE', +# help='Name of AMQP type to include. Supported types:\n%s' % +# sorted(AmqpPrimitiveTypes.TYPE_MAP.keys())) + parser.add_argument('--exclude-type', action='append', metavar='AMQP-TYPE', + help='Name of AMQP type to exclude. Supported types:\n%s' % + sorted(AmqpPrimitiveTypes.TYPE_MAP.keys())) +# shim_group = test_group.add_mutually_exclusive_group() +# shim_group.add_argument('--include-shim', action='append', metavar='SHIM-NAME', +# help='Name of shim to include. Supported shims:\n%s' % sorted(SHIM_MAP.keys())) + parser.add_argument('--exclude-shim', action='append', metavar='SHIM-NAME', + help='Name of shim to exclude. Supported shims:\n%s' % sorted(SHIM_MAP.keys())) + self.args = parser.parse_args() + + +#--- Main program start --- + +if __name__ == '__main__': + + ARGS = TestOptions().args + #print 'ARGS:', ARGS # debug + + # Connect to broker to find broker type + CONNECTION_PROPS = qpid_interop_test.broker_properties.get_broker_properties(ARGS.broker) + if CONNECTION_PROPS is None: + print 'WARNING: Unable to get connection properties - unknown broker' + BROKER = 'unknown' + else: + BROKER = CONNECTION_PROPS[symbol(u'product')] if symbol(u'product') in CONNECTION_PROPS \ + else '<product not found>' + BROKER_VERSION = CONNECTION_PROPS[symbol(u'version')] if symbol(u'version') in CONNECTION_PROPS \ + else '<version not found>' + BROKER_PLATFORM = CONNECTION_PROPS[symbol(u'platform')] if symbol(u'platform') in CONNECTION_PROPS \ + else '<platform not found>' + print 'Test Broker: %s v.%s on %s' % (BROKER, BROKER_VERSION, BROKER_PLATFORM) + print + sys.stdout.flush() + + TYPES = AmqpPrimitiveTypes() + + # TEST_CASE_CLASSES is a list that collects all the test classes that are constructed. One class is constructed + # per AMQP type used as the key in map AmqpPrimitiveTypes.TYPE_MAP. + TEST_CASE_CLASSES = [] + + # TEST_SUITE is the final suite of tests that will be run and which contains all the dynamically created + # type classes, each of which contains a test for the combinations of client shims + TEST_SUITE = unittest.TestSuite() + + # Remove shims excluded from the command-line + if ARGS.exclude_shim is not None: + for shim in ARGS.exclude_shim: + SHIM_MAP.pop(shim) + # Create test classes dynamically + for at in sorted(TYPES.get_type_list()): + if ARGS.exclude_type is None or at not in ARGS.exclude_type: + test_case_class = create_testcase_class(BROKER, + TYPES, + ARGS.broker, + at, + product(SHIM_MAP.values(), repeat=2)) + TEST_CASE_CLASSES.append(test_case_class) + TEST_SUITE.addTest(unittest.makeSuite(test_case_class)) + + # Finally, run all the dynamically created tests + RES = unittest.TextTestRunner(verbosity=2).run(TEST_SUITE) + if not RES.wasSuccessful(): + sys.exit(1) # Errors or failures present http://git-wip-us.apache.org/repos/asf/qpid-interop-test/blob/514bac75/src/python/qpid_interop_test/broker_properties.py ---------------------------------------------------------------------- diff --git a/src/python/qpid_interop_test/broker_properties.py b/src/python/qpid_interop_test/broker_properties.py new file mode 100644 index 0000000..01a090f --- /dev/null +++ b/src/python/qpid_interop_test/broker_properties.py @@ -0,0 +1,55 @@ +""" +Module containing a small client which connects to the broker and +gets the broker connection properties so as to identify the broker. +""" + +# +# 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 proton.handlers import MessagingHandler +from proton.reactor import Container + +class Client(MessagingHandler): + """ + Client to connect to broker and collect connection properties, used to identify the test broker + """ + def __init__(self, url): + super(Client, self).__init__() + self.url = url + self.remote_properties = None + + def on_connection_remote_open(self, event): + """Callback for remote connection open""" + self.remote_properties = event.connection.remote_properties + event.connection.close() + + def on_start(self, event): + """Event loop start""" + event.container.connect(url=self.url) + + def get_connection_properties(self): + """Return the connection properties""" + return self.remote_properties + + +def get_broker_properties(broker_url): + """Start client, then return its connection properties""" + msg_handler = Client(broker_url) + Container(msg_handler).run() + return msg_handler.get_connection_properties() http://git-wip-us.apache.org/repos/asf/qpid-interop-test/blob/514bac75/src/python/qpid_interop_test/interop_test_errors.py ---------------------------------------------------------------------- diff --git a/src/python/qpid_interop_test/interop_test_errors.py b/src/python/qpid_interop_test/interop_test_errors.py new file mode 100644 index 0000000..6be8959 --- /dev/null +++ b/src/python/qpid_interop_test/interop_test_errors.py @@ -0,0 +1,29 @@ +""" +Module containing Error classes for interop testing +""" + +# +# 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. +# + +class InteropTestError(StandardError): + """ + Generic simple error class for use in interop tests + """ + def __init__(self, error_message): + super(InteropTestError, self).__init__(error_message) --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
