# HG changeset patch # User Vyacheslav Savchenko <25712100+SeyfSV@users.noreply.github.com> # Date 1592990611 -10800 # Wed Jun 24 12:23:31 2020 +0300 # Node ID 449e17b8dd577683bf0f5604aab71219ce4ff180 # Parent 1ba572cd9ec5f51b4c469bf2a4436100113dfe3a GH #159 Unit testing with guthub actions * GH #159 Unit-testing improvements * GH #159 Fix buffer_too_short test * GH #163 Fix pcf.disconnect() in tests * GH #163 Cleanup Co-authored-by: Alexandre Yang <alexandre.yang@datadoghq.com> * GH #163 Bring back Windows python 3.5 install-test * GH #163 Fix install test and other polish Co-authored-by: Alexandre Yang <alexandre.yang@datadoghq.com> diff --git a/.coveragerc b/.coveragerc new file mode 100644 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,2 @@ +[run] +omit = */CMQ*.py diff --git a/.github/workflows/install-tests.yml b/.github/workflows/install-tests.yml new file mode 100644 --- /dev/null +++ b/.github/workflows/install-tests.yml @@ -0,0 +1,46 @@ +name: install-tests +on: + push: + branches: + - master + - gh159-actions-unit-testing + pull_request: + +jobs: + pymqi_test_job: + strategy: + matrix: + environment: ['macos-latest', 'windows-latest', 'ubuntu-latest'] + python-version: [2.7, 3.5, 3.6, 3.7, 3.8] + mq-client-version: [9.1.5.0] + exclude: + # Windows runner does not have libraries required for build with python2.7 + - environment: windows-latest + python-version: 2.7 + runs-on: ${{ matrix.environment}} + steps: + - name: Checkout source + uses: actions/checkout@v2 + + - name: Cache MQ Client + uses: actions/cache@v1 + with: + path: ${{ github.workspace }}/setup-mqclient + key: mqclient-${{ runner.os }}-${{ matrix.mq-client-version }} + + - name: Install MQ Client + id: setup-mqclient + uses: SeyfSV/setup-mqclient@v0.1.3 + with: + mq-client-version: ${{ matrix.mq-client-version }} + + - name: Setup python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + + - name: Install pymqi + env: + MQ_FILE_PATH: ${{ steps.setup-mqclient.outputs.mq-file-path }} + run: | + python setup.py install --verbose diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,121 @@ +name: unit-tests +on: + push: + branches: + - master + - gh159-actions-unit-testing + pull_request: + +jobs: + pymqi_test_job: + strategy: + max-parallel: 3 + matrix: + environment: ['ubuntu-latest'] + # windows-latest: runner does allow to run ibmcom/mq linux container + # macos-latest: runner does not have docker + # macos-latest: has some issues in SeyfSV/setup-mqclient@master action + #environment: ['ubuntu-latest', 'macos-latest', 'windows-latest'] + python-version: [2.7, 3.5, 3.6, 3.7, 3.8] + mq-client-version: [9.1.4.0] + + services: + mq: + image: ibmcom/mq + env: + LICENSE: accept + MQ_QMGR_NAME: MQTEST + ports: + - 8886:1414 + volumes: + - mqm_volume:/mnt/mqm + - pki_volume:/etc/mqm/pki + options: --name mq --health-cmd "dspmq -m MQTEST" --health-interval 5s --health-timeout 5s --health-retries 10 + + runs-on: ${{ matrix.environment}} + steps: + - name: Extract pki_volume path + id: pki_volume_path + run: | + echo "::set-output name=volume_path::$(docker volume inspect --format '{{ .Mountpoint }}' pki_volume)" + + - name: Extract mqm_volume path + id: mqm_volume_path + run: | + echo "::set-output name=volume_path::$(docker volume inspect --format '{{ .Mountpoint }}' mqm_volume)" + + - name: Checkout source + uses: actions/checkout@v2 + + - name: Cache MQ Client + uses: actions/cache@v1 + with: + path: ${{ github.workspace }}/setup-mqclient + key: mqclient-${{ runner.os }}-${{ matrix.mq-client-version }} + + - name: Install MQ Client + id: setup-mqclient + uses: SeyfSV/setup-mqclient@v0.1.3 + with: + mq-client-version: ${{ matrix.mq-client-version }} + + - name: Setup python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + + - name: Install pymqi dependencies + run: | + pip install tox + + - name: Prepare to TLS test + run: | + mkdir ./keys + runmqakm -keydb -create -db ./keys/mqtest.kdb -pw Secret13 -stash + runmqakm -cert -create -db ./keys/mqtest.kdb -type kdb -pw Secret13 \ + -label mqtest -dn CN=mqtest -size 2048 -x509version 3 -expire 365 -fips -sig_alg SHA256WithRSA + runmqakm -keydb -create -db ./keys/client.kdb -pw Secret13 -stash + runmqakm -cert -create -db ./keys/client.kdb -type kdb -pw Secret13 \ + -label client -dn CN=client -size 2048 -x509version 3 -expire 365 -fips -sig_alg SHA256WithRSA + runmqakm -cert -extract -db ./keys/mqtest.kdb -pw Secret13 \ + -label mqtest -target ./keys/mqtest.pem + runmqakm -cert -add -db ./keys/client.kdb -pw Secret13 \ + -label mqtest -file ./keys/mqtest.pem + runmqakm -cert -extract -db ./keys/client.kdb -pw Secret13 \ + -label client -target ./keys/client.pem + runmqakm -cert -add -db ./keys/mqtest.kdb -pw Secret13 \ + -label client -file ./keys/client.pem + sudo cp -r ./keys ${{ steps.pki_volume_path.outputs['volume_path'] }} + sudo chown -R `id -u`:1001 ${{ steps.pki_volume_path.outputs['volume_path'] }}/keys + sudo chmod -R g+r ${{ steps.pki_volume_path.outputs['volume_path'] }}/keys + + - name: Test pymqi with tox + id: test-tox + env: + PYMQI_TEST_TLS_SKIP: 0 + PYMQI_TEST_TLS_KEY_REPO_LOCATION_QMGR: /etc/mqm/pki/keys + PYMQI_TEST_TLS_KEY_REPO_LOCATION_CLIENT: ./keys + run: | + tox -e docker + + - name: Copy MQ Server logs + if: failure() + run: | + sudo cp -r ${{ steps.mqm_volume_path.outputs['volume_path'] }}/data/qmgrs/MQTEST/errors ./errors + sudo chmod o+x ./errors + sudo chmod -R o+r ./errors + + - name: Upload MQ Server logs + if: failure() + uses: actions/upload-artifact@v1 + with: + name: mq-server-logs + path: ./errors + + - name: Upload MQ Client logs + if: failure() + uses: actions/upload-artifact@v1 + with: + name: mq-client-logs + path: ${{ steps.setup-mqclient.outputs.mq-file-path }}/errors + diff --git a/.gitignore b/.gitignore --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ .idea .idea/* -code/build/* +build/* code/dist code/develop-eggs/* code/MANIFEST diff --git a/code/README b/code/README --- a/code/README +++ b/code/README @@ -1,3 +1,6 @@ + + + PyMQI - Python interface to IBM MQ (WebSphere MQ, MQSeries) ---------------------------------------------------------- diff --git a/code/setup.py b/code/setup.py deleted file mode 100644 --- a/code/setup.py +++ /dev/null @@ -1,198 +0,0 @@ -# Setup script usable for distutils. -# -# Original Author: Maas-Maarten Zeeman -# -# Linux build is re-entrant/multithreaded. - -# stdlib -import os -import sys -try: - from setuptools import setup -except ImportError: - from distutils.core import setup - -from distutils.core import Extension -from distutils import spawn -from struct import calcsize - -version = '1.11.0' - -# Build either in bindings or client mode. -bindings_mode = 0 -if sys.argv[-1] in ('bindings', 'server'): - bindings_mode = 1 - sys.argv = sys.argv[:-1] -if sys.argv[-1] == 'client': - bindings_mode = 0 - sys.argv = sys.argv[:-1] - -# Are we running 64bits? -if calcsize('P') == 8: - bits = 64 -else: - bits = 32 - -def get_windows_settings(): - """ Windows settings. - """ - if bits == 64: - library_dirs = [r'c:\Program Files (x86)\IBM\WebSphere MQ\tools\Lib64'] - include_dirs = [r'c:\Program Files (x86)\IBM\WebSphere MQ\tools\c\include'] - else: - library_dirs = [r'c:\Program Files\IBM\WebSphere MQ\Tools\Lib'] - include_dirs = [r'c:\Program Files\IBM\WebSphere MQ\tools\c\include'] - - if bindings_mode: - libraries = ['mqm'] - else: - if bits == 64: - libraries = ['mqic'] - else: - libraries = ['mqic32'] - - return library_dirs, include_dirs, libraries - -def get_sunos_zlinux_settings(): - """ SunOS and z/Linux settings. - """ - if bits == 64: - library_dirs = ['/opt/mqm/lib64'] - else: - library_dirs = ['/opt/mqm/lib'] - - include_dirs = ['/opt/mqm/inc'] - - if bindings_mode: - libraries = ['mqm','mqmcs','mqmzse'] - else: - libraries = ['mqic'] - - return library_dirs, include_dirs, libraries - -def get_aix_settings(): - """ AIX settings. - """ - if bits == 64: - library_dirs = ['/usr/mqm/lib64'] - else: - library_dirs = ['/usr/mqm/lib'] - - include_dirs = ['/usr/mqm/inc'] - - if bindings_mode: - libraries = ['mqm_r'] - else: - libraries = ['mqic_r'] - - return library_dirs, include_dirs, libraries - -def get_generic_unix_settings(): - """ Generic UNIX, including Linux, settings. - """ - if bits == 64: - library_dirs = ['/opt/mqm/lib64'] - else: - library_dirs = ['/opt/mqm/lib'] - - include_dirs = ['/opt/mqm/inc'] - - if bindings_mode: - libraries = ['mqm_r'] - else: - libraries = ['mqic_r'] - - return library_dirs, include_dirs, libraries - -def get_locations_by_command_path(command_path): - """ Extracts directory locations by the path to one of MQ commands, such as dspmqver. - """ - command_dir = os.path.dirname(command_path) - mq_installation_path = os.path.abspath(os.path.join(command_dir, '..')) - - if bits == 64: - library_dirs = ['{}/lib64'.format(mq_installation_path)] - else: - library_dirs = ['{}/lib'.format(mq_installation_path)] - - include_dirs = ['{}/inc'.format(mq_installation_path)] - - if bindings_mode: - libraries = ['mqm_r'] - else: - libraries = ['mqic_r'] - - return library_dirs, include_dirs, libraries - -# Windows -if sys.platform == 'win32': - library_dirs, include_dirs, libraries = get_windows_settings() - -# SunOS and z/Linux -elif sys.platform == 'sunos5' or sys.platform == 'linux-s390': - library_dirs, include_dirs, libraries = get_sunos_zlinux_settings() - -# AIX -elif sys.platform.startswith('aix'): - library_dirs, include_dirs, libraries = get_aix_settings() - -# At this point, to preserve backward-compatibility we try out generic -# UNIX settings first, i.e. libraries and include files in well-known locations. -# Otherwise, to support Mac, we look up dspmqver in $PATH. -else: - - has_generic_lib = os.path.exists('/opt/mqm/lib64') if bits == 64 else os.path.exists('/opt/mqm/lib') - - if has_generic_lib: - library_dirs, include_dirs, libraries = get_generic_unix_settings() - - else: - - # On Mac, users can install MQ to any location so we look up - # the path that dspmqver is installed to and find the rest - # of the information needed in relation to that base directory. - dspmqver_path = spawn.find_executable('dspmqver') - - # We have found the command so we will be able to extract the relevant directories now - if dspmqver_path: - library_dirs, include_dirs, libraries = get_locations_by_command_path(dspmqver_path) - - else: - raise Exception('MQ libraries could not be found') - -if bindings_mode: - print('Building PyMQI bindings mode %sbits' % bits) -else: - print('Building PyMQI client mode %sbits' % bits) - -print('Using library_dirs:`%s`, include:`%s`, libraries:`%s`' % (library_dirs, include_dirs, libraries)) - -setup(name = 'pymqi', - version = version, - description = 'Python IBM MQI Extension for IBM MQ (formerly WebSphere MQ and MQSeries).', - long_description= 'PyMQI is a Python library for working with IBM MQ (formerly WebSphere MQ and MQSeries) implementing MQI and PCF protocols.', - author='Dariusz Suchojad', - author_email='pymqi@m.zato.io', - url='https://dsuch.github.io/pymqi/', - download_url='https://pypi.python.org/pypi/pymqi', - platforms='OS Independent', - packages = ['pymqi'], - license='Python Software Foundation License', - keywords=('pymqi IBM MQ WebSphere WMQ MQSeries IBM middleware messaging queueing asynchronous SOA EAI ESB integration'), - - classifiers = [ - 'Development Status :: 5 - Production/Stable', - 'License :: OSI Approved :: Python Software Foundation License', - 'Intended Audience :: Developers', - 'Natural Language :: English', - 'Operating System :: OS Independent', - 'Programming Language :: C', - 'Programming Language :: Python', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Topic :: Software Development :: Object Brokering', - ], - py_modules = ['pymqi.CMQC', 'pymqi.CMQCFC', 'pymqi.CMQXC', 'pymqi.CMQZC'], - ext_modules = [Extension('pymqi.pymqe',['pymqi/pymqe.c'], define_macros=[('PYQMI_BINDINGS_MODE_BUILD', bindings_mode)], - library_dirs = library_dirs, - include_dirs = include_dirs, - libraries = libraries)]) diff --git a/code/tests/README b/code/tests/README --- a/code/tests/README +++ b/code/tests/README @@ -5,26 +5,46 @@ * The test suite itself requires at least Python 2.5 because of its reliance on the 'uuid' module which has been introduced in 2.5 -* Install nose and testfixtures - $ sudo pip install nose - $ sudo pip install testfixtures +* Install tox + ``` + $ sudo pip install tox + ``` -* Set a proper configuration by modifying the defaults in config.py or override +* Set a proper configuration by modifying the defaults in config.py or override these properties with environment variables, see config.py, if necessary. -* Prepare the test environment using create_mq_objects.py (needs a local MQ - server installation to access the MQ command executables) or create the - objects by hand, consistent with the configuration. +* Prepare the test environment: + * Using create_mq_objects.py (needs a local MQ + server installation to access the MQ command executables) or create the + objects by hand, consistent with the configuration. + * Using Docker image (ibmcom/mq) provided by IBM. + For minimal setup this command can be used: + ``` + $ docker run \ + --env LICENSE=accept \ + --env MQ_QMGR_NAME=MQTEST \ + --publish 8886:1414 \ + ibmcom/mq + ``` -* Use nose to execute the tests: +* Use tox to execute the tests: + * With local MQ: + ``` + $ tox -e local + ``` + * With Docker image: + ``` + $ tox -e docker + ``` + +* Use py.test to execute the tests: * Directly: - $ nosetests + ``` + $ py.test + ``` Note that you need an inplace build of pymqe.so if you want to run the tests on the checkout rather than e.g. a test installation - (python setup.py build_ext --inplace). - * Through setup.py: - $ python setup.py nosetests - (this issues an inplace-build automatically) + (`python setup.py build_ext --inplace`). * [Delete the test environment with delete_mq_objects.py] diff --git a/code/tests/create_mq_objects.py b/code/tests/create_mq_objects.py --- a/code/tests/create_mq_objects.py +++ b/code/tests/create_mq_objects.py @@ -150,8 +150,8 @@ errormsg="MQ queue manager connection authentication setup not successful.") else: print("Connection authentication not applicable for pre-8.0 MQ.") - - + + print("Disabling MQ queue manager channel authentication records feature.") run_mqsc(mqsc_script_channel_auth, errormsg="Disabling MQ queue manager channel authentication records not successful.") diff --git a/code/tests/env.py b/code/tests/env.py deleted file mode 100644 --- a/code/tests/env.py +++ /dev/null @@ -1,22 +0,0 @@ -# test environment settings - -import sys -import os - - -def add_syspath(p): - """Add path p to sys.path without defeating PYTHONPATH. - - Appends to sys.path if PYTHONPATH is set, otherwise put p at sys.path index - 1. - """ - if p not in sys.path: - if "PYTHONPATH" in os.environ: - sys.path.append(p) - else: - sys.path.insert(1, p) - - -# Add pymqi code directory to sys.path for import from checkout -# (needs in-place build of pymqe.so) -add_syspath(os.path.normpath(os.path.join(os.path.dirname(__file__), '..'))) diff --git a/code/tests/requirements.txt b/code/tests/requirements.txt --- a/code/tests/requirements.txt +++ b/code/tests/requirements.txt @@ -1,4 +1,6 @@ ddt nose pytest +pytest-cov testfixtures +tox diff --git a/code/tests/run_pymqi_test_suite.py b/code/tests/run_pymqi_test_suite.py deleted file mode 100644 --- a/code/tests/run_pymqi_test_suite.py +++ /dev/null @@ -1,27 +0,0 @@ -''' -Created on 17 Nov 2010 - -@author: hannes -''' - -import unittest - -import test_rfh2 -import test_h2py -import test_rfh2_put_get - -h2py_suite = unittest.TestLoader().loadTestsFromTestCase(test_h2py.Testh2py) - -rfh2_suite = unittest.TestLoader().loadTestsFromTestCase(test_rfh2.TestRFH2) -rfh2_put_get_suite = unittest.TestLoader().loadTestsFromTestCase(test_rfh2_put_get.TestRFH2PutGet) - -all_suite = unittest.TestSuite([h2py_suite, rfh2_suite]) - -mq_not_required_tests = [h2py_suite, rfh2_suite] -mq_required_tests = [rfh2_put_get_suite] - -mq_not_required_suite = unittest.TestSuite(mq_not_required_tests) -mq_required_suite = unittest.TestSuite(mq_required_tests) - -unittest.TextTestRunner(verbosity=2).run(mq_not_required_suite) -unittest.TextTestRunner(verbosity=2).run(mq_required_suite) diff --git a/code/tests/test_api_transition_pep8.py b/code/tests/test_api_transition_pep8.py --- a/code/tests/test_api_transition_pep8.py +++ b/code/tests/test_api_transition_pep8.py @@ -1,27 +1,27 @@ """ All sorts of tests related to making the API PEP-8 compliant. """ - -# stdlib -import sys - -sys.path.insert(0, "..") - -from nose.tools import eq_ +import unittest # PyMQI import pymqi -def test_backward_compatibility(): - """ Makes sure all the relevant classes and methods have backward-compatible - replacements. - """ - eq_(pymqi.gmo, pymqi.GMO) - eq_(pymqi.pmo, pymqi.PMO) - eq_(pymqi.od, pymqi.OD) - eq_(pymqi.md, pymqi.MD) - eq_(pymqi.cd, pymqi.CD) - eq_(pymqi.sco, pymqi.SCO) - eq_(pymqi.QueueManager.connectWithOptions, pymqi.QueueManager.connect_with_options) - eq_(pymqi.QueueManager.connectTCPClient, pymqi.QueueManager.connect_tcp_client) - eq_(pymqi.QueueManager.getHandle, pymqi.QueueManager.get_handle) - eq_(pymqi.PCFExecute.stringifyKeys, pymqi.PCFExecute.stringify_keys) + +class TestApiTransitionPEP8(unittest.TestCase): + """All sorts of tests related to making the API PEP-8 compliant.""" + + def test_backward_compatibility(self): + """Test backward-compatible. + + Makes sure all the relevant classes and methods have + backward-compatible replacements. + """ + self.assertEqual(pymqi.gmo, pymqi.GMO) + self.assertEqual(pymqi.pmo, pymqi.PMO) + self.assertEqual(pymqi.od, pymqi.OD) + self.assertEqual(pymqi.md, pymqi.MD) + self.assertEqual(pymqi.cd, pymqi.CD) + self.assertEqual(pymqi.sco, pymqi.SCO) + self.assertEqual(pymqi.QueueManager.connectWithOptions, pymqi.QueueManager.connect_with_options) + self.assertEqual(pymqi.QueueManager.connectTCPClient, pymqi.QueueManager.connect_tcp_client) + self.assertEqual(pymqi.QueueManager.getHandle, pymqi.QueueManager.get_handle) + self.assertEqual(pymqi.PCFExecute.stringifyKeys, pymqi.PCFExecute.stringify_keys) diff --git a/code/tests/test_base_api.py b/code/tests/test_base_api.py --- a/code/tests/test_base_api.py +++ b/code/tests/test_base_api.py @@ -323,5 +323,21 @@ self.assertEqual(message, b'\xd1\x82\xd0\xb5\xd1\x81\xd1\x82') self.assertEqual(md.Format, pymqi.CMQC.MQFMT_NONE) + def test_put1(self): + input_msg = b'Hello world!' + self.qmgr.put1(self.queue_name, input_msg) + # now get the message from the queue + queue = pymqi.Queue(self.qmgr, self.queue_name) + result_msg = queue.get() + self.assertEqual(input_msg, result_msg) + + def test_inquire(self): + attribute = pymqi.CMQC.MQCA_Q_MGR_NAME + expected_value = utils.py3str2bytes(self.queue_manager) + attribute_value = self.qmgr.inquire(attribute) + self.assertEqual(len(attribute_value), pymqi.CMQC.MQ_Q_MGR_NAME_LENGTH) + self.assertEqual(attribute_value.strip(), expected_value) + + if __name__ == "__main__": unittest.main() diff --git a/code/tests/test_message_property.py b/code/tests/test_message_property.py --- a/code/tests/test_message_property.py +++ b/code/tests/test_message_property.py @@ -3,7 +3,6 @@ import unittest import config -import env import utils import pymqi @@ -56,13 +55,13 @@ pymqi.CMQC.MQIA_Q_TYPE: queue_type, pymqi.CMQC.MQIA_MAX_Q_DEPTH: max_depth, pymqi.CMQCFC.MQIACF_REPLACE: pymqi.CMQCFC.MQRP_YES} - pcf = pymqi.PCFExecute(self.qmgr) + pcf = pymqi.PCFExecute(self.qmgr, response_wait_interval=120000) pcf.MQCMD_CREATE_Q(args) pcf.disconnect def delete_queue(self, queue_name): - pcf = pymqi.PCFExecute(self.qmgr) + pcf = pymqi.PCFExecute(self.qmgr, response_wait_interval=120000) args = {pymqi.CMQC.MQCA_Q_NAME: utils.py3str2bytes(queue_name), pymqi.CMQCFC.MQIACF_PURGE: pymqi.CMQCFC.MQPO_YES} pcf.MQCMD_DELETE_Q(args) diff --git a/code/tests/test_mq80.py b/code/tests/test_mq80.py --- a/code/tests/test_mq80.py +++ b/code/tests/test_mq80.py @@ -19,7 +19,6 @@ # test config & env import config -import env # PyMQI import pymqi @@ -46,8 +45,8 @@ conn = self.get_conn() pcf = pymqi.PCFExecute(conn) command_level = pcf.MQCMD_INQUIRE_Q_MGR()[0][CMQC.MQIA_COMMAND_LEVEL] + conn.disconnect() self.assertGreaterEqual(command_level, 800) - conn.disconnect() def test_connect_with_credentials(self): diff --git a/code/tests/test_pcf.py b/code/tests/test_pcf.py --- a/code/tests/test_pcf.py +++ b/code/tests/test_pcf.py @@ -1,8 +1,10 @@ """Test PCF usage.""" import os from unittest import skip +from unittest import skipIf from ddt import data from ddt import ddt +from sys import version_info as sys_version_info from test_setup import Tests # noqa from test_setup import main # noqa @@ -23,13 +25,10 @@ # max length of queue names is 48 characters cls.queue_name = "{prefix}PCF.QUEUE".format(prefix=cls.prefix) - cls.pcf = pymqi.PCFExecute(cls.qmgr, response_wait_interval=600) @classmethod def tearDownClass(cls): """Tear down test environment.""" - cls.pcf.disconnect() - super(TestPCF, cls).tearDownClass() def setUp(self): @@ -274,6 +273,7 @@ self.assertEqual(item[pymqi.CMQC.MQCA_Q_NAME].strip(), b'SYSTEM.ADMIN.COMMAND.QUEUE') self.assertEqual(item[pymqi.CMQCFC.MQIAMO_PUTS], [14, 0]) + @skipIf(sys_version_info < (3, 7),'Python pre 3.7 issues: https://github.com/dsuch/pymqi/issues/207#issuecomment-645422229') def test_mqcfbs_old(self): """Test byte string MQCFBS with old style.""" attrs = { diff --git a/code/tests/test_queue_manager.py b/code/tests/test_queue_manager.py --- a/code/tests/test_queue_manager.py +++ b/code/tests/test_queue_manager.py @@ -5,7 +5,6 @@ import unittest from uuid import uuid4 -from nose.tools import eq_ from testfixtures import Replacer import config # noqa @@ -128,18 +127,6 @@ def test_backout(self): pass - def test_put1(self): - qmgr = pymqi.QueueManager(None) - qmgr.connect_tcp_client( - self.qm_name, pymqi.cd(), self.channel, self.conn_info, user=self.user, - password=self.password) - input_msg = b'Hello world!' - qmgr.put1(self.queue_name, input_msg) - # now get the message from the queue - queue = pymqi.Queue(qmgr, self.queue_name) - result_msg = queue.get() - self.assertEqual(input_msg, result_msg) - def test_inquire(self): qmgr = pymqi.QueueManager(None) qmgr.connect_tcp_client( @@ -151,50 +138,5 @@ self.assertEqual(len(attribute_value), pymqi.CMQC.MQ_Q_MGR_NAME_LENGTH) self.assertEqual(attribute_value.strip(), expected_value) - def test_is_connected(self): - """Makes sure the QueueManager's 'is_connected' property works as - expected. - """ - # uses a mock so no real connection to a queue manager will be - # established - the parameters below are basically moot - with Replacer() as r: - queue_manager = uuid4().hex - channel = uuid4().hex - host = uuid4().hex - port = "1431" - conn_info = "%s(%s)" % (host, port) - user = "myuser" - password = "mypass" - - for expected in(True, False): - - # noinspection PyUnusedLocal - def _connect_tcp_client(*ignored_args, **ignored_kwargs): - pass - - # noinspection PyUnusedLocal - def _getattr(self2, name): - if expected: - class _DummyMethod(object): - pass - # The mere fact of not raising an exception will suffice - # for QueueManager._is_connected to understand it as an - # all's OK condition. - return _DummyMethod - else: - raise Exception() - - r.replace('pymqi.QueueManager.connect_tcp_client', - _connect_tcp_client) - r.replace('pymqi.PCFExecute.__getattr__', _getattr) - - qmgr = pymqi.QueueManager(None) - qmgr.connect_tcp_client( - queue_manager, pymqi.cd(), channel, conn_info, user, - password) - - eq_(qmgr.is_connected, expected) - - if __name__ == '__main__': unittest.main() diff --git a/code/tests/test_rfh2.py b/code/tests/test_rfh2.py --- a/code/tests/test_rfh2.py +++ b/code/tests/test_rfh2.py @@ -10,6 +10,7 @@ from pymqi import CMQC + class TestRFH2(unittest.TestCase): """This test case tests the RFH2 class and it's methods. """ @@ -222,8 +223,8 @@ rfh2.unpack(self.single_rfh2_message[0:32]) except pymqi.PYIFError as e: self.assertEqual(str(e), - "PYMQI Error: RFH2 - Buffer too short. Should be 36 bytes or longer. Buffer Length: 32", - "Not Buffer to short exception?") + 'PYMQI Error: RFH2 - Buffer too short. Should be 36+ bytes instead of 32', + 'Not Buffer to short exception?') def test_buffer_too_short_for_complete_rfh2_exception(self): """Test exception occurs when buffer is too short for complete RFH2. diff --git a/code/tests/test_rfh2_put_get.py b/code/tests/test_rfh2_put_get.py --- a/code/tests/test_rfh2_put_get.py +++ b/code/tests/test_rfh2_put_get.py @@ -10,8 +10,10 @@ import pymqi from pymqi import CMQC +import test_setup -class TestRFH2PutGet(unittest.TestCase): + +class TestRFH2PutGet(test_setup.Tests): """This test case tests the RFH2 class and it's methods. """ @@ -22,6 +24,7 @@ Must be run as a user that has 'mqm' access. """ + super(TestRFH2PutGet, self).setUp() self.single_rfh2_message = open( os.path.join(self.messages_dir, "single_rfh2.dat"), "rb").read() @@ -33,21 +36,10 @@ self.multiple_rfh2_message_not_well_formed = \ self.multiple_rfh2_message[0:117] + self.multiple_rfh2_message[121:] - queue_manager = config.MQ.QM.NAME - channel = config.MQ.QM.CHANNEL - conn_info = "%s(%s)" % (config.MQ.QM.HOST, config.MQ.QM.PORT) - queue_name = config.MQ.QUEUE.QUEUE_NAMES['TestRFH2PutGet'] + self.create_queue(self.queue_name) - self.qmgr = None - if pymqi.__mqbuild__ == 'server': - self.qmgr = pymqi.QueueManager(queue_manager) - else: - self.qmgr = pymqi.QueueManager(None) - self.qmgr.connect_tcp_client( - queue_manager, pymqi.cd(), channel, conn_info, - user=config.MQ.QM.USER, password=config.MQ.QM.PASSWORD) - self.put_queue = pymqi.Queue(self.qmgr, queue_name) - self.get_queue = pymqi.Queue(self.qmgr, queue_name) + self.put_queue = pymqi.Queue(self.qmgr, self.queue_name) + self.get_queue = pymqi.Queue(self.qmgr, self.queue_name) self.clear_queue(self.get_queue) def tearDown(self): @@ -56,7 +48,8 @@ """ self.put_queue.close() self.get_queue.close() - self.qmgr.disconnect() + self.delete_queue(self.queue_name) + super(TestRFH2PutGet, self).tearDown() @staticmethod def clear_queue(queue): diff --git a/code/tests/test_setup.py b/code/tests/test_setup.py --- a/code/tests/test_setup.py +++ b/code/tests/test_setup.py @@ -42,19 +42,21 @@ cls.conn_info = '{0}({1})'.format(cls.host, cls.port) - cls.qmgr = pymqi.QueueManager(None) - try: + if pymqi.__mqbuild__ == 'server': + cls.qmgr = pymqi.QueueManager(cls.queue_manager) + else: + cls.qmgr = pymqi.QueueManager(None) cls.qmgr.connectTCPClient(cls.queue_manager, pymqi.CD(), cls.channel, cls.conn_info, cls.user, cls.password) - except pymqi.MQMIError as ex: - if ex.comp == pymqi.CMQC.MQCC_FAILED: - raise ex + + cls.pcf = pymqi.PCFExecute(cls.qmgr, response_wait_interval=5000) cls.version = cls.inquire_qmgr_version().decode() @classmethod def tearDownClass(cls): """Clear test environment.""" + cls.pcf.disconnect() cls.qmgr.disconnect() def setUp(self): @@ -81,18 +83,16 @@ attrs.append(pymqi.CFIN(Parameter=pymqi.CMQCFC.MQIACF_REPLACE, Value=pymqi.CMQCFC.MQRP_YES)) - pcf = pymqi.PCFExecute(self.qmgr) - pcf.MQCMD_CREATE_Q(attrs) + self.pcf.MQCMD_CREATE_Q(attrs) def delete_queue(self, queue_name): """Delete queue.""" - pcf = pymqi.PCFExecute(self.qmgr) attrs = [] attrs.append(pymqi.CFST(Parameter=pymqi.CMQC.MQCA_Q_NAME, String=utils.py3str2bytes(queue_name))) attrs.append(pymqi.CFIN(Parameter=pymqi.CMQCFC.MQIACF_PURGE, Value=pymqi.CMQCFC.MQPO_YES)) - pcf.MQCMD_DELETE_Q(attrs) + self.pcf.MQCMD_DELETE_Q(attrs) def create_channel(self, channel_name, attrs=None): """Create channle.""" @@ -104,29 +104,24 @@ Value=pymqi.CMQC.MQCHT_SVRCONN)) attrs.append(pymqi.CFIN(Parameter=pymqi.CMQCFC.MQIACF_REPLACE, Value=pymqi.CMQCFC.MQRP_YES)) - pcf = pymqi.PCFExecute(self.qmgr) - pcf.MQCMD_CREATE_CHANNEL(attrs) + self.pcf.MQCMD_CREATE_CHANNEL(attrs) def delete_channel(self, channel_name): """Delete channel.""" - pcf = pymqi.PCFExecute(self.qmgr) attrs = [] attrs.append(pymqi.CFST(Parameter=pymqi.CMQCFC.MQCACH_CHANNEL_NAME, String=utils.py3str2bytes(channel_name))) - pcf.MQCMD_DELETE_CHANNEL(attrs) + self.pcf.MQCMD_DELETE_CHANNEL(attrs) def create_auth_rec(self, attrs): """Create authentication recoed.""" - pcf = pymqi.PCFExecute(self.qmgr) - pcf.MQCMD_SET_CHLAUTH_REC(attrs) + self.pcf.MQCMD_SET_CHLAUTH_REC(attrs) def delete_auth_rec(self, attrs): """Delete authentication recoed.""" - pcf = pymqi.PCFExecute(self.qmgr) - pcf.MQCMD_SET_CHLAUTH_REC(attrs) + self.pcf.MQCMD_SET_CHLAUTH_REC(attrs) @classmethod def edit_qmgr(cls, attrs): """Edit connected Queue Manager.""" - pcf = pymqi.PCFExecute(cls.qmgr) - pcf.MQCMD_CHANGE_Q_MGR(attrs) + cls.pcf.MQCMD_CHANGE_Q_MGR(attrs) diff --git a/code/tests/test_tls.py b/code/tests/test_tls.py --- a/code/tests/test_tls.py +++ b/code/tests/test_tls.py @@ -147,11 +147,14 @@ attrs.append(pymqi.CFIN(Parameter=pymqi.CMQCFC.MQIACF_ACTION, Value=pymqi.CMQCFC.MQACT_REPLACE)) attrs.append(pymqi.CFST(Parameter=pymqi.CMQCFC.MQCACH_CLIENT_USER_ID, - String=self.user)) + String=utils.py3str2bytes(self.user))) attrs.append(pymqi.CFIN(Parameter=pymqi.CMQC.MQIA_CHECK_CLIENT_BINDING, Value=pymqi.CMQCFC.MQCHK_REQUIRED_ADMIN)) attrs.append(pymqi.CFIN(Parameter=pymqi.CMQCFC.MQIACH_USER_SOURCE, - Value=pymqi.CMQC.MQUSRC_CHANNEL)) + Value=pymqi.CMQC.MQUSRC_MAP)) + attrs.append(pymqi.CFST(Parameter=pymqi.CMQCFC.MQCACH_MCA_USER_ID, + String=b'mqm')) + self.create_auth_rec(attrs) attrs = [] @@ -160,7 +163,7 @@ attrs.append(pymqi.CFIN(Parameter=pymqi.CMQCFC.MQIACF_CHLAUTH_TYPE, Value=pymqi.CMQCFC.MQCAUT_BLOCKUSER)) attrs.append(pymqi.CFST(Parameter=pymqi.CMQCFC.MQCACH_MCA_USER_ID_LIST, - String='nobody')) + String=b'nobody')) attrs.append(pymqi.CFIN(Parameter=pymqi.CMQCFC.MQIACH_WARNING, Value=pymqi.CMQC.MQWARN_NO)) attrs.append(pymqi.CFIN(Parameter=pymqi.CMQCFC.MQIACF_ACTION, @@ -224,4 +227,4 @@ self.assertTrue(is_connected) if __name__ == '__main__': - main(module='test_pcf') + main(module='test_tls') diff --git a/setup.py b/setup.py new file mode 100644 --- /dev/null +++ b/setup.py @@ -0,0 +1,202 @@ +# Setup script usable for distutils. +# +# Original Author: Maas-Maarten Zeeman +# +# Linux build is re-entrant/multithreaded. + +# stdlib +import os +import sys +try: + from setuptools import setup +except ImportError: + from distutils.core import setup + +from distutils.core import Extension +from distutils import spawn +from struct import calcsize + +version = '1.11.1' + +# Build either in bindings or client mode. +bindings_mode = 0 +if sys.argv[-1] in ('bindings', 'server'): + bindings_mode = 1 + sys.argv = sys.argv[:-1] +if sys.argv[-1] == 'client': + bindings_mode = 0 + sys.argv = sys.argv[:-1] + +# Are we running 64bits? +if calcsize('P') == 8: + bits = 64 +else: + bits = 32 + +def get_windows_settings(): + """ Windows settings. + """ + if bits == 64: + library_dirs = [r'c:\Program Files (x86)\IBM\WebSphere MQ\tools\Lib64', + r'{}\tools\Lib64'.format(os.environ['MQ_FILE_PATH'])] + include_dirs = [r'c:\Program Files (x86)\IBM\WebSphere MQ\tools\c\include', + r'{}\tools\c\include'.format(os.environ['MQ_FILE_PATH'])] + else: + library_dirs = [r'c:\Program Files\IBM\WebSphere MQ\Tools\Lib', + r'{}\tools\Lib'.format(os.environ['MQ_FILE_PATH'])] + include_dirs = [r'c:\Program Files\IBM\WebSphere MQ\tools\c\include', + r'{}\tools\c\include'.format(os.environ['MQ_FILE_PATH'])] + + if bindings_mode: + libraries = ['mqm'] + else: + if bits == 64: + libraries = ['mqic'] + else: + libraries = ['mqic32'] + + return library_dirs, include_dirs, libraries + +def get_sunos_zlinux_settings(): + """ SunOS and z/Linux settings. + """ + if bits == 64: + library_dirs = ['/opt/mqm/lib64'] + else: + library_dirs = ['/opt/mqm/lib'] + + include_dirs = ['/opt/mqm/inc'] + + if bindings_mode: + libraries = ['mqm','mqmcs','mqmzse'] + else: + libraries = ['mqic'] + + return library_dirs, include_dirs, libraries + +def get_aix_settings(): + """ AIX settings. + """ + if bits == 64: + library_dirs = ['/usr/mqm/lib64'] + else: + library_dirs = ['/usr/mqm/lib'] + + include_dirs = ['/usr/mqm/inc'] + + if bindings_mode: + libraries = ['mqm_r'] + else: + libraries = ['mqic_r'] + + return library_dirs, include_dirs, libraries + +def get_generic_unix_settings(): + """ Generic UNIX, including Linux, settings. + """ + if bits == 64: + library_dirs = ['/opt/mqm/lib64'] + else: + library_dirs = ['/opt/mqm/lib'] + + include_dirs = ['/opt/mqm/inc'] + + if bindings_mode: + libraries = ['mqm_r'] + else: + libraries = ['mqic_r'] + + return library_dirs, include_dirs, libraries + +def get_locations_by_command_path(command_path): + """ Extracts directory locations by the path to one of MQ commands, such as dspmqver. + """ + command_dir = os.path.dirname(command_path) + mq_installation_path = os.path.abspath(os.path.join(command_dir, '..')) + + if bits == 64: + library_dirs = ['{}/lib64'.format(mq_installation_path)] + else: + library_dirs = ['{}/lib'.format(mq_installation_path)] + + include_dirs = ['{}/inc'.format(mq_installation_path)] + + if bindings_mode: + libraries = ['mqm_r'] + else: + libraries = ['mqic_r'] + + return library_dirs, include_dirs, libraries + +# Windows +if sys.platform == 'win32': + library_dirs, include_dirs, libraries = get_windows_settings() + +# SunOS and z/Linux +elif sys.platform == 'sunos5' or sys.platform == 'linux-s390': + library_dirs, include_dirs, libraries = get_sunos_zlinux_settings() + +# AIX +elif sys.platform.startswith('aix'): + library_dirs, include_dirs, libraries = get_aix_settings() + +# At this point, to preserve backward-compatibility we try out generic +# UNIX settings first, i.e. libraries and include files in well-known locations. +# Otherwise, to support Mac, we look up dspmqver in $PATH. +else: + + has_generic_lib = os.path.exists('/opt/mqm/lib64') if bits == 64 else os.path.exists('/opt/mqm/lib') + + if has_generic_lib: + library_dirs, include_dirs, libraries = get_generic_unix_settings() + + else: + + # On Mac, users can install MQ to any location so we look up + # the path that dspmqver is installed to and find the rest + # of the information needed in relation to that base directory. + dspmqver_path = spawn.find_executable('dspmqver') + + # We have found the command so we will be able to extract the relevant directories now + if dspmqver_path: + library_dirs, include_dirs, libraries = get_locations_by_command_path(dspmqver_path) + + else: + raise Exception('MQ libraries could not be found') + +if bindings_mode: + print('Building PyMQI bindings mode %sbits' % bits) +else: + print('Building PyMQI client mode %sbits' % bits) + +print('Using library_dirs:`%s`, include:`%s`, libraries:`%s`' % (library_dirs, include_dirs, libraries)) + +setup(name = 'pymqi', + version = version, + description = 'Python IBM MQI Extension for IBM MQ (formerly WebSphere MQ and MQSeries).', + long_description= 'PyMQI is a Python library for working with IBM MQ (formerly WebSphere MQ and MQSeries) implementing MQI and PCF protocols.', + author='Dariusz Suchojad', + author_email='pymqi@m.zato.io', + url='https://dsuch.github.io/pymqi/', + download_url='https://pypi.python.org/pypi/pymqi', + platforms='OS Independent', + package_dir = {'': 'code'}, + packages = ['pymqi'], + license='Python Software Foundation License', + keywords=('pymqi IBM MQ WebSphere WMQ MQSeries IBM middleware messaging queueing asynchronous SOA EAI ESB integration'), + classifiers = [ + 'Development Status :: 5 - Production/Stable', + 'License :: OSI Approved :: Python Software Foundation License', + 'Intended Audience :: Developers', + 'Natural Language :: English', + 'Operating System :: OS Independent', + 'Programming Language :: C', + 'Programming Language :: Python', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: Software Development :: Object Brokering', + ], + py_modules = ['pymqi.CMQC', 'pymqi.CMQCFC', 'pymqi.CMQXC', 'pymqi.CMQZC'], + ext_modules = [Extension('pymqi.pymqe',['code/pymqi/pymqe.c'], define_macros=[('PYQMI_BINDINGS_MODE_BUILD', bindings_mode)], + library_dirs = library_dirs, + include_dirs = include_dirs, + libraries = libraries)]) diff --git a/tox.ini b/tox.ini --- a/tox.ini +++ b/tox.ini @@ -24,3 +24,41 @@ # W291 trailing whitespace # W293 blank line contains whitespace ignore=E112,E121,E122,E123,E124,E125,E126,E127,E128,E129,E131,E225,E226,E231,E251,E261,E302,E401,E713,F403,W291,W293 + + +[testenv] +deps = + -rcode/tests/requirements.txt + pytest-cov + +commands = + py.test -x --cov=pymqi --cov-config=.coveragerc + ; flake8 code/pymqi code/tests + +setenv = + PYMQI_TEST_QM_NAME = MQTEST + PYMQI_TEST_QM_HOST = localhost + PYMQI_TEST_QM_TRANSPORT = TCP + +[testenv:local] +setenv = + PYMQI_TEST_QM_PORT = 8887 + PYMQI_TEST_QM_CHANNEL = CH1 + PYMQI_TEST_QM_USER = + PYMQI_TEST_QM_PASSWORD = + PYMQI_TEST_QM_CONN_AUTH_SUPPORTED = 0 + +[testenv:docker] +# Environment for testing with +# Docker image ibmcom/mq used as MQ Server +setenv = PYMQI_TEST_QM_PORT = 8886 + PYMQI_TEST_QM_CHANNEL = DEV.ADMIN.SVRCONN + PYMQI_TEST_QM_USER = admin + PYMQI_TEST_QM_PASSWORD = passw0rd + PYMQI_TEST_QM_CONN_AUTH_USE_PW = REQUIRED + # Without HOME error AMQ6235E occurs + HOME = /home/runner +passenv = + PYMQI_TEST_TLS_SKIP + PYMQI_TEST_TLS_KEY_REPO_LOCATION_QMGR + PYMQI_TEST_TLS_KEY_REPO_LOCATION_CLIENT