Repository: qpid-interop-test Updated Branches: refs/heads/master [created] 6d0d60388
QPIDIT-1: Intial checkin of Proton Python test shim Project: http://git-wip-us.apache.org/repos/asf/qpid-interop-test/repo Commit: http://git-wip-us.apache.org/repos/asf/qpid-interop-test/commit/6d0d6038 Tree: http://git-wip-us.apache.org/repos/asf/qpid-interop-test/tree/6d0d6038 Diff: http://git-wip-us.apache.org/repos/asf/qpid-interop-test/diff/6d0d6038 Branch: refs/heads/master Commit: 6d0d603883d3cba8af02152d23b0d72ba79c8afd Parents: Author: Kim van der Riet <[email protected]> Authored: Thu Jun 11 10:47:50 2015 -0400 Committer: Kim van der Riet <[email protected]> Committed: Thu Jun 11 10:47:50 2015 -0400 ---------------------------------------------------------------------- .project | 17 + .pydevproject | 8 + LICENSE | 202 ++++++++++ QUICKSTART | 59 +++ README | 63 +++ STATUS | 48 +++ etc/proton-python-amqp-types.patch | 215 ++++++++++ shims/qpid-proton-python/src/__init__.py | 18 + .../src/proton-python-receive | 106 +++++ shims/qpid-proton-python/src/proton-python-send | 136 +++++++ src/py/qpid-interop-test/__init__.py | 22 ++ src/py/qpid-interop-test/shim_utils.py | 388 +++++++++++++++++++ src/py/qpid-interop-test/shim_utils.pyc | Bin 0 -> 14712 bytes .../types/simple_type_tests.py | 357 +++++++++++++++++ 14 files changed, 1639 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/qpid-interop-test/blob/6d0d6038/.project ---------------------------------------------------------------------- diff --git a/.project b/.project new file mode 100644 index 0000000..ea09747 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>qpid-interop-test</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.python.pydev.PyDevBuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.python.pydev.pythonNature</nature> + </natures> +</projectDescription> http://git-wip-us.apache.org/repos/asf/qpid-interop-test/blob/6d0d6038/.pydevproject ---------------------------------------------------------------------- diff --git a/.pydevproject b/.pydevproject new file mode 100644 index 0000000..037bd25 --- /dev/null +++ b/.pydevproject @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<?eclipse-pydev version="1.0"?><pydev_project> +<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH"> +<path>/${PROJECT_DIR_NAME}</path> +</pydev_pathproperty> +<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property> +<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property> +</pydev_project> http://git-wip-us.apache.org/repos/asf/qpid-interop-test/blob/6d0d6038/LICENSE ---------------------------------------------------------------------- diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. http://git-wip-us.apache.org/repos/asf/qpid-interop-test/blob/6d0d6038/QUICKSTART ---------------------------------------------------------------------- diff --git a/QUICKSTART b/QUICKSTART new file mode 100644 index 0000000..cc7d0d9 --- /dev/null +++ b/QUICKSTART @@ -0,0 +1,59 @@ +QUICKSTART GUIDE +================ + +0. Prerequisites +---------------- +* qpid-proton built and installed +* For JMS testing, qpid-jms built and installed + +1. Get the source +----------------- +git clone https://git-wip-us.apache.org/repos/asf/qpid-interop-test.git + +2. Patch Proton and build +------------------------- +Proton needs to be patched with a change that adds support for the missing AMQP types in the current +python implementation. This patch will be proposed as a patch to Proton itself, and if accepted, +this part of the procedure will go away. Until then, the patch is located in the etc directory as +proton-python-amqp-types.patch. Copy this to the qpid-proton directory, then: + +cd <path/to/>qpid-proton +patch -p1 proton-python-amqp-types.patch +cd build/ +make +sudo make install +cd .. +mvn -DskipTests package +mvn -DskipTests install + +3. Build Qpid JMS +----------------- +cd <path/to/>qpid-jms +mvn -DskipTests package +mvn -DskipTests install + + +3 Prepare the tests +------------------- +(ONLY IF NEEDED): Edit src/py/qpid_interop/types/simple_type_tests.py as +needed, particularly AmqpPrimitiveTypes.TYPE_MAP which controls the AMQP types +and test values for each type. + +4. Run the tests (uninstalled) +------------------------------ +Note that installation is still to be completed, this section will change to +reflect installation details when complete. + +Assuming proton's make install has been run, from top level qpid-interop-test directory: +export PYTHONPATH=/usr/local/lib64/proton/bindings/python:src/py/qpid-interop-test +export LD_LIBRARY_PATH=/usr/local/lib64 +export QPID_INTEROP_TEST_HOME=<abs path to top level qpid-interop-test directory> + +Start a broker. If using qpidd: +qpidd --load-module amqp.so -m yes --auth no --queue-pattern qpid-interop.simple_type_tests --default-flow-stop-threshold 0 --default-flow-resume-threshold 0 --default-queue-limit 0 --log-enable info+ + +NOTE: YOU MUST USE THE --queue-pattern qpid-interop.simple_type_tests parameter with the Qpid broker! + +From top level directory: +./src/py/qpid-interop-test/types/simple_type_tests.py + http://git-wip-us.apache.org/repos/asf/qpid-interop-test/blob/6d0d6038/README ---------------------------------------------------------------------- diff --git a/README b/README new file mode 100644 index 0000000..8fc1d3c --- /dev/null +++ b/README @@ -0,0 +1,63 @@ +Qpid Client Interoperability Test Suite +--------------------------------------- + +This directory contains the Qpid Proton Client API test suite. + +All files in this directory are under the Apache License - see LICENSE for +details. + +Source Code +----------- +Source code may be obtained from Apache git server +https://git-wip-us.apache.org/repos/asf/qpid-interop-test.git + +Documentation +------------- +A quickstart guide for building and using this test suite is contained in +QUICKSTART. Detailed documentation for adding tests and using them are +(will be) contained in the docs directory. + +Issues +------ +Issues are tracked in the Apache JIRA at +https://issues.apache.org/jira/browse/QPIDIT/?selectedTab=com.atlassian.jira.jira-projects-plugin:summary-panel + +Support +------- +Support may be obtained from the qpid-users mailing list [email protected] + +Writing shims for new clients +----------------------------- +A detailed description of this process is (will be) contained in the docs +directory. The very short version of this is as follows: + +1. Write a pair of client programs using the client API under test. + +The first is a sender which reads the following from the command-line: + +<broker address> <amqp type> <test value 1> <test value 2> ... + +and is responsible for sending messages containing the test values each in a +single message in the appropriate AMQP type format. + +The second client program is a receiver, and must read the following from the +command-line + +<broker address> <amqp type> <num messages> + +and is responsible for receiving <num messages> messages from the broker and +printing the bodies of the received messages appropriately decoded for type +<amqp type>. The printed output will contain the amqp type on the first line +and each received message body on subsequent lines, one message per line. + +2. Add a subclass for this client in +src/py/qpid-interop-test/types/simple_type_test.py +derived from class Shim and which overrides NAME, ENV (as needed), SHIM_LOC, +SEND and RECEIVE. SEND and RECEIVE must point to the two clients written +in step 1 above. + +3. Add an instance of your new shim class to SHIM_MAP keyed against its name. + +That's it! + http://git-wip-us.apache.org/repos/asf/qpid-interop-test/blob/6d0d6038/STATUS ---------------------------------------------------------------------- diff --git a/STATUS b/STATUS new file mode 100644 index 0000000..40df1b1 --- /dev/null +++ b/STATUS @@ -0,0 +1,48 @@ +1. AMQP type tests: +------------------- +Main test launcher: +Python program src/py/qpid-interop-test/types/simple_type_tests.py + +The launcher uses "shims", each of which is a simple pair of test programs +which receive their instructions through the command-line. These are written +using the target test client API. One program is a sender and is responsible +for sending test messages, while the other is a receiver and is responsible +for recieving the test messages and returning them to the launcher. + +1.1 Qpid-proton-python shim +--------------------------- +Status: Working. + AMQP types: + null - implemented + boolean - implemented + ubyte - implemented with Proton patch support for this type + ushort - implemented with Proton patch support for this type + uint - implemented with Proton patch support for this type + ulong - implemented + byte - implemented with Proton patch support for this type - fails in swig interface + short - implemented with Proton patch support for this type + int - implemented with Proton patch support for this type + long - implemented + float - implemented with Proton patch support for this type + double - implemented + decimal32 - implemented with Proton patch support for this type - fails in swig interface + decimal64 - implemented with Proton patch support for this type - fails in swig interface + decimal128 - implemented with Proton patch support for this type - not working, commented out + char - implemented - not working, commented out + timestamp - implemented + uuid - implemented + binary - implemented + string - implemented + symbol - implemented + list - implemented + map - implemented - test broken + array - not yet implemented - TODO + +1.2 Qpid-JMS shim +----------------- +Status: In progress, AMQP string type works with Qpid-proton-python. + + +2. AMQP functionality tests: +---------------------------- +Not yet implemented \ No newline at end of file http://git-wip-us.apache.org/repos/asf/qpid-interop-test/blob/6d0d6038/etc/proton-python-amqp-types.patch ---------------------------------------------------------------------- diff --git a/etc/proton-python-amqp-types.patch b/etc/proton-python-amqp-types.patch new file mode 100644 index 0000000..eabe25a --- /dev/null +++ b/etc/proton-python-amqp-types.patch @@ -0,0 +1,215 @@ +From bc720193ab06730e8b69b01b9ada314f62660084 Mon Sep 17 00:00:00 2001 +From: Kim van der Riet <[email protected]> +Date: Tue, 12 May 2015 11:44:54 -0400 +Subject: [PATCH 1924/1924] Added python classes to represent AMQP types not + currently supported + +--- + proton-c/bindings/python/proton/__init__.py | 110 +++++++++++++++++++++++----- + 1 file changed, 90 insertions(+), 20 deletions(-) + +diff --git a/proton-c/bindings/python/proton/__init__.py b/proton-c/bindings/python/proton/__init__.py +index e3cd9e3..26ad1f3 100644 +--- a/proton-c/bindings/python/proton/__init__.py ++++ b/proton-c/bindings/python/proton/__init__.py +@@ -1275,6 +1275,56 @@ class char(unicode): + def __repr__(self): + return "char(%s)" % unicode.__repr__(self) + ++class byte(int): ++ ++ def __repr__(self): ++ return "byte(%s)" % int.__repr__(self) ++ ++class short(int): ++ ++ def __repr__(self): ++ return "short(%s)" % int.__repr__(self) ++ ++class int32(int): ++ ++ def __repr__(self): ++ return "int32(%s)" % int.__repr__(self) ++ ++class ubyte(int): ++ ++ def __repr__(self): ++ return "ubyte(%s)" % int.__repr__(self) ++ ++class ushort(int): ++ ++ def __repr__(self): ++ return "ushort(%s)" % int.__repr__(self) ++ ++class uint(int): ++ ++ def __repr__(self): ++ return "uint(%s)" % int.__repr__(self) ++ ++class float32(float): ++ ++ def __repr__(self): ++ return "float32(%s)" % float.__repr__(self) ++ ++class decimal32(int): ++ ++ def __repr__(self): ++ return "decimal32(%s)" % int.__repr__(self) ++ ++class decimal64(int): ++ ++ def __repr__(self): ++ return "decimal64(%s)" % int.__repr__(self) ++ ++class decimal128(bytes): ++ ++ def __repr__(self): ++ return "decimal128(%s)" % bytes.__repr__(self) ++ + class Described(object): + + def __init__(self, descriptor, value): +@@ -1887,42 +1937,42 @@ class Data: + If the current node is an unsigned byte, returns its value, + returns 0 otherwise. + """ +- return pn_data_get_ubyte(self._data) ++ return ubyte(pn_data_get_ubyte(self._data)) + + def get_byte(self): + """ + If the current node is a signed byte, returns its value, returns 0 + otherwise. + """ +- return pn_data_get_byte(self._data) ++ return byte(pn_data_get_byte(self._data)) + + def get_ushort(self): + """ + If the current node is an unsigned short, returns its value, + returns 0 otherwise. + """ +- return pn_data_get_ushort(self._data) ++ return ushort(pn_data_get_ushort(self._data)) + + def get_short(self): + """ + If the current node is a signed short, returns its value, returns + 0 otherwise. + """ +- return pn_data_get_short(self._data) ++ return short(pn_data_get_short(self._data)) + + def get_uint(self): + """ + If the current node is an unsigned int, returns its value, returns + 0 otherwise. + """ +- return pn_data_get_uint(self._data) ++ return uint(pn_data_get_uint(self._data)) + + def get_int(self): + """ + If the current node is a signed int, returns its value, returns 0 + otherwise. + """ +- return pn_data_get_int(self._data) ++ return int32(pn_data_get_int(self._data)) + + def get_char(self): + """ +@@ -1957,7 +2007,7 @@ class Data: + If the current node is a float, returns its value, raises 0 + otherwise. + """ +- return pn_data_get_float(self._data) ++ return float32(pn_data_get_float(self._data)) + + def get_double(self): + """ +@@ -1972,7 +2022,7 @@ class Data: + If the current node is a decimal32, returns its value, returns 0 + otherwise. + """ +- return pn_data_get_decimal32(self._data) ++ return decimal32(pn_data_get_decimal32(self._data)) + + # XXX: need to convert + def get_decimal64(self): +@@ -1980,7 +2030,7 @@ class Data: + If the current node is a decimal64, returns its value, returns 0 + otherwise. + """ +- return pn_data_get_decimal64(self._data) ++ return decimal64(pn_data_get_decimal64(self._data)) + + # XXX: need to convert + def get_decimal128(self): +@@ -1988,7 +2038,7 @@ class Data: + If the current node is a decimal128, returns its value, returns 0 + otherwise. + """ +- return pn_data_get_decimal128(self._data) ++ return decimal128(pn_data_get_decimal128(self._data)) + + def get_uuid(self): + """ +@@ -2140,19 +2190,29 @@ class Data: + put_mappings = { + None.__class__: lambda s, _: s.put_null(), + bool: put_bool, +- dict: put_dict, +- list: put_sequence, +- tuple: put_sequence, +- unicode: put_string, +- bytes: put_binary, +- symbol: put_symbol, ++ ubyte: put_ubyte, ++ ushort: put_ushort, ++ uint: put_uint, ++ ulong: put_ulong, ++ byte: put_byte, ++ short: put_short, ++ int32: put_int, + int: put_long, +- char: put_char, + long: put_long, +- ulong: put_ulong, +- timestamp: put_timestamp, ++ float32: put_float, + float: put_double, ++ decimal32: put_decimal32, ++ decimal64: put_decimal64, ++ decimal128: put_decimal128, ++ char: put_char, ++ timestamp: put_timestamp, + uuid.UUID: put_uuid, ++ bytes: put_binary, ++ unicode: put_string, ++ symbol: put_symbol, ++ list: put_sequence, ++ tuple: put_sequence, ++ dict: put_dict, + Described: put_py_described, + Array: put_py_array + } +@@ -3998,5 +4058,15 @@ __all__ = [ + "dispatch", + "symbol", + "timestamp", +- "ulong" ++ "ulong", ++ "byte", ++ "short", ++ "int32", ++ "ubyte", ++ "ushort", ++ "uint", ++ "float32", ++ "decimal32", ++ "decimal64", ++ "decimal128" + ] +-- +1.9.3 + http://git-wip-us.apache.org/repos/asf/qpid-interop-test/blob/6d0d6038/shims/qpid-proton-python/src/__init__.py ---------------------------------------------------------------------- diff --git a/shims/qpid-proton-python/src/__init__.py b/shims/qpid-proton-python/src/__init__.py new file mode 100644 index 0000000..31d5a2e --- /dev/null +++ b/shims/qpid-proton-python/src/__init__.py @@ -0,0 +1,18 @@ +# +# 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. +# http://git-wip-us.apache.org/repos/asf/qpid-interop-test/blob/6d0d6038/shims/qpid-proton-python/src/proton-python-receive ---------------------------------------------------------------------- diff --git a/shims/qpid-proton-python/src/proton-python-receive b/shims/qpid-proton-python/src/proton-python-receive new file mode 100755 index 0000000..080782d --- /dev/null +++ b/shims/qpid-proton-python/src/proton-python-receive @@ -0,0 +1,106 @@ +#!/usr/bin/env python +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Issues: +# * Capturing errors from client or broker +# * Correct Message body types, use of cproton.Data class? + +import sys +from proton.handlers import MessagingHandler +from proton.reactor import Container +from struct import pack, unpack + +class ReceiverError(StandardError): + def __init__(self, error_message): + super(ReceiverError, self).__init__(error_message) + +class Receiver(MessagingHandler): + def __init__(self, url, amqp_type, expected_num_messages): + super(Receiver, self).__init__() + self.url = url + self.amqp_type = amqp_type + self.received_value_list = [] + self.expected = int(expected_num_messages) + self.received = 0 + + def get_received_value_list(self): + return self.received_value_list + + def on_start(self, event): + event.container.create_receiver(self.url) + + def on_message(self, event): + if event.message.id and event.message.id < self.received: + return # ignore duplicate message + if self.expected == 0 or self.received < self.expected: + if self.amqp_type == 'null' or \ + self.amqp_type == 'boolean' or \ + self.amqp_type == 'ubyte' or \ + self.amqp_type == 'ushort' or \ + self.amqp_type == 'uint' or \ + self.amqp_type == 'ulong' or \ + self.amqp_type == 'byte' or \ + self.amqp_type == 'short' or \ + self.amqp_type == 'int' or \ + self.amqp_type == 'long' or \ + self.amqp_type == 'decimal32' or \ + self.amqp_type == 'decimal64' or \ + self.amqp_type == 'decimal128' or \ + self.amqp_type == 'timestamp' or \ + self.amqp_type == 'uuid': + self.received_value_list.append(str(event.message.body)) + elif self.amqp_type == 'float': + self.received_value_list.append('0x%08x' % unpack('!L', pack('!f', event.message.body))[0]) + elif self.amqp_type == 'double': + self.received_value_list.append('0x%016x' % unpack('!Q', pack('!d', event.message.body))[0]) + elif self.amqp_type == 'decimal128': + self.received_value_list.append(event.message.body.encode('hex')) + elif self.amqp_type == 'char' or \ + self.amqp_type == 'binary' or \ + self.amqp_type == 'string' or \ + self.amqp_type == 'symbol': + self.received_value_list.append(event.message.body) + elif self.amqp_type == 'list' or \ + self.amqp_type == 'map': + self.received_value_list.append(event.message.body) + else: + print 'proton-python-receive: Unsupported AMQP type "%s"' % self.amqp_type + return + self.received += 1 + if self.received == self.expected: + event.receiver.close() + event.connection.close() + +# --- main --- +# Args: 1: Broker address (ip-addr:port) +# 2: Queue name +# 3: AMQP type +# 4: Expected number of test values to receive +try: + rcv = Receiver('%s/%s' % (sys.argv[1], sys.argv[2]), sys.argv[3], sys.argv[4]) + Container(rcv).run() + print sys.argv[3] + for val in rcv.get_received_value_list(): + print val +except KeyboardInterrupt: + pass +except Exception as e: + print 'proton-python-receive EXCEPTION:', e + http://git-wip-us.apache.org/repos/asf/qpid-interop-test/blob/6d0d6038/shims/qpid-proton-python/src/proton-python-send ---------------------------------------------------------------------- diff --git a/shims/qpid-proton-python/src/proton-python-send b/shims/qpid-proton-python/src/proton-python-send new file mode 100755 index 0000000..2b8703d --- /dev/null +++ b/shims/qpid-proton-python/src/proton-python-send @@ -0,0 +1,136 @@ +#!/usr/bin/env python +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Issues: +# * Capturing errors from client or broker +# * Correct Message body types, use of cproton.Data class? + +import sys +from ast import literal_eval +from proton import byte, char, decimal32, decimal64, decimal128, float32, int32, Message, short, symbol, timestamp, ubyte, uint, ulong, ushort +from proton.handlers import MessagingHandler +from proton.reactor import Container +from shim_utils import StrToObj +from struct import unpack +from traceback import format_exc +from uuid import UUID + +class Sender(MessagingHandler): + def __init__(self, url, amqp_type, test_value_list): + super(Sender, self).__init__() + self.url = url + self.amqp_type = amqp_type + self.test_value_list = test_value_list + self.sent = 0 + self.confirmed = 0 + self.total = len(test_value_list) + + def on_start(self, event): + event.container.create_sender(self.url) + + def on_sendable(self, event): + if self.sent == 0: + for test_value in self.test_value_list: + if event.sender.credit: + message = self.create_message(test_value) + if message is not None: + event.sender.send(message) + self.sent += 1 + else: + event.connection.close() + return + + def create_message(self, test_value): + # Non-string types using literal_eval + if self.amqp_type == 'null': + return Message(id=(self.sent+1), body=None) + elif self.amqp_type == 'boolean': + return Message(id=(self.sent+1), body=True if test_value == 'True' else False) + elif self.amqp_type == 'ubyte': + return Message(id=(self.sent+1), body=ubyte(literal_eval(test_value))) + elif self.amqp_type == 'ushort': + return Message(id=(self.sent+1), body=ushort(literal_eval(test_value))) + elif self.amqp_type == 'uint': + return Message(id=(self.sent+1), body=uint(literal_eval(test_value))) + elif self.amqp_type == 'ulong': + return Message(id=(self.sent+1), body=ulong(literal_eval(test_value))) + elif self.amqp_type == 'byte': + return Message(id=(self.sent+1), body=byte(literal_eval(test_value))) + elif self.amqp_type == 'short': + return Message(id=(self.sent+1), body=short(literal_eval(test_value))) + elif self.amqp_type == 'int': + return Message(id=(self.sent+1), body=int32(literal_eval(test_value))) + elif self.amqp_type == 'long': + return Message(id=(self.sent+1), body=long(literal_eval(test_value))) + elif self.amqp_type == 'float': + return Message(id=(self.sent+1), body=float32(unpack('!f', test_value[2:].decode('hex'))[0])) + elif self.amqp_type == 'double': + return Message(id=(self.sent+1), body=unpack('!d', test_value[2:].decode('hex'))[0]) + elif self.amqp_type == 'decimal32': + return Message(id=(self.sent+1), body=decimal32(literal_eval(test_value))) + elif self.amqp_type == 'decimal64': + return Message(id=(self.sent+1), body=decimal64(literal_eval(test_value))) + elif self.amqp_type == 'decimal128': + return Message(id=(self.sent+1), body=decimal128(test_value.decode('hex'))) + elif self.amqp_type == 'char': + return Message(id=(self.sent+1), body=char(test_value)) + elif self.amqp_type == 'timestamp': + return Message(id=(self.sent+1), body=timestamp(literal_eval(test_value))) + elif self.amqp_type == 'uuid': + return Message(id=(self.sent+1), body=UUID(test_value)) + elif self.amqp_type == 'binary': + return Message(id=(self.sent+1), body=bytes(test_value)) + elif self.amqp_type == 'string': + return Message(id=(self.sent+1), body=unicode(test_value)) + elif self.amqp_type == 'symbol': + return Message(id=(self.sent+1), body=symbol(test_value)) + elif self.amqp_type == 'list': + return Message(id=(self.sent+1), body=StrToObj(list(test_value).__iter__()).run()) + elif self.amqp_type == 'map': + return Message(id=(self.sent+1), body=StrToObj(list(test_value).__iter__()).run()) + else: + print 'proton-python-send: Unsupported AMQP type "%s"' % self.amqp_type + return None + + def on_accepted(self, event): + self.confirmed += 1 + if self.confirmed == self.total: + event.connection.close() + + def on_disconnected(self, event): + self.sent = self.confirmed + + @staticmethod + def _map_string_to_map(str_list): + return {} + +# --- main --- +# Args: 1: Broker address (ip-addr:port) +# 2: Queue name +# 3: AMQP type +# 4...n: Test values as strings +try: + Container(Sender('%s/%s' % (sys.argv[1], sys.argv[2]), sys.argv[3], sys.argv[4:])).run() +except KeyboardInterrupt: + pass +except Exception as e: + print 'proton-python-send EXCEPTION:', e + print format_exc() + \ No newline at end of file http://git-wip-us.apache.org/repos/asf/qpid-interop-test/blob/6d0d6038/src/py/qpid-interop-test/__init__.py ---------------------------------------------------------------------- diff --git a/src/py/qpid-interop-test/__init__.py b/src/py/qpid-interop-test/__init__.py new file mode 100644 index 0000000..7cecede --- /dev/null +++ b/src/py/qpid-interop-test/__init__.py @@ -0,0 +1,22 @@ +# +# 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 shim_utils +import types + http://git-wip-us.apache.org/repos/asf/qpid-interop-test/blob/6d0d6038/src/py/qpid-interop-test/shim_utils.py ---------------------------------------------------------------------- diff --git a/src/py/qpid-interop-test/shim_utils.py b/src/py/qpid-interop-test/shim_utils.py new file mode 100755 index 0000000..ba70654 --- /dev/null +++ b/src/py/qpid-interop-test/shim_utils.py @@ -0,0 +1,388 @@ +#!/usr/bin/env python + +""" +Module containing utilities for the shims used in qpid-interop. +""" + +# +# 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 sys +import proton +from ast import literal_eval +from uuid import UUID + +class StrToObj(object): + """ + Class StrObj consumes the characters from a string iterator str_itr + and interprets the string so as to create the equivalent python + object(s) in a manner similar to ast.leteral_eval(). The difference + is that this version supports proton types as a part of the + interpretation, so that a string such as + + '[1, char('a'), timestamp(1234567)]' + + can be parsed. + + The following compound python types can be handled: + [...] list + (...) tuple + {...} dict + which may contain any legal combination of simple python types + including strings, embedded lists, tuples and dicts, and the + supported proton types. All of the simple types must be supported + by ast.leteral_eval() to be supported in this class. + + The following proton types are handled: + proton.char + proton.symbol + proton.timestamp + proton.ulong + + Typical usage: + ------------- + For a python string object str containing the string to be parsed, + it is necessary to obtain an iterator to the individual characters + of the string. This can be done as follows: + + obj = StrToObj(list(str).__iter__()).run() + + where obj will contain an instance of the object described in the + string. The run() method starts the process of consuming characters + from the iterator and interpreting them. + + This class is also the parent class of a number of more specialized + subclasses for handling sub-sections of the string interpretation: + + StrToObj (this class) + ^ + | + +----------+-------------+ + | | | + StrToStr StrToType StrToSeq + ^ + | + +-------------+-----------+ + | | | + StrToList StrToTuple StrToDict + + and which make up all the elements of the composite types needed + for this application. Simple types are handled by calling + ast.literal_evel(), and the types that can be handled are + determined by that function. + """ + + def __init__(self, str_itr): + self._str_itr = str_itr + self._done = False + self._obj = None + self._str_buff = '' + + def get_obj(self): + """ + Return object created as a result of parsing the string + supplied to the constructor and using the run() method. + """ + return self._obj + + def run(self): + """ + Starts the process of 'consuming' characters from the supplied + string iterator and of interpreting them in the context of the + previous characters. + """ + try: + while not self._done: + char = self._str_itr.next() + if not self._process_char(char): + self._str_buff += char + except StopIteration: + if len(self._str_buff) > 0 and self._obj is None: + self._obj = literal_eval(self._str_buff) + self._str_buff = '' + return self._obj + + def _add_elt(self, elt): + """ + Sets the object itself to parameter elt. This function is + frequently overloaded by its subclasses. + """ + self._obj = elt + + def _process_char(self, char): + """ + Consume and process a single character in the context of those + that have passed before. This function is overloaded by its + subclasses to provide either additional or alternative + processing. + + This function checks for the characters that start a list + ('['), a dict ('{'), a tuple ('(' when preceded only by + whitespace), a string ('\'' or '"') or special proton types + ('(' when preceded by a constructor string). + + If any of these chars are found, a new object of the + appropriate class is constructed to handle the processing of + that type, and when its run() function completes, a constructed + object will be returned. + """ + if char == '[': + self._add_elt(StrToList(char, self._str_itr).run()) + return True + if char == '{': + self._add_elt(StrToDict(char, self._str_itr).run()) + return True + if char == '(': + if len(self._str_buff.strip()) == 0: + self._add_elt(StrToTuple(char, self._str_itr).run()) + else: + self._add_elt(StrToType(char, self._str_itr, + self._str_buff).run()) + self._str_buff = '' + return True + if char == '\'' or char == '"': + str_prefix = None + if len(self._str_buff.strip()) > 0: + str_prefix = self._str_buff.strip() + self._add_elt(StrToStr(char, self._str_itr, str_prefix).run()) + self._str_buff = '' + return True + return False + + +class StrToStr(StrToObj): + """ + Class to consume a string delimited by either ' or " chars. + """ + + def __init__(self, delim_char, str_itr, str_prefix): + super(StrToStr, self).__init__(str_itr) + self._delim_char = delim_char + self._str_prefix = str_prefix + self._escape_flag = False + + def _process_char(self, char): + """ + This function processes a python string type, and continues + consuming characters until another valid delimiter character + ('\'' or '"') is encountered. A delimiter character that is + preceded by an escape character ('\\') is excluded. + + The entire string may have a prefix of one or more characters. + Only the u prefix (eg u'hello') has any effect; the b prefix + has no effect in Python 2.x but paves the way for Python 3.x + where it does have significance, and the r prefix affects the + display or printing of strings only. + """ + if char == '\\': + self._escape_flag = not self._escape_flag + elif char == self._delim_char and not self._escape_flag: + if self._str_prefix is None: + self._obj = self._str_buff + elif 'u' in self._str_prefix or 'U' in self._str_prefix: + self._obj = unicode(self._str_buff) + else: + self._obj = self._str_buff # Ignore other prefixes (b, B, r, R) + self._str_buff = '' + self._done = True + return True + else: + self._escape_flag = False + return False + + +class StrToType(StrToObj): + """ + Class for processing special proton python types. + """ + + def __init__(self, _, str_itr, type_str): + super(StrToType, self).__init__(str_itr) + self._type_str = type_str.strip() + self._val = None + + def _add_elt(self, elt): + """ + Sets the value portion of the type to elt. This is then used + in the constructor of the type. + """ + self._val = elt + + def _process_char(self, char): + """ + Process characters to identify the value to the constructor of + the proton type being processed. The proton type string is + passed to the constructor, and when the '(' char is encountered, + this function is used until the matching ')' char is reached. + + The parent StrToObj._process_char() is called first to process + any compound types and strings which may be present as a + constructor value. + """ + if super(StrToType, self)._process_char(char): + return True + if char == ')': + if len(self._str_buff.strip()) > 0: + if self._val is not None: + # This condition should not ever arise, either + # self._val is set OR self._str_buff contains a + # value, but not both. + raise RuntimeError('self._val=%s and self._str_buff=%s' % + (self._val, self._str_buff)) + self._val = literal_eval( + self._str_buff[self._str_buff.find('(')+1:]) + if self._type_str == 'ubyte': + self._obj = proton.ubyte(self._val) + elif self._type_str == 'ushort': + self._obj = proton.ushort(self._val) + elif self._type_str == 'uint': + self._obj = proton.uint(self._val) + elif self._type_str == 'ulong': + self._obj = proton.ulong(self._val) + elif self._type_str == 'byte': + self._obj = proton.byte(self._val) + elif self._type_str == 'short': + self._obj = proton.short(self._val) + elif self._type_str == 'int32': + self._obj = proton.int32(self._val) + elif self._type_str == 'float32': + self._obj = proton.float32(self._val) + elif self._type_str == 'decimal32': + self._obj = proton.decimal32(self._val) + elif self._type_str == 'decimal64': + self._obj = proton.decimal64(self._val) + elif self._type_str == 'decimal128': + self._obj = proton.decimal128(self._val) + elif self._type_str == 'char': + self._obj = proton.char(self._val) + elif self._type_str == 'symbol': + self._obj = proton.symbol(self._val) + elif self._type_str == 'timestamp': + self._obj = proton.timestamp(self._val) + elif self._type_str == 'UUID': + self._obj = UUID(self._val) + else: + raise ValueError('StrToType: unknown type \'%s\'' % self._type_str) + self._str_buff = '' + self._done = True + return True + + +class StrToSeq(StrToObj): + """ + Class which consumes comma-delimited sequence types such as lists, + dicts and tuples. + """ + + def __init__(self, _, str_itr, close_char, seq_type): + super(StrToSeq, self).__init__(str_itr) + self._close_char = close_char + self._obj = seq_type() + + def _process_char(self, char): + """ + Processing for container sequence types that use a ',' + character as an element delimiter. Individual elements may be + any legal type, including proton types and nested containers. + """ + if super(StrToSeq, self)._process_char(char): + return True + if char == ',' or char == self._close_char: + if char == self._close_char: + self._done = True + if len(self._str_buff.strip()) > 0: + self._add_elt(literal_eval(self._str_buff.strip())) + self._str_buff = '' + return True + return False + +class StrToList(StrToSeq): + """ + Class which consumes a list of the form '[...]'. + """ + + def __init__(self, char, str_itr): + super(StrToList, self).__init__(char, str_itr, ']', list) + + def _add_elt(self, elt): + """ + Adds an additional element into the list object. + """ + self._obj.append(elt) + +class StrToTuple(StrToSeq): + """ + Class which consumes a tuple of the form '(...)'. Tuples without + the enclosing braces are not currently supported, however. + """ + + def __init__(self, char, str_itr): + super(StrToTuple, self).__init__(char, str_itr, ')', tuple) + + def _add_elt(self, elt): + """ + Adds an additional element into the tuple object. + """ + self._obj = self._obj + (elt,) + +class StrToDict(StrToSeq): + """ + Class which consumed a dict of the form '{...}'. + """ + + def __init__(self, c, str_itr): + super(StrToDict, self).__init__(c, str_itr, '}', dict) + self._key = None + + def _add_elt(self, elt): + """ + Called twice for dicts; the first call sets the key object, + and the second call sets the value object and then inserts + the entire key:value pair into the map. + """ + if self._key is None: + self._key = elt + else: + self._obj[self._key] = elt + self._key = None + + def _process_char(self, c): + """ + Processing of characters for the python dict type using the + 'key:value' syntax. The key and value may be any legal python + or proton type, including embedded containers. + """ + if super(StrToDict, self)._process_char(c): + return True + if c == ':': + if len(self._str_buff.strip()) > 0: + self._add_elt(literal_eval(self._str_buff.strip())) + self._str_buff = '' + return True + return False + +# --- main --- + +# This command-line entry point is for testing only, it does not +# provide any useful functionality on its own. + +#if len(sys.argv) == 2: +# print '%r' % StrToObj(list(sys.argv[1]).__iter__()).run() +#else: +# print 'Usage: shim_utils <string>' http://git-wip-us.apache.org/repos/asf/qpid-interop-test/blob/6d0d6038/src/py/qpid-interop-test/shim_utils.pyc ---------------------------------------------------------------------- diff --git a/src/py/qpid-interop-test/shim_utils.pyc b/src/py/qpid-interop-test/shim_utils.pyc new file mode 100644 index 0000000..49b13d2 Binary files /dev/null and b/src/py/qpid-interop-test/shim_utils.pyc differ http://git-wip-us.apache.org/repos/asf/qpid-interop-test/blob/6d0d6038/src/py/qpid-interop-test/types/simple_type_tests.py ---------------------------------------------------------------------- diff --git a/src/py/qpid-interop-test/types/simple_type_tests.py b/src/py/qpid-interop-test/types/simple_type_tests.py new file mode 100755 index 0000000..2e3fc20 --- /dev/null +++ b/src/py/qpid-interop-test/types/simple_type_tests.py @@ -0,0 +1,357 @@ +#!/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 unittest + +from ast import literal_eval +from itertools import product +from os import getenv +from proton import char, int32, symbol, timestamp, ulong +from shim_utils import StrToObj +from subprocess import check_output +from time import mktime, time +from uuid import UUID, uuid4 + +QPID_INTEROP_TEST_HOME = getenv('QPID_INTEROP_TEST_HOME') # TODO - propose a sensible default when installation details are worked out + + +class SimpleTypeTestError(StandardError): + """ + Error class for use in simpe AMQP type tests + """ + def __init__(self, error_message): + super(SimpleTypeTestError, self).__init__(error_message) + + +class AmqpPrimitiveTypes(object): + """ + 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, 0x01, 0xff, 0x100, 0x7fffffffffffffff, 0x8000000000000000, 0xffffffffffffffff], + 'byte': [0x0, 0x7f, 0x80, 0xff], + 'short': [-0x8000, -0x1, 0x0, 0x7fff], + 'int': [-0x80000000, -0x1, 0x0, 0x7fffffff], + 'long': [-0x8000000000000000, -0x81, -0x80, -0x01, 0x0, 0x7f, 0x80, 0x7fffffffffffffff], + # Because of difficulty with rounding of floating point numbers, we use the binary representation instead + # which should be exact when comparing. + '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 + '0xff800000', # -Infinity + '0x7fffffff', # +NaN + '0xffffffff'], # -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 positive normalized number + '0x7fefffffffffffff', # Largest positive normalized number + '0xffefffffffffffff', # Largest negative normalized number + '0x7ff0000000000000', # +Infinity + '0xfff0000000000000', # -Infinity + '0x7fffffffffffffff', # +NaN + '0xffffffffffffffff'], # -NaN + 'decimal32': [0, 100, -1000],#, + 'decimal64': [0, 100, -1000],#, + #'decimal128': [b'00000000000000000000000000000000', + # b'00000000000000000000000000000100', + # b'0102030405060708090a0b0c0d0e0f00'], + #'char': [u'a', u'Z', u'0', u'\x01', u'\x7f'], #TODO: Char value \x00 causes problems in check_output(), find another solution + # timestamp must be in milliseconds since the unix epoch + 'timestamp': [0, int(mktime((2000, 1, 1, 0, 0, 0, 5, 1, 0))*1000), int(time()*1000)], + 'uuid': [UUID(int=0x0), UUID('00010203-0405-0607-0809-0a0b0c0d0e0f'), uuid4()], + 'binary': [bytes(), bytes(12345), b'Hello, world!', b'\x01\x02\x03\x04\x05\xff', + b'The quick brown fox jumped over the lazy cow 0123456789' * 1000], + # 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 cow 0123456789' * 1000], + 'symbol': ['', 'myDomain.123', 'domain.0123456789.' * 1000], + 'list': [[], + [1, -2, 3.14], + [u'a', u'b', u'c'], + [ulong(12345), timestamp(int(time()*1000)), int32(-25), uuid4(), symbol('a.b.c')], + [[], None, [1,2,3], {1:'one', 2:'two', 3:'three', 4:True, 5:False, 6:None}, True, False, char(u'5')], + [[],[[],[[],[],[]],[]],[]], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] * 1000], + 'map': [{}, {1:u'one', 2:u'two'}, {None:None, 1:1, '2':'2', True:False, False:True}]#, + #'array': [[], [1,2,3], ['Hello', 'world']] + } + + @staticmethod + def get_type_list(): + """Return a list of simple AMQP types which this test suite supports""" + return AmqpPrimitiveTypes.TYPE_MAP.keys() + + @staticmethod + def get_test_value_list(amqp_type): + """Return a list of test values to use when testing the supplied AMQP type.""" + if amqp_type not in AmqpPrimitiveTypes.TYPE_MAP.keys(): + return None + return AmqpPrimitiveTypes.TYPE_MAP[amqp_type] + + +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: + queue_name = 'qpid-interop.simple_type_tests.%s.%s.%s' % (amqp_type, send_shim.NAME, receive_shim.NAME) + send_error_text = send_shim.send(broker_addr, queue_name, amqp_type, test_value_list) + if len(send_error_text) > 0: + self.fail('Send shim \'%s\':\n%s' % (send_shim.NAME, send_error_text)) + receive_text = receive_shim.receive(broker_addr, queue_name, amqp_type, len(test_value_list)) + if type(receive_text) is list: + self.assertEqual(receive_text, test_value_list, msg='\n sent:%s\nreceived:%s' % \ + (test_value_list, receive_text)) + else: + self.fail(receive_text) + else: + self.fail('Type %s has no test values' % amqp_type) + + +def create_testcase_class(broker_addr, amqp_type, test_value_list, 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, broker_addr, send_shim, receive_shim): + """Function which creates a new test method in class cls""" + + 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' % (send_shim.NAME, receive_shim.NAME) + inner_test_method.__doc__ = 'AMQP type \'%s\' interop test: %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': test_value_list} + new_class = type(class_name, (AmqpTypeTestCase,), class_dict) + for send_shim, receive_shim in shim_product: + add_test_method(new_class, broker_addr, send_shim, receive_shim) + return new_class + + +class Shim(object): + """ + Abstract shim class, parent of all shims. + """ + NAME = None + ENV = [] + SHIM_LOC = None + SEND = None + RECEIVE = None + + def send(self, broker_addr, queue_name, amqp_type, test_value_list): + """ + Send the values of type amqp_type in test_value_list to queue queue_name. Return output (if any) from stdout. + """ + arg_list = [self.SEND, broker_addr, queue_name, amqp_type] + for test_value in test_value_list: + if amqp_type == 'string' or amqp_type == 'char' or amqp_type == 'float' or amqp_type == 'double': + arg_list.append(test_value) # Not using str() on strings preserves the unicode prefix u'...' + else: + arg_list.append(str(test_value)) + #print + #print '>>>', arg_list + return check_output(arg_list) + + def receive(self, broker_addr, queue_name, amqp_type, num_test_values): + """ + Receive num_test_values messages containing type amqp_type from queue queue_name. If the first line returned + from stdout is the AMQP type, then the rest is assumed to be the returned test value list. Otherwise error + output is assumed. + """ + try: + arg_list = [self.RECEIVE, broker_addr, queue_name, amqp_type, str(num_test_values)] + #print '>>>', arg_list + output = check_output(arg_list) + str_tvl = output.split('\n')[0:-1] # remove trailing \n + if str_tvl[0] == amqp_type: + received_test_value_list = [] + for stv in str_tvl[1:]: + # Non-string types using literal_eval + if amqp_type == 'null' or \ + amqp_type == 'boolean' or \ + amqp_type == 'ubyte' or \ + amqp_type == 'ushort' or \ + amqp_type == 'uint' or \ + amqp_type == 'ulong' or \ + amqp_type == 'byte' or \ + amqp_type == 'short' or \ + amqp_type == 'int' or \ + amqp_type == 'long' or \ + amqp_type == 'decimal32' or \ + amqp_type == 'decimal64' or \ + amqp_type == 'timestamp': + received_test_value_list.append(literal_eval(stv)) + # Non-string types not using literal_evel + elif amqp_type == 'uuid': + received_test_value_list.append(UUID(stv)) + elif amqp_type == 'binary': + received_test_value_list.append(bytes(stv)) + # String and float types used as-is + elif amqp_type == 'float' or \ + amqp_type == 'double' or \ + amqp_type == 'decimal128' or \ + amqp_type == 'char' or \ + amqp_type == 'string' or \ + amqp_type == 'symbol': + received_test_value_list.append(stv) + elif amqp_type == 'list' or \ + amqp_type == 'map': + received_test_value_list.append(StrToObj(list(stv).__iter__()).run()) + else: + raise SimpleTypeTestError('ERROR: Shim.receive(): AMQP type \'%s\' not implemented' % amqp_type) + return received_test_value_list + else: + return output # return error string + except Exception as e: + return str(e) + '\n' + output + + +class ProtonPythonShim(Shim): + """ + Shim for qpid-proton Python client + """ + NAME = 'ProtonPython' + SHIM_LOC = QPID_INTEROP_TEST_HOME + '/shims/qpid-proton-python/src/' + SEND = SHIM_LOC + 'proton-python-send' + RECEIVE = SHIM_LOC + 'proton-python-receive' + + +class QpidJmsShim(Shim): + """ + Shim for qpid-jms JMS client + """ + NAME = 'QpidJms' + SHIM_LOC = '/shims/qpid-jms/src/main/java/' + SEND = SHIM_LOC + 'org/apache/qpid/qpid-interop-test/shim/ProtonJmsReceiver' + RECEIVE = SHIM_LOC + 'org/apache/qpid/qpid-interop-test/shim/ProtonJmsReceiver' + + +# 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 = {ProtonPythonShim.NAME: ProtonPythonShim()} + + +class TestOptions(object): + def __init__(self): + parser = argparse.ArgumentParser(description='Qpid-interop AMQP client interoparability test suite') + 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' % SHIM_NAMES) + 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 + + # 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(AmqpPrimitiveTypes.get_type_list()): + if args.exclude_type is None or at not in args.exclude_type: + test_case_class = create_testcase_class(args.broker, + at, + AmqpPrimitiveTypes.get_test_value_list(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 + unittest.TextTestRunner(verbosity=2).run(TEST_SUITE) --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
