blob: be0d15bb45a89a4406560933ed13fe99023a18ae [file] [log] [blame]
"""
Module containing common classes
"""
#
# 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
from os import getcwd, getenv, path
import sys
from time import time
import unittest
from proton import symbol
import qpid_interop_test.broker_properties
import qpid_interop_test.shims
import qpid_interop_test.xunit_log
# TODO: propose a sensible default when installation details are worked out
QIT_INSTALL_PREFIX = getenv('QIT_INSTALL_PREFIX')
if QIT_INSTALL_PREFIX is None:
print('ERROR: Environment variable QIT_INSTALL_PREFIX is not set')
sys.exit(1)
QIT_TEST_SHIM_HOME = path.join(QIT_INSTALL_PREFIX, 'libexec', 'qpid_interop_test', 'shims')
QPID_JMS_SHIM_VER = '0.1.0'
DEFUALT_XUNIT_LOG_DIR = path.join(getcwd(), 'xunit_logs')
class QitTestTypeMap(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 known 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 = {}
# client_skip: For known client issues where a type would cause a test to fail or hang,
# entries in client_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 clients for which this
# type should be skipped.
# Format: {'jms_msg_type_1' : {'client_1' : 'skip msg for client_1',
# 'client_2' : 'skip msg for client_2',
# ...
# },
# 'jms_msg_type_2' : {'client_1' : 'skip msg for client_1',
# 'client_2' : 'skip msg for client_2',
# ...
# },
# ...
# }
# where client_1, client_2, ... are client product names as defined by the
# test shim NAME.
client_skip = {}
def get_type_list(self):
"""Return a list of types which this test suite supports"""
return self.type_map.keys()
def get_types(self, args):
"""Return the list of types"""
if "include_type" in args and args.include_type is not None:
new_type_map = {}
for this_type in args.include_type:
try:
new_type_map[this_type] = self.type_map[this_type]
except KeyError:
print('No such type: "%s". Use --help for valid types' % this_type)
sys.exit(1) # Errors or failures present
self.type_map = new_type_map
if "exclude_type" in args and args.exclude_type is not None:
for this_type in args.exclude_type:
try:
self.type_map.pop(this_type)
except KeyError:
print('No such type: "%s". Use --help for valid types' % this_type)
sys.exit(1) # Errors or failures present
return self
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("BROKER: " + 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]
def skip_client_test_message(self, test_type, client_name, role):
"""Return the message to use if a test is skipped"""
if test_type in self.client_skip.keys():
if client_name in self.client_skip[test_type]:
return str(role + ": " + self.client_skip[test_type][client_name])
return None
def skip_client_test(self, test_type, client_name):
"""Return boolean True if test should be skipped"""
return test_type in self.client_skip.keys() and \
client_name in self.client_skip[test_type]
@staticmethod
def merge_dicts(*dict_args):
"""Static method to merge two or more dictionaries"""
res = {}
for this_dict in dict_args:
res.update(this_dict)
return res
class QitCommonTestOptions(object):
"""
Class controlling common command-line arguments used to control tests.
"""
def __init__(self, test_description, shim_map, default_xunit_dir=DEFUALT_XUNIT_LOG_DIR):
self._parser = argparse.ArgumentParser(description=test_description)
self._parser.add_argument('--sender', action='store', default='localhost:5672', metavar='IP-ADDR:PORT',
help='Node to which test suite will send messages.')
self._parser.add_argument('--receiver', action='store', default='localhost:5672', metavar='IP-ADDR:PORT',
help='Node from which test suite will receive messages.')
self._parser.add_argument('--no-skip', action='store_true',
help='Do not skip tests that are excluded by default for reasons of a known bug')
self._parser.add_argument('--broker-type', action='store', metavar='BROKER_NAME',
help='Disable test of broker type (using connection properties) by specifying' +
' the broker name, or "None".')
self._parser.add_argument('--xunit-log', action='store_true',
help='Enable xUnit logging of test results')
self._parser.add_argument('--xunit-log-dir', action='store', default=default_xunit_dir,
metavar='LOG-DIR-PATH',
help='Default xUnit log directory where xUnit logs are written [xunit_logs dir' +
' in current directory (%s)]' % default_xunit_dir)
shim_group = self._parser.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()))
shim_group.add_argument('--exclude-shim', action='append', metavar='SHIM-NAME',
help='Name of shim to exclude. Supported shims: see "include-shim" above')
def args(self):
"""Return the parsed args"""
return self._parser.parse_args()
def print_help(self, file=None):
"""Print help"""
self._parser.print_help(file)
def print_usage(self, file=None):
"""Print usage"""
self._parser.print_usage(file)
class QitTestCase(unittest.TestCase):
"""
Abstract base class for QIT test cases
"""
def __init__(self, methodName='runTest'):
super(QitTestCase, self).__init__(methodName)
self.duration = 0
def setUp(self):
"""Called when test starts"""
self.start_time = time()
def tearDown(self):
"""Called when test finishes"""
self.duration = time() - self.start_time
def name(self):
"""Return test name"""
return self._testMethodName
class QitTest(object):
"""
Top-level test class with test entry-point
"""
TEST_NAME = ''
def __init__(self, test_options_class, test_values_class):
self._create_shim_map()
self.args = test_options_class(self.shim_map).args()
self._modify_shim_map()
self._discover_broker()
self.types = test_values_class().get_types(self.args)
self._generate_tests()
self.test_result = None
self.duration = 0
def get_result(self):
"""Get success of test run, True = success, False = failure/error"""
if self.test_result is None:
return None
return self.test_result.wasSuccessful()
def run_test(self):
"""Run the test"""
start_time = time()
self.test_result = unittest.TextTestRunner(verbosity=2).run(self.test_suite)
self.duration = time() - start_time
def write_logs(self):
"""Write the logs"""
if self.args.xunit_log_dir is not None:
xunit_log_dir = self.args.xunit_log_dir
else:
xunit_log_dir = DEFUALT_XUNIT_LOG_DIR
qpid_interop_test.xunit_log.Xunit(self.args.xunit_log, self.TEST_NAME, xunit_log_dir, self.test_suite,
self.test_result, self.duration)
def _create_shim_map(self):
"""Create a shim map {'shim_name': <shim_instance>}"""
proton_cpp_rcv_shim = path.join(QIT_TEST_SHIM_HOME, 'qpid-proton-cpp', self.TEST_NAME, 'Receiver')
proton_cpp_snd_shim = path.join(QIT_TEST_SHIM_HOME, 'qpid-proton-cpp', self.TEST_NAME, 'Sender')
proton_python_rcv_shim = path.join(QIT_TEST_SHIM_HOME, 'qpid-proton-python', self.TEST_NAME, 'Receiver.py')
proton_python_snd_shim = path.join(QIT_TEST_SHIM_HOME, 'qpid-proton-python', self.TEST_NAME, 'Sender.py')
self.shim_map = {qpid_interop_test.shims.ProtonCppShim.NAME: \
qpid_interop_test.shims.ProtonCppShim(proton_cpp_snd_shim, proton_cpp_rcv_shim),
qpid_interop_test.shims.ProtonPython2Shim.NAME: \
qpid_interop_test.shims.ProtonPython2Shim(proton_python_snd_shim, proton_python_rcv_shim),
qpid_interop_test.shims.ProtonPython3Shim.NAME: \
qpid_interop_test.shims.ProtonPython3Shim(proton_python_snd_shim, proton_python_rcv_shim),
}
# Add shims that need detection during installation only if the necessary bits are present
# Rhea Javascript client
rhea_rcv_shim = path.join(QIT_TEST_SHIM_HOME, 'rhea-js', self.TEST_NAME, 'Receiver.js')
rhea_snd_shim = path.join(QIT_TEST_SHIM_HOME, 'rhea-js', self.TEST_NAME, 'Sender.js')
if path.isfile(rhea_rcv_shim) and path.isfile(rhea_snd_shim):
self.shim_map[qpid_interop_test.shims.RheaJsShim.NAME] = \
qpid_interop_test.shims.RheaJsShim(rhea_snd_shim, rhea_rcv_shim)
else:
print('WARNING: Rhea Javascript shims not found')
# AMQP DotNetLite client
amqpnetlite_rcv_shim = path.join(QIT_TEST_SHIM_HOME, 'amqpnetlite', self.TEST_NAME, 'Receiver.exe')
amqpnetlite_snd_shim = path.join(QIT_TEST_SHIM_HOME, 'amqpnetlite', self.TEST_NAME, 'Sender.exe')
if path.isfile(amqpnetlite_rcv_shim) and path.isfile(amqpnetlite_snd_shim):
self.shim_map[qpid_interop_test.shims.AmqpNetLiteShim.NAME] = \
qpid_interop_test.shims.AmqpNetLiteShim(amqpnetlite_snd_shim, amqpnetlite_rcv_shim)
else:
print('WARNING: AMQP DotNetLite shims not found')
def _modify_shim_map(self):
"""Modify shim_map based on args"""
# Use only shims included from the command-line
if self.args.include_shim is not None:
temp_shim_map = {}
for shim in self.args.include_shim:
try:
temp_shim_map[shim] = self.shim_map[shim]
except KeyError:
print('No such shim: "%s". Use --help for valid shims' % shim)
sys.exit(1) # Errors or failures present
self.shim_map = temp_shim_map
# Remove shims excluded from the command-line
elif self.args.exclude_shim is not None:
for shim in self.args.exclude_shim:
try:
self.shim_map.pop(shim)
except KeyError:
print('No such shim: "%s". Use --help for valid shims' % shim)
sys.exit(1) # Errors or failures present
def _discover_broker(self):
"""Connect to broker and get connection properties to discover broker name and version"""
if self.args.broker_type is not None:
if self.args.broker_type == 'None':
self.broker = None
else:
self.broker = self.broker.broker_type
else:
connection_props = qpid_interop_test.broker_properties.get_broker_properties(self.args.sender)
if connection_props is None:
print('WARNING: Unable to get connection properties - unknown broker')
self.broker = 'unknown'
else:
self.broker = connection_props[symbol(u'product')] if symbol(u'product') in connection_props \
else '<product not found>'
self.broker_version = connection_props[symbol(u'version')] if symbol(u'version') in connection_props \
else '<version not found>'
self.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\n' % (self.broker, self.broker_version, self.broker_platform))
sys.stdout.flush()
if self.args.no_skip:
self.broker = None # Will cause all tests to run, no matter which broker
def _generate_tests(self):
"""Generate tests dynamically - each subclass must override this function"""
self.test_suite = None
class QitJmsTest(QitTest):
"""Class with specialized Java and classpath functionality"""
def _create_shim_map(self):
"""Create a shim map {'shim_name': <shim_instance>}"""
super(QitJmsTest, self)._create_shim_map()
qpid_jms_rcv_shim = 'org.apache.qpid.interop_test.%s.Receiver' % self.TEST_NAME
qpid_jms_snd_shim = 'org.apache.qpid.interop_test.%s.Sender' % self.TEST_NAME
classpath_file_name = path.join(QIT_TEST_SHIM_HOME, 'qpid-jms', 'cp.txt')
if path.isfile(classpath_file_name):
with open(classpath_file_name, 'r') as classpath_file:
classpath = classpath_file.read()
else:
classpath = path.join(QIT_TEST_SHIM_HOME, 'qpid-jms',
'qpid-interop-test-jms-shim-%s-jar-with-dependencies.jar' % QPID_JMS_SHIM_VER)
self.shim_map[qpid_interop_test.shims.QpidJmsShim.NAME] = \
qpid_interop_test.shims.QpidJmsShim(classpath, qpid_jms_snd_shim, qpid_jms_rcv_shim)