diff options
author | Robert Gemmell <robbie@apache.org> | 2015-06-25 10:22:51 +0000 |
---|---|---|
committer | Robert Gemmell <robbie@apache.org> | 2015-06-25 10:22:51 +0000 |
commit | 32ae758bc2e8fd962b66a4ab6341b14009f1907e (patch) | |
tree | 2f4d8174813284a6ea58bb6b7f6520aa92287476 /qpid/cpp/src/tests | |
parent | 116d91ad7825a98af36a869fc751206fbce0c59f (diff) | |
parent | f7e896076143de4572b4f1f67ef0765125f2498d (diff) | |
download | qpid-python-32ae758bc2e8fd962b66a4ab6341b14009f1907e.tar.gz |
NO-JIRA: create branch for qpid-cpp 0.34 RC process
git-svn-id: https://svn.apache.org/repos/asf/qpid/branches/qpid-cpp-0.34-rc@1687469 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'qpid/cpp/src/tests')
283 files changed, 55802 insertions, 0 deletions
diff --git a/qpid/cpp/src/tests/.valgrind.supp b/qpid/cpp/src/tests/.valgrind.supp new file mode 100644 index 0000000000..1a24a9178e --- /dev/null +++ b/qpid/cpp/src/tests/.valgrind.supp @@ -0,0 +1,179 @@ +{ + Leak in TCPConnector: https://bugzilla.redhat.com/show_bug.cgi?id=520600 + Memcheck:Leak + fun:_vgrZU_libcZdsoZa_calloc + fun:_dl_allocate_tls + fun:* + fun:* + fun:* + fun:_ZN4qpid6client12TCPConnector7connectERKSsi +} + +{ + Leak in TCPConnector: https://bugzilla.redhat.com/show_bug.cgi?id=520600 + Memcheck:Leak + fun:_vgrZU_libcZdsoZa_calloc + fun:_dl_allocate_tls + fun:* + fun:* + fun:_ZN4qpid6client12TCPConnector7connectERKSsi +} + +{ + Reported on FC5 and RHEL5 when md5 sasl libs are installed + Memcheck:Leak + fun:* + fun:_dl_map_object_from_fd + fun:_dl_map_object + fun:openaux + fun:_dl_catch_error + fun:_dl_map_object_deps + fun:dl_open_worker + fun:_dl_catch_error + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_error + fun:_dlerror_run + fun:* + fun:_sasl_get_plugin + fun:_sasl_load_plugins + fun:sasl_client_init +} + +{ + Uninitialised value problem in _dl_relocate (F7, F8) + Memcheck:Cond + fun:_dl_relocate_object + fun:*dl_* +} + +{ + False "possibly leaked" in boost program_options - global std::string var. + Memcheck:Leak + fun:_Znwj + fun:_ZNSs4_Rep9_S_createEjjRKSaIcE + obj:/usr/lib/libstdc++.so.6.0.8 + fun:_ZNSsC1EPKcRKSaIcE + obj:/usr/lib/libboost_program_options.so.1.33.1 +} + +{ + INVESTIGATE + Memcheck:Leak + fun:calloc + fun:_dl_allocate_tls + fun:pthread_create@@GLIBC_2.2.5 + fun:_ZN4qpid6client9Connector4initEv + fun:_ZN4qpid6client14ConnectionImpl4openERKSsiS3_S3_S3_ +} + +{ + INVESTIGATE + Memcheck:Param + write(buf) + obj:/lib64/tls/libc-2.3.4.so + fun:_ZNK4qpid3sys6Socket5writeEPKvm + fun:_ZN4qpid3sys8AsynchIO9writeableERNS0_14DispatchHandleE +} + +{ + "Conditional jump or move depends on uninitialised value(s)" from Xerces parser + Memcheck:Cond + fun:_ZN11xercesc_2_717XMLUTF8Transcoder13transcodeFromEPKhjPtjRjPh + fun:_ZN11xercesc_2_79XMLReader14xcodeMoreCharsEPtPhj + fun:_ZN11xercesc_2_79XMLReader17refreshCharBufferEv +} + +{ + INVESTIGATE + Memcheck:Param + socketcall.sendto(msg) + fun:send + fun:get_mapping + fun:__nscd_get_map_ref + fun:nscd_gethst_r + fun:__nscd_gethostbyname_r + fun:gethostbyname_r@@GLIBC_2.2.5 + fun:gethostbyname + fun:_ZNK4qpid3sys6Socket7connectERKSsi +} + +{ + INVESTIGATE + Memcheck:Leak + fun:calloc + fun:_dl_allocate_tls + fun:pthread_create@@GLIBC_2.2.5 + fun:_ZN4qpid6broker5Timer5startEv + fun:_ZN4qpid6broker5TimerC1Ev + fun:_ZN4qpid6broker10DtxManagerC1Ev + fun:_ZN4qpid6broker6BrokerC1ERKNS1_7OptionsE + fun:_ZN4qpid6broker6Broker6createERKNS1_7OptionsE + fun:_ZN20ClientSessionFixtureC1Ev + fun:_Z14testQueueQueryv + fun:_ZN5boost9unit_test9ut_detail17unit_test_monitor8functionEv + obj:/usr/lib64/libboost_unit_test_framework.so.1.32.0 + fun:_ZN5boost17execution_monitor7executeEbi + fun:_ZN5boost9unit_test9ut_detail17unit_test_monitor21execute_and_translateEPNS0_9test_caseEMS3_FvvEi + fun:_ZN5boost9unit_test9test_case3runEv + fun:_ZN5boost9unit_test10test_suite6do_runEv + fun:_ZN5boost9unit_test9test_case3runEv + fun:main +} + +{ + INVESTIGATE + Memcheck:Leak + fun:calloc + fun:_dl_allocate_tls + fun:pthread_create@@GLIBC_2.2.5 + fun:_ZN4qpid6client9Connector4initEv +} + +{ + MICK -- FIX + Memcheck:Leak + fun:_Znam + fun:_ZN4qpid7Options5parseEiPPcRKSsb +} + +{ + MICK -- FIX + Memcheck:Leak + fun:malloc + fun:strdup + fun:_ZN4qpid7Options5parseEiPPcRKSsb +} + +{ + Known leak in boost.thread 1.33.1. Wildcards for 64/32 bit diffs. + Memcheck:Leak + fun:* + obj:/usr/*/libboost_thread.so.1.33.1 + fun:_ZN5boost6detail3tss3setEPv +} + +{ + Shows up on RHEL5: believed benign + Memcheck:Cond + fun:__strcpy_chk + fun:_sasl_load_plugins + fun:sasl_client_init +} + +{ + Seems like a use after delete issue in boost unit_test + Memcheck:Addr8 + fun:_ZN5boost9unit_test14framework_implD1Ev + fun:exit + fun:(below main) +} + +{ + Seems like a use after delete issue in boost unit_test + Memcheck:Addr4 + fun:_ZN5boost9unit_test14framework_implD1Ev + fun:exit + fun:(below main) +} + diff --git a/qpid/cpp/src/tests/AccumulatedAckTest.cpp b/qpid/cpp/src/tests/AccumulatedAckTest.cpp new file mode 100644 index 0000000000..c736a519d2 --- /dev/null +++ b/qpid/cpp/src/tests/AccumulatedAckTest.cpp @@ -0,0 +1,237 @@ + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/framing/AccumulatedAck.h" +#include "unit_test.h" +#include <iostream> +#include <list> + +using std::list; +using namespace qpid::framing; + + +namespace qpid { +namespace tests { + +bool covers(const AccumulatedAck& ack, int i) +{ + return ack.covers(SequenceNumber(i)); +} + +void update(AccumulatedAck& ack, int start, int end) +{ + ack.update(SequenceNumber(start), SequenceNumber(end)); +} + +QPID_AUTO_TEST_SUITE(AccumulatedAckTestSuite) + +QPID_AUTO_TEST_CASE(testGeneral) +{ + AccumulatedAck ack(0); + ack.clear(); + update(ack, 3,3); + update(ack, 7,7); + update(ack, 9,9); + update(ack, 1,2); + update(ack, 4,5); + update(ack, 6,6); + + for(int i = 1; i <= 7; i++) BOOST_CHECK(covers(ack, i)); + BOOST_CHECK(covers(ack, 9)); + + BOOST_CHECK(!covers(ack, 8)); + BOOST_CHECK(!covers(ack, 10)); + + ack.consolidate(); + + for(int i = 1; i <= 7; i++) BOOST_CHECK(covers(ack, i)); + BOOST_CHECK(covers(ack, 9)); + + BOOST_CHECK(!covers(ack, 8)); + BOOST_CHECK(!covers(ack, 10)); +} + +QPID_AUTO_TEST_CASE(testCovers) +{ + AccumulatedAck ack(5); + update(ack, 7, 7); + update(ack, 9, 9); + + BOOST_CHECK(covers(ack, 1)); + BOOST_CHECK(covers(ack, 2)); + BOOST_CHECK(covers(ack, 3)); + BOOST_CHECK(covers(ack, 4)); + BOOST_CHECK(covers(ack, 5)); + BOOST_CHECK(covers(ack, 7)); + BOOST_CHECK(covers(ack, 9)); + + BOOST_CHECK(!covers(ack, 6)); + BOOST_CHECK(!covers(ack, 8)); + BOOST_CHECK(!covers(ack, 10)); +} + +QPID_AUTO_TEST_CASE(testUpdateFromCompletionData) +{ + AccumulatedAck ack(0); + SequenceNumber mark(2); + SequenceNumberSet ranges; + ranges.addRange(SequenceNumber(5), SequenceNumber(8)); + ranges.addRange(SequenceNumber(10), SequenceNumber(15)); + ranges.addRange(SequenceNumber(9), SequenceNumber(9)); + ranges.addRange(SequenceNumber(3), SequenceNumber(4)); + + ack.update(mark, ranges); + + for(int i = 0; i <= 15; i++) { + BOOST_CHECK(covers(ack, i)); + } + BOOST_CHECK(!covers(ack, 16)); + BOOST_CHECK_EQUAL((uint32_t) 15, ack.mark.getValue()); +} + +QPID_AUTO_TEST_CASE(testCase1) +{ + AccumulatedAck ack(3); + update(ack, 1,2); + for(int i = 1; i <= 3; i++) BOOST_CHECK(covers(ack, i)); + BOOST_CHECK(!covers(ack, 4)); +} + +QPID_AUTO_TEST_CASE(testCase2) +{ + AccumulatedAck ack(3); + update(ack, 3,6); + for(int i = 1; i <= 6; i++) BOOST_CHECK(covers(ack, i)); + BOOST_CHECK(!covers(ack, 7)); +} + +QPID_AUTO_TEST_CASE(testCase3) +{ + AccumulatedAck ack(3); + update(ack, 4,6); + for(int i = 1; i <= 6; i++) { + BOOST_CHECK(covers(ack, i)); + } + BOOST_CHECK(!covers(ack, 7)); +} + +QPID_AUTO_TEST_CASE(testCase4) +{ + AccumulatedAck ack(3); + update(ack, 5,6); + for(int i = 1; i <= 6; i++) { + if (i == 4) BOOST_CHECK(!covers(ack, i)); + else BOOST_CHECK(covers(ack, i)); + } + BOOST_CHECK(!covers(ack, 7)); +} + +QPID_AUTO_TEST_CASE(testConsolidation1) +{ + AccumulatedAck ack(3); + update(ack, 7,7); + BOOST_CHECK_EQUAL((uint32_t) 3, ack.mark.getValue()); + BOOST_CHECK_EQUAL((size_t) 1, ack.ranges.size()); + + update(ack, 8,9); + BOOST_CHECK_EQUAL((uint32_t) 3, ack.mark.getValue()); + BOOST_CHECK_EQUAL((size_t) 1, ack.ranges.size()); + + update(ack, 1,2); + BOOST_CHECK_EQUAL((uint32_t) 3, ack.mark.getValue()); + BOOST_CHECK_EQUAL((size_t) 1, ack.ranges.size()); + + update(ack, 4,5); + BOOST_CHECK_EQUAL((uint32_t) 5, ack.mark.getValue()); + BOOST_CHECK_EQUAL((size_t) 1, ack.ranges.size()); + + update(ack, 6,6); + BOOST_CHECK_EQUAL((uint32_t) 9, ack.mark.getValue()); + BOOST_CHECK_EQUAL((size_t) 0, ack.ranges.size()); + + for(int i = 1; i <= 9; i++) BOOST_CHECK(covers(ack, i)); + BOOST_CHECK(!covers(ack, 10)); +} + +QPID_AUTO_TEST_CASE(testConsolidation2) +{ + AccumulatedAck ack(0); + update(ack, 10,12); + BOOST_CHECK_EQUAL((uint32_t) 0, ack.mark.getValue()); + BOOST_CHECK_EQUAL((size_t) 1, ack.ranges.size()); + + update(ack, 7,9); + BOOST_CHECK_EQUAL((uint32_t) 0, ack.mark.getValue()); + BOOST_CHECK_EQUAL((size_t) 1, ack.ranges.size()); + BOOST_CHECK_EQUAL((uint32_t) 7, ack.ranges.front().start.getValue()); + BOOST_CHECK_EQUAL((uint32_t) 12, ack.ranges.front().end.getValue()); + + update(ack, 5,7); + BOOST_CHECK_EQUAL((uint32_t) 0, ack.mark.getValue()); + BOOST_CHECK_EQUAL((size_t) 1, ack.ranges.size()); + BOOST_CHECK_EQUAL((uint32_t) 5, ack.ranges.front().start.getValue()); + BOOST_CHECK_EQUAL((uint32_t) 12, ack.ranges.front().end.getValue()); + + update(ack, 3,4); + BOOST_CHECK_EQUAL((uint32_t) 0, ack.mark.getValue()); + BOOST_CHECK_EQUAL((size_t) 1, ack.ranges.size()); + BOOST_CHECK_EQUAL((uint32_t) 3, ack.ranges.front().start.getValue()); + BOOST_CHECK_EQUAL((uint32_t) 12, ack.ranges.front().end.getValue()); + + update(ack, 1,2); + BOOST_CHECK_EQUAL((uint32_t) 12, ack.mark.getValue()); + BOOST_CHECK_EQUAL((size_t) 0, ack.ranges.size()); + + for(int i = 1; i <= 12; i++) BOOST_CHECK(covers(ack, i)); + BOOST_CHECK(!covers(ack, 13)); +} + +QPID_AUTO_TEST_CASE(testConsolidation3) +{ + AccumulatedAck ack(0); + update(ack, 10,12); + update(ack, 6,7); + update(ack, 3,4); + update(ack, 1,15); + BOOST_CHECK_EQUAL((uint32_t) 15, ack.mark.getValue()); + BOOST_CHECK_EQUAL((size_t) 0, ack.ranges.size()); +} + +QPID_AUTO_TEST_CASE(testConsolidation4) +{ + AccumulatedAck ack(0); + ack.update(SequenceNumber(0), SequenceNumber(2)); + ack.update(SequenceNumber(5), SequenceNumber(8)); + ack.update(SequenceNumber(10), SequenceNumber(15)); + ack.update(SequenceNumber(9), SequenceNumber(9)); + ack.update(SequenceNumber(3), SequenceNumber(4)); + + for(int i = 0; i <= 15; i++) { + BOOST_CHECK(covers(ack, i)); + } + BOOST_CHECK(!covers(ack, 16)); + BOOST_CHECK_EQUAL((uint32_t) 15, ack.mark.getValue()); +} + +QPID_AUTO_TEST_SUITE_END() + + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/Acl.cpp b/qpid/cpp/src/tests/Acl.cpp new file mode 100644 index 0000000000..9c3de0de62 --- /dev/null +++ b/qpid/cpp/src/tests/Acl.cpp @@ -0,0 +1,166 @@ +/* + * + * Copyright (c) 2014 The Apache Software Foundation + * + * 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. + * + */ + + +#include "unit_test.h" +#include "test_tools.h" +#include "qpid/acl/AclLexer.h" +#include <boost/assign.hpp> + +using namespace std; +using namespace qpid; +using namespace qpid::acl; +using namespace boost::assign; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(AclTestSuite) + +#define OBJ_ENUMS(e, s) \ + BOOST_CHECK_EQUAL(AclHelper::getObjectTypeStr((e)),(s)); \ + BOOST_CHECK_EQUAL(AclHelper::getObjectType((s)),(e)) + +QPID_AUTO_TEST_CASE(TestLexerObjectEnums) { + BOOST_CHECK_EQUAL(OBJECTSIZE, 7); + OBJ_ENUMS(OBJ_QUEUE, "queue"); + OBJ_ENUMS(OBJ_EXCHANGE, "exchange"); + OBJ_ENUMS(OBJ_BROKER, "broker"); + OBJ_ENUMS(OBJ_LINK, "link"); + OBJ_ENUMS(OBJ_METHOD, "method"); + OBJ_ENUMS(OBJ_QUERY, "query"); + OBJ_ENUMS(OBJ_CONNECTION, "connection"); + int maxLen = 0; + for (int i=0; i<acl::OBJECTSIZE; i++) { + int thisLen = AclHelper::getObjectTypeStr( ObjectType(i) ).length(); + if (thisLen > maxLen) + maxLen = thisLen; + } + BOOST_CHECK_EQUAL(maxLen, acl::OBJECTTYPE_STR_WIDTH); +} + +#define ACT_ENUMS(e, s) \ + BOOST_CHECK_EQUAL(AclHelper::getActionStr((e)),(s)); \ + BOOST_CHECK_EQUAL(AclHelper::getAction((s)),(e)) + +QPID_AUTO_TEST_CASE(TestLexerActionEnums) { + BOOST_CHECK_EQUAL(ACTIONSIZE, 12); + ACT_ENUMS(ACT_CONSUME, "consume"); + ACT_ENUMS(ACT_PUBLISH, "publish"); + ACT_ENUMS(ACT_CREATE, "create"); + ACT_ENUMS(ACT_ACCESS, "access"); + ACT_ENUMS(ACT_BIND, "bind"); + ACT_ENUMS(ACT_UNBIND, "unbind"); + ACT_ENUMS(ACT_DELETE, "delete"); + ACT_ENUMS(ACT_PURGE, "purge"); + ACT_ENUMS(ACT_UPDATE, "update"); + ACT_ENUMS(ACT_MOVE, "move"); + ACT_ENUMS(ACT_REDIRECT, "redirect"); + ACT_ENUMS(ACT_REROUTE, "reroute"); + int maxLen = 0; + for (int i=0; i<acl::ACTIONSIZE; i++) { + int thisLen = AclHelper::getActionStr( Action(i) ).length(); + if (thisLen > maxLen) + maxLen = thisLen; + } + BOOST_CHECK_EQUAL(maxLen, acl::ACTION_STR_WIDTH); +} + +#define PROP_ENUMS(e, s) \ + BOOST_CHECK_EQUAL(AclHelper::getPropertyStr((e)),(s)); \ + BOOST_CHECK_EQUAL(AclHelper::getProperty((s)),(e)) + +QPID_AUTO_TEST_CASE(TestLexerPropertyEnums) { + BOOST_CHECK_EQUAL(PROPERTYSIZE, 21); + PROP_ENUMS(PROP_NAME, "name"); + PROP_ENUMS(PROP_DURABLE, "durable"); + PROP_ENUMS(PROP_OWNER, "owner"); + PROP_ENUMS(PROP_ROUTINGKEY, "routingkey"); + PROP_ENUMS(PROP_AUTODELETE, "autodelete"); + PROP_ENUMS(PROP_EXCLUSIVE, "exclusive"); + PROP_ENUMS(PROP_TYPE, "type"); + PROP_ENUMS(PROP_ALTERNATE, "alternate"); + PROP_ENUMS(PROP_QUEUENAME, "queuename"); + PROP_ENUMS(PROP_EXCHANGENAME, "exchangename"); + PROP_ENUMS(PROP_SCHEMAPACKAGE, "schemapackage"); + PROP_ENUMS(PROP_SCHEMACLASS, "schemaclass"); + PROP_ENUMS(PROP_POLICYTYPE, "policytype"); + PROP_ENUMS(PROP_PAGING, "paging"); + PROP_ENUMS(PROP_HOST, "host"); + PROP_ENUMS(PROP_MAXPAGES, "maxpages"); + PROP_ENUMS(PROP_MAXPAGEFACTOR, "maxpagefactor"); + PROP_ENUMS(PROP_MAXQUEUESIZE, "maxqueuesize"); + PROP_ENUMS(PROP_MAXQUEUECOUNT, "maxqueuecount"); + PROP_ENUMS(PROP_MAXFILESIZE, "maxfilesize"); + PROP_ENUMS(PROP_MAXFILECOUNT, "maxfilecount"); + +} + +#define SPECPROP_ENUMS(e, s) \ + BOOST_CHECK_EQUAL(AclHelper::getPropertyStr((e)),(s)); \ + BOOST_CHECK_EQUAL(AclHelper::getSpecProperty((s)),(e)) + +QPID_AUTO_TEST_CASE(TestLexerSpecPropertyEnums) { + BOOST_CHECK_EQUAL(SPECPROPSIZE, 27); + SPECPROP_ENUMS(SPECPROP_NAME, "name"); + SPECPROP_ENUMS(SPECPROP_DURABLE, "durable"); + SPECPROP_ENUMS(SPECPROP_OWNER, "owner"); + SPECPROP_ENUMS(SPECPROP_ROUTINGKEY, "routingkey"); + SPECPROP_ENUMS(SPECPROP_AUTODELETE, "autodelete"); + SPECPROP_ENUMS(SPECPROP_EXCLUSIVE, "exclusive"); + SPECPROP_ENUMS(SPECPROP_TYPE, "type"); + SPECPROP_ENUMS(SPECPROP_ALTERNATE, "alternate"); + SPECPROP_ENUMS(SPECPROP_QUEUENAME, "queuename"); + SPECPROP_ENUMS(SPECPROP_EXCHANGENAME, "exchangename"); + SPECPROP_ENUMS(SPECPROP_SCHEMAPACKAGE, "schemapackage"); + SPECPROP_ENUMS(SPECPROP_SCHEMACLASS, "schemaclass"); + SPECPROP_ENUMS(SPECPROP_POLICYTYPE, "policytype"); + SPECPROP_ENUMS(SPECPROP_PAGING, "paging"); + SPECPROP_ENUMS(SPECPROP_HOST, "host"); + SPECPROP_ENUMS(SPECPROP_MAXQUEUESIZELOWERLIMIT, "queuemaxsizelowerlimit"); + SPECPROP_ENUMS(SPECPROP_MAXQUEUESIZEUPPERLIMIT, "queuemaxsizeupperlimit"); + SPECPROP_ENUMS(SPECPROP_MAXQUEUECOUNTLOWERLIMIT, "queuemaxcountlowerlimit"); + SPECPROP_ENUMS(SPECPROP_MAXQUEUECOUNTUPPERLIMIT, "queuemaxcountupperlimit"); + SPECPROP_ENUMS(SPECPROP_MAXFILESIZELOWERLIMIT, "filemaxsizelowerlimit"); + SPECPROP_ENUMS(SPECPROP_MAXFILESIZEUPPERLIMIT, "filemaxsizeupperlimit"); + SPECPROP_ENUMS(SPECPROP_MAXFILECOUNTLOWERLIMIT, "filemaxcountlowerlimit"); + SPECPROP_ENUMS(SPECPROP_MAXFILECOUNTUPPERLIMIT, "filemaxcountupperlimit"); + SPECPROP_ENUMS(SPECPROP_MAXPAGESLOWERLIMIT, "pageslowerlimit"); + SPECPROP_ENUMS(SPECPROP_MAXPAGESUPPERLIMIT, "pagesupperlimit"); + SPECPROP_ENUMS(SPECPROP_MAXPAGEFACTORLOWERLIMIT, "pagefactorlowerlimit"); + SPECPROP_ENUMS(SPECPROP_MAXPAGEFACTORUPPERLIMIT, "pagefactorupperlimit"); + + BOOST_CHECK_EQUAL(AclHelper::getSpecProperty("maxqueuesize"), SPECPROP_MAXQUEUESIZEUPPERLIMIT); + BOOST_CHECK_EQUAL(AclHelper::getSpecProperty("maxqueuecount"), SPECPROP_MAXQUEUECOUNTUPPERLIMIT); +} + +#define RESULT_ENUMS(e, s) \ + BOOST_CHECK_EQUAL(AclHelper::getAclResultStr((e)),(s)); \ + BOOST_CHECK_EQUAL(AclHelper::getAclResult((s)),(e)) + +QPID_AUTO_TEST_CASE(TestLexerResultEnums) { + BOOST_CHECK_EQUAL(RESULTSIZE, 4); + RESULT_ENUMS(ALLOW, "allow"); + RESULT_ENUMS(ALLOWLOG, "allow-log"); + RESULT_ENUMS(DENY, "deny"); + RESULT_ENUMS(DENYLOG, "deny-log"); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/AclHost.cpp b/qpid/cpp/src/tests/AclHost.cpp new file mode 100644 index 0000000000..7d60c5a63d --- /dev/null +++ b/qpid/cpp/src/tests/AclHost.cpp @@ -0,0 +1,166 @@ +/* + * + * Copyright (c) 2014 The Apache Software Foundation + * + * 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. + * + */ + + +#include "unit_test.h" +#include "test_tools.h" +#include "qpid/AclHost.h" +#include "qpid/sys/SocketAddress.h" +#include <boost/assign.hpp> + +using namespace std; +using namespace qpid; +using namespace boost::assign; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(AclHostTestSuite) + +#define ACLURL_CHECK_INVALID(STR) BOOST_CHECK_THROW(AclHost(STR), AclHost::Invalid) + +#define SENSE_IP_VERSIONS() \ + bool haveIPv4(true); \ + try { \ + sys::SocketAddress sa("1.1.1.1", ""); \ + sa.firstAddress(); \ +} catch (qpid::Exception) { \ + haveIPv4 = false; \ +} \ + bool haveIPv6(true); \ + try { \ + sys::SocketAddress sa("::1", ""); \ + sa.firstAddress(); \ +} catch (qpid::Exception) { \ + haveIPv6 = false; \ +} \ +(void) haveIPv4; \ +(void) haveIPv6; + +QPID_AUTO_TEST_CASE(TestParseTcpIPv4) { + SENSE_IP_VERSIONS(); + if (haveIPv4) { + BOOST_CHECK_EQUAL(AclHost("1.1.1.1").str(), "(1.1.1.1,1.1.1.1)"); + BOOST_CHECK_EQUAL(AclHost("1.1.1.1,2.2.2.2").str(), "(1.1.1.1,2.2.2.2)"); + } +} + +QPID_AUTO_TEST_CASE(TestParseTcpIPv6) { + SENSE_IP_VERSIONS(); + if (haveIPv6) { + BOOST_CHECK_EQUAL(AclHost("[::1]").str(), "([::1],[::1])"); + BOOST_CHECK_EQUAL(AclHost("[::1],::5").str(), "([::1],[::5])"); + } +} + +QPID_AUTO_TEST_CASE(TestParseAll) { + SENSE_IP_VERSIONS(); + if (haveIPv4 || haveIPv6) { + BOOST_CHECK_EQUAL(AclHost("").str(), "(all)"); + } +} + +QPID_AUTO_TEST_CASE(TestInvalidMixedIpFamilies) { + SENSE_IP_VERSIONS(); + if (haveIPv4 && haveIPv6) { + ACLURL_CHECK_INVALID("1.1.1.1,[::1]"); + ACLURL_CHECK_INVALID("[::1],1.1.1.1"); + } +} + +QPID_AUTO_TEST_CASE(TestMalformedIPv4) { + SENSE_IP_VERSIONS(); + if (haveIPv4) { + ACLURL_CHECK_INVALID("1.1.1.1.1"); + ACLURL_CHECK_INVALID("1.1.1.777"); + ACLURL_CHECK_INVALID("1.1.1.1abcd"); + ACLURL_CHECK_INVALID("1.1.1.*"); + } +} + +QPID_AUTO_TEST_CASE(TestRangeWithInvertedSizeOrder) { + SENSE_IP_VERSIONS(); + if (haveIPv4) { + ACLURL_CHECK_INVALID("1.1.1.100,1.1.1.1"); + } + if (haveIPv6) { + ACLURL_CHECK_INVALID("[FF::1],[::1]"); + } +} + +QPID_AUTO_TEST_CASE(TestSingleHostResolvesMultipleAddresses) { + SENSE_IP_VERSIONS(); + AclHost XX("localhost"); +} + +QPID_AUTO_TEST_CASE(TestMatchSingleAddresses) { + SENSE_IP_VERSIONS(); + if (haveIPv4) { + AclHost host1("1.1.1.1"); + BOOST_CHECK(host1.match("1.1.1.1") == true); + BOOST_CHECK(host1.match("1.2.1.1") == false); + } + if (haveIPv6) { + AclHost host2("FF::1"); + BOOST_CHECK(host2.match("00FF:0000::1") == true); + } +} + +QPID_AUTO_TEST_CASE(TestMatchIPv4Range) { + SENSE_IP_VERSIONS(); + if (haveIPv4) { + AclHost host1("192.168.0.0,192.168.255.255"); + BOOST_CHECK(host1.match("128.1.1.1") == false); + BOOST_CHECK(host1.match("192.167.255.255") == false); + BOOST_CHECK(host1.match("192.168.0.0") == true); + BOOST_CHECK(host1.match("192.168.0.1") == true); + BOOST_CHECK(host1.match("192.168.1.0") == true); + BOOST_CHECK(host1.match("192.168.255.254") == true); + BOOST_CHECK(host1.match("192.168.255.255") == true); + BOOST_CHECK(host1.match("192.169.0.0") == false); + if (haveIPv6) { + BOOST_CHECK(host1.match("::1") == false); + } + } +} + +QPID_AUTO_TEST_CASE(TestMatchIPv6Range) { + SENSE_IP_VERSIONS(); + if (haveIPv6) { + AclHost host1("::10,::1:0"); + BOOST_CHECK(host1.match("::1") == false); + BOOST_CHECK(host1.match("::f") == false); + BOOST_CHECK(host1.match("::10") == true); + BOOST_CHECK(host1.match("::11") == true); + BOOST_CHECK(host1.match("::ffff") == true); + BOOST_CHECK(host1.match("::1:0") == true); + BOOST_CHECK(host1.match("::1:1") == false); + if (haveIPv4) { + BOOST_CHECK(host1.match("192.169.0.0") == false); + } + AclHost host2("[fc00::],[fc00::ff]"); + BOOST_CHECK(host2.match("fc00::") == true); + BOOST_CHECK(host2.match("fc00::1") == true); + BOOST_CHECK(host2.match("fc00::ff") == true); + BOOST_CHECK(host2.match("fc00::100") == false); + + } +} +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/Address.cpp b/qpid/cpp/src/tests/Address.cpp new file mode 100644 index 0000000000..0fd3585958 --- /dev/null +++ b/qpid/cpp/src/tests/Address.cpp @@ -0,0 +1,135 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include <iostream> +#include "qpid/messaging/Address.h" +#include "qpid/types/Variant.h" + +#include "unit_test.h" + +using namespace qpid::messaging; +using namespace qpid::types; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(AddressSuite) + +QPID_AUTO_TEST_CASE(testParseNameOnly) +{ + Address address("my-topic"); + BOOST_CHECK_EQUAL(std::string("my-topic"), address.getName()); +} + +QPID_AUTO_TEST_CASE(testParseSubject) +{ + Address address("my-topic/my-subject"); + BOOST_CHECK_EQUAL(std::string("my-topic"), address.getName()); + BOOST_CHECK_EQUAL(std::string("my-subject"), address.getSubject()); +} + +QPID_AUTO_TEST_CASE(testParseOptions) +{ + Address address("my-topic; {a:bc, x:101, y:'a string'}"); + BOOST_CHECK_EQUAL(std::string("my-topic"), address.getName()); + + BOOST_CHECK_EQUAL(std::string("bc"), address.getOptions()["a"]); + BOOST_CHECK_EQUAL(101, static_cast<int>(address.getOptions()["x"])); + BOOST_CHECK_EQUAL(std::string("a string"), address.getOptions()["y"]); + + // Test asString() and asInt64() once here + + BOOST_CHECK_EQUAL(std::string("bc"), address.getOptions()["a"].asString()); + BOOST_CHECK_EQUAL(static_cast<uint16_t>(101), address.getOptions()["x"].asInt64()); + BOOST_CHECK_EQUAL(std::string("a string"), address.getOptions()["y"].asString()); +} + +QPID_AUTO_TEST_CASE(testParseSubjectAndOptions) +{ + Address address("my-topic/my-subject; {a:bc, x:101, y:'a string'}"); + BOOST_CHECK_EQUAL(std::string("my-topic"), address.getName()); + BOOST_CHECK_EQUAL(std::string("my-subject"), address.getSubject()); + + BOOST_CHECK_EQUAL(std::string("bc"), address.getOptions()["a"]); + BOOST_CHECK_EQUAL(101, static_cast<int>(address.getOptions()["x"])); + BOOST_CHECK_EQUAL(std::string("a string"), address.getOptions()["y"]); +} + +QPID_AUTO_TEST_CASE(testParseNestedOptions) +{ + Address address("my-topic; {a:{p:202, q:'another string'}, x:101, y:'a string'}"); + BOOST_CHECK_EQUAL(std::string("my-topic"), address.getName()); + BOOST_CHECK_EQUAL(202, static_cast<int>(address.getOptions()["a"].asMap()["p"])); + BOOST_CHECK_EQUAL(std::string("another string"), address.getOptions()["a"].asMap()["q"]); + BOOST_CHECK_EQUAL(std::string("a string"), address.getOptions()["y"]); +} + +QPID_AUTO_TEST_CASE(testParseOptionsWithList) +{ + Address address("my-topic; {a:[202, 'another string'], x:101}"); + BOOST_CHECK_EQUAL(std::string("my-topic"), address.getName()); + Variant::List& list = address.getOptions()["a"].asList(); + Variant::List::const_iterator i = list.begin(); + BOOST_CHECK(i != list.end()); + BOOST_CHECK_EQUAL((uint16_t) 202, i->asInt64()); + BOOST_CHECK(++i != list.end()); + BOOST_CHECK_EQUAL(std::string("another string"), i->asString()); + BOOST_CHECK_EQUAL((uint16_t) 101, address.getOptions()["x"].asInt64()); +} + +QPID_AUTO_TEST_CASE(testParseOptionsWithEmptyList) +{ + Address address("my-topic; {a:[], x:101}"); + BOOST_CHECK_EQUAL(std::string("my-topic"), address.getName()); + Variant::List& list = address.getOptions()["a"].asList(); + BOOST_CHECK_EQUAL(list.size(), (size_t) 0); + BOOST_CHECK_EQUAL((uint16_t) 101, address.getOptions()["x"].asInt64()); +} + +QPID_AUTO_TEST_CASE(testParseOptionsWithEmptyMap) +{ + Address address("my-topic; {a:{}, x:101}"); + BOOST_CHECK_EQUAL(std::string("my-topic"), address.getName()); + Variant::Map& map = address.getOptions()["a"].asMap(); + BOOST_CHECK_EQUAL(map.size(), (size_t) 0); + BOOST_CHECK_EQUAL((uint16_t) 101, address.getOptions()["x"].asInt64()); +} + +QPID_AUTO_TEST_CASE(testParseQuotedNameAndSubject) +{ + Address address("'my topic with / in it'/'my subject with ; in it'"); + BOOST_CHECK_EQUAL(std::string("my topic with / in it"), address.getName()); + BOOST_CHECK_EQUAL(std::string("my subject with ; in it"), address.getSubject()); +} + +QPID_AUTO_TEST_CASE(testParseOptionsWithEmptyStringAsValue) +{ + Address address("my-topic; {a:'', x:101}"); + BOOST_CHECK_EQUAL(std::string("my-topic"), address.getName()); + Variant a = address.getOptions()["a"]; + BOOST_CHECK_EQUAL(VAR_STRING, a.getType()); + std::string aVal = a; + BOOST_CHECK(aVal.size() == 0); + BOOST_CHECK_EQUAL((uint16_t) 101, address.getOptions()["x"].asInt64()); +} + +QPID_AUTO_TEST_SUITE_END() + +}} diff --git a/qpid/cpp/src/tests/Array.cpp b/qpid/cpp/src/tests/Array.cpp new file mode 100644 index 0000000000..8ce7615162 --- /dev/null +++ b/qpid/cpp/src/tests/Array.cpp @@ -0,0 +1,84 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include <iostream> +#include <sstream> +#include "qpid/framing/Array.h" +#include "qpid/framing/FieldValue.h" + +#include "unit_test.h" + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(ArrayTestSuite) + +using namespace qpid::framing; + +void populate(std::vector<std::string>& data, int count = 10) +{ + for (int i = 0; i < count; i++) { + std::stringstream out; + out << "item-" << i; + data.push_back(out.str()); + } +} + +QPID_AUTO_TEST_CASE(testEncodeDecode) +{ + std::vector<std::string> data; + populate(data); + + Array a(data); + + char buff[200]; + Buffer wbuffer(buff, 200); + a.encode(wbuffer); + + Array b; + Buffer rbuffer(buff, 200); + b.decode(rbuffer); + BOOST_CHECK_EQUAL(a, b); + + std::vector<std::string> data2; + std::transform(b.begin(), b.end(), std::back_inserter(data2), Array::get<std::string, Array::ValuePtr>); + //BOOST_CHECK_EQUAL(data, data2); + BOOST_CHECK(data == data2); +} + +QPID_AUTO_TEST_CASE(testArrayAssignment) +{ + std::vector<std::string> data; + populate(data); + Array b; + { + Array a(data); + b = a; + BOOST_CHECK_EQUAL(a, b); + } + std::vector<std::string> data2; + std::transform(b.begin(), b.end(), std::back_inserter(data2), Array::get<std::string, Array::ValuePtr>); + //BOOST_CHECK_EQUAL(data, data2); + BOOST_CHECK(data == data2); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/AsyncCompletion.cpp b/qpid/cpp/src/tests/AsyncCompletion.cpp new file mode 100644 index 0000000000..dc43f10156 --- /dev/null +++ b/qpid/cpp/src/tests/AsyncCompletion.cpp @@ -0,0 +1,153 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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. + * + */ + + +#include "unit_test.h" +#include "test_tools.h" +#include "BrokerFixture.h" +#include "qpid/broker/NullMessageStore.h" +#include "qpid/sys/BlockingQueue.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/sys/Time.h" +#include "qpid/framing/QueueQueryResult.h" +#include "qpid/client/TypedResult.h" + +using namespace std; +using namespace qpid; +using namespace client; +using namespace framing; + +namespace qpid { namespace broker { +class TransactionContext; +class PersistableQueue; +}} + +using broker::PersistableMessage; +using broker::NullMessageStore; +using broker::TransactionContext; +using broker::PersistableQueue; +using sys::TIME_SEC; +using boost::intrusive_ptr; + +/** @file + * Unit tests for async completion. + * Using a dummy store, verify that the broker indicates async completion of + * message enqueues at the correct time. + */ + +namespace qpid { +namespace tests { + +class AsyncCompletionMessageStore : public NullMessageStore { + public: + sys::BlockingQueue<boost::intrusive_ptr<PersistableMessage> > enqueued; + + AsyncCompletionMessageStore() : NullMessageStore() {} + ~AsyncCompletionMessageStore(){} + + void enqueue(TransactionContext*, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& ) + { + enqueued.push(msg); + } +}; + +QPID_AUTO_TEST_SUITE(AsyncCompletionTestSuite) + +/** + * Send a sync after a bunch of incomplete messages, verify the sync completes + * only when all the messages are complete. + */ +QPID_AUTO_TEST_CASE(testWaitTillComplete) { + SessionFixture fix; + AsyncCompletionMessageStore* store = new AsyncCompletionMessageStore; + boost::shared_ptr<qpid::broker::MessageStore> p; + p.reset(store); + fix.broker->setStore(p); + AsyncSession s = fix.session; + + static const int count = 3; + + s.queueDeclare("q", arg::durable=true); + Completion transfers[count]; + for (int i = 0; i < count; ++i) { + Message msg(boost::lexical_cast<string>(i), "q"); + msg.getDeliveryProperties().setDeliveryMode(PERSISTENT); + transfers[i] = s.messageTransfer(arg::content=msg); + } + + // Get hold of the broker-side messages. + typedef vector<intrusive_ptr<PersistableMessage> > BrokerMessages; + BrokerMessages enqueued; + for (int j = 0; j < count; ++j) + enqueued.push_back(store->enqueued.pop(TIME_SEC)); + + // Send a sync, make sure it does not complete till all messages are complete. + // In reverse order for fun. + Completion sync = s.executionSync(arg::sync=true); + for (int k = count-1; k >= 0; --k) { + BOOST_CHECK(!transfers[k].isComplete()); // Should not be complete yet. + BOOST_CHECK(!sync.isComplete()); // Should not be complete yet. + enqueued[k]->enqueueComplete(); + } + sync.wait(); // Should complete now, all messages are completed. +} + +/** + * Send a sync after all messages are complete, verify it completes immediately. + */ +QPID_AUTO_TEST_CASE(testSyncAfterComplete) { + SessionFixture fix; + AsyncCompletionMessageStore* store = new AsyncCompletionMessageStore; + boost::shared_ptr<qpid::broker::MessageStore> p; + p.reset(store); + fix.broker->setStore(p); + AsyncSession s = fix.session; + + static const int count = 3; + + s.queueDeclare("q", arg::durable=true); + // Transfer and complete all the messages + for (int i = 0; i < count; ++i) { + Message msg(boost::lexical_cast<string>(i), "q"); + msg.getDeliveryProperties().setDeliveryMode(PERSISTENT); + Completion transfer = s.messageTransfer(arg::content=msg, arg::sync=true); + intrusive_ptr<PersistableMessage> enqueued = store->enqueued.pop(TIME_SEC); + enqueued->enqueueComplete(); + transfer.wait(); + } + // Send a sync, make sure it completes immediately + Completion sync = s.executionSync(arg::sync=true); + sync.wait(); // Should complete now, all messages are completed. +} + +QPID_AUTO_TEST_CASE(testGetResult) { + SessionFixture fix; + AsyncSession s = fix.session; + + s.queueDeclare("q", arg::durable=true); + TypedResult<QueueQueryResult> tr = s.queueQuery("q"); + QueueQueryResult qq = tr.get(); + BOOST_CHECK_EQUAL(qq.getQueue(), "q"); + BOOST_CHECK_EQUAL(qq.getMessageCount(), 0U); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/AtomicValue.cpp b/qpid/cpp/src/tests/AtomicValue.cpp new file mode 100644 index 0000000000..d855d993a7 --- /dev/null +++ b/qpid/cpp/src/tests/AtomicValue.cpp @@ -0,0 +1,54 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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. + * + */ + + +#include "unit_test.h" +#include "test_tools.h" +#include "qpid/sys/AtomicValue.h" + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(AtomicValueTestSuite) + +QPID_AUTO_TEST_CASE(test) { + qpid::sys::AtomicValue<int> x(0); + BOOST_CHECK_EQUAL(++x, 1); + BOOST_CHECK_EQUAL(--x,0); + BOOST_CHECK_EQUAL(x+=5,5); + BOOST_CHECK_EQUAL(x-=10,-5); + BOOST_CHECK_EQUAL(x.fetchAndAdd(7), -5); + BOOST_CHECK_EQUAL(x.get(),2); + BOOST_CHECK_EQUAL(x.fetchAndSub(3), 2); + BOOST_CHECK_EQUAL(x.get(),-1); + + BOOST_CHECK_EQUAL(x.valueCompareAndSwap(-1,10), -1); + BOOST_CHECK_EQUAL(x.get(), 10); + BOOST_CHECK_EQUAL(x.valueCompareAndSwap(5, 6), 10); + BOOST_CHECK_EQUAL(x.get(), 10); + + BOOST_CHECK(!x.boolCompareAndSwap(5, 6)); + BOOST_CHECK_EQUAL(x.get(), 10); + BOOST_CHECK(x.boolCompareAndSwap(10, 6)); + BOOST_CHECK_EQUAL(x.get(), 6); +} + + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/Blob.cpp b/qpid/cpp/src/tests/Blob.cpp new file mode 100644 index 0000000000..9878d92fe4 --- /dev/null +++ b/qpid/cpp/src/tests/Blob.cpp @@ -0,0 +1,21 @@ +/* + * + * 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. + * + */ + diff --git a/qpid/cpp/src/tests/BrokerFixture.h b/qpid/cpp/src/tests/BrokerFixture.h new file mode 100644 index 0000000000..474b9d747f --- /dev/null +++ b/qpid/cpp/src/tests/BrokerFixture.h @@ -0,0 +1,168 @@ +#ifndef TESTS_BROKERFIXTURE_H +#define TESTS_BROKERFIXTURE_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/broker/Broker.h" +#include "qpid/broker/BrokerOptions.h" +#include "qpid/client/Connection.h" +#include "qpid/client/ConnectionImpl.h" +#include "qpid/client/Session.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/client/LocalQueue.h" +#include "qpid/log/Logger.h" +#include "qpid/log/Options.h" +#include "qpid/sys/Thread.h" +#include <boost/noncopyable.hpp> + +namespace qpid { +namespace tests { + +/** + * A fixture with an in-process broker. + */ +struct BrokerFixture : private boost::noncopyable { + typedef qpid::broker::Broker Broker; + typedef boost::intrusive_ptr<Broker> BrokerPtr; + typedef qpid::broker::BrokerOptions BrokerOptions; + typedef std::vector<std::string> Args; + + BrokerPtr broker; + BrokerOptions opts; + uint16_t port; + qpid::sys::Thread brokerThread; + + BrokerFixture(const Args& args=Args(), const BrokerOptions& opts0=BrokerOptions(), + bool isExternalPort_=false, uint16_t externalPort_=0) : + opts(opts0) + { + init(args, isExternalPort_, externalPort_); + } + + BrokerFixture(const BrokerOptions& opts0, + bool isExternalPort_=false, uint16_t externalPort_=0) : + opts(opts0) + { + init(Args(), isExternalPort_, externalPort_); + } + + void shutdownBroker() { + if (broker) { + broker->shutdown(); + brokerThread.join(); + broker = BrokerPtr(); + } + } + + ~BrokerFixture() { shutdownBroker(); } + + /** Open a connection to the broker. */ + void open(qpid::client::Connection& c) { + c.open("localhost", getPort()); + } + + uint16_t getPort() { return port; } + + private: + void init(const Args& args, bool isExternalPort=false, uint16_t externalPort=0) + { + // Keep the tests quiet unless logging env. vars have been set by user. + if (!::getenv("QPID_LOG_ENABLE") && !::getenv("QPID_TRACE")) { + qpid::log::Options logOpts; + logOpts.selectors.clear(); + logOpts.deselectors.clear(); + logOpts.selectors.push_back("error+"); + qpid::log::Logger::instance().configure(logOpts); + } + // Default options, may be over-ridden when we parse args. + opts.port=0; + opts.listenInterfaces.push_back("127.0.0.1"); + opts.workerThreads=1; + opts.dataDir=""; + opts.auth=false; + + // Argument parsing + if (args.size() > 0) { + std::vector<const char*> argv(args.size()); + for (size_t i = 0; i<args.size(); ++i) + argv[i] = args[i].c_str(); + Plugin::addOptions(opts); + opts.parse(argv.size(), &argv[0]); + } + broker = Broker::create(opts); + // TODO aconway 2007-12-05: At one point BrokerFixture + // tests could hang in Connection ctor if the following + // line is removed. This may not be an issue anymore. + broker->accept(); + if (isExternalPort) port = externalPort; + else port = broker->getPort(qpid::broker::Broker::TCP_TRANSPORT); + brokerThread = qpid::sys::Thread(*broker); + }; +}; + +/** Connection that opens in its constructor */ +struct LocalConnection : public qpid::client::Connection { + LocalConnection(uint16_t port) { open("localhost", port); } + LocalConnection(const qpid::client::ConnectionSettings& s) { open(s); } + ~LocalConnection() { close(); } +}; + +/** Convenience class to create and open a connection and session + * and some related useful objects. + */ +template <class ConnectionType=LocalConnection, class SessionType=qpid::client::Session> +struct ClientT { + ConnectionType connection; + SessionType session; + qpid::client::SubscriptionManager subs; + qpid::client::LocalQueue lq; + std::string name; + + ClientT(uint16_t port, const std::string& name_=std::string(), int timeout=0) + : connection(port), session(connection.newSession(name_,timeout)), subs(session), name(name_) {} + ClientT(const qpid::client::ConnectionSettings& settings, const std::string& name_=std::string(), int timeout=0) + : connection(settings), session(connection.newSession(name_, timeout)), subs(session), name(name_) {} + + ~ClientT() { close(); } + void close() { session.close(); connection.close(); } +}; + +typedef ClientT<> Client; + +/** + * A BrokerFixture and ready-connected BrokerFixture::Client all in one. + */ +template <class ConnectionType, class SessionType=qpid::client::Session> +struct SessionFixtureT : BrokerFixture, ClientT<ConnectionType,SessionType> { + + SessionFixtureT(const BrokerOptions& opts=BrokerOptions()) : + BrokerFixture(BrokerFixture::Args(), opts), + ClientT<ConnectionType,SessionType>(getPort()) + {} + +}; + +typedef SessionFixtureT<LocalConnection> SessionFixture; + +}} // namespace qpid::tests + +#endif /*!TESTS_BROKERFIXTURE_H*/ diff --git a/qpid/cpp/src/tests/BrokerMgmtAgent.cpp b/qpid/cpp/src/tests/BrokerMgmtAgent.cpp new file mode 100644 index 0000000000..bad7e768a6 --- /dev/null +++ b/qpid/cpp/src/tests/BrokerMgmtAgent.cpp @@ -0,0 +1,387 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "unit_test.h" +#include "MessagingFixture.h" +#include "qpid/management/Buffer.h" +#include "qpid/management/ManagementAgent.h" +#include "qpid/messaging/Message.h" +#include "qpid/amqp_0_10/Codecs.h" +#include "qpid/log/Logger.h" +#include "qpid/log/Options.h" + +#include "qmf/org/apache/qpid/broker/mgmt/test/TestObject.h" + +#include <iomanip> + + +using qpid::management::Mutex; +using qpid::management::Manageable; +using qpid::management::Buffer; +using namespace qpid::messaging; +using namespace qpid::types; + + + +namespace qpid { +namespace tests { + +namespace _qmf = qmf::org::apache::qpid::broker::mgmt::test; +namespace { + +typedef boost::shared_ptr<_qmf::TestObject> TestObjectPtr; +typedef std::vector<TestObjectPtr> TestObjectVector; + +// Instantiates a broker and its internal management agent. Provides +// factories for constructing Receivers for object indication messages. +// +class AgentFixture +{ + MessagingFixture *mFix; + + public: + AgentFixture( unsigned int pubInterval=10, + bool qmfV2=false, + qpid::broker::Broker::Options opts = qpid::broker::Broker::Options()) + { + opts.enableMgmt=true; + opts.qmf1Support=!qmfV2; + opts.qmf2Support=qmfV2; + opts.mgmtPubInterval=pubInterval; + mFix = new MessagingFixture(opts, true); + + _qmf::TestObject::registerSelf(getBrokerAgent()); + }; + ~AgentFixture() + { + delete mFix; + }; + ::qpid::management::ManagementAgent *getBrokerAgent() { return mFix->broker->getManagementAgent(); } + Receiver createV1DataIndRcvr( const std::string package, const std::string klass ) + { + return mFix->session.createReceiver(std::string("kqueue; {create: always, delete: always, " + "node: {type: queue, " + "x-bindings: [{exchange: qpid.management, " + "key: 'console.obj.1.0.") + + package + std::string(".") + klass + + std::string("'}]}}")); + }; + Receiver createV2DataIndRcvr( const std::string package, const std::string klass ) + { + std::string p(package); + std::replace(p.begin(), p.end(), '.', '_'); + std::string k(klass); + std::replace(k.begin(), k.end(), '.', '_'); + + return mFix->session.createReceiver(std::string("kqueue; {create: always, delete: always, " + "node: {type: queue, " + "x-bindings: [{exchange: qmf.default.topic, " + "key: 'agent.ind.data.") + + p + std::string(".") + k + + std::string("'}]}}")); + }; +}; + + +// A "management object" that supports the TestObject +// +class TestManageable : public qpid::management::Manageable +{ + management::ManagementObject::shared_ptr mgmtObj; + const std::string key; + public: + TestManageable(management::ManagementAgent *agent, std::string _key) + : key(_key) + { + _qmf::TestObject::shared_ptr tmp(new _qmf::TestObject(agent, this)); + + // seed it with some default values... + tmp->set_string1(key); + tmp->set_bool1(true); + qpid::types::Variant::Map vMap; + vMap["one"] = qpid::types::Variant(1); + vMap["two"] = qpid::types::Variant("two"); + vMap["three"] = qpid::types::Variant("whatever"); + tmp->set_map1(vMap); + + mgmtObj = tmp; + }; + ~TestManageable() { mgmtObj.reset(); } + management::ManagementObject::shared_ptr GetManagementObject() const { return mgmtObj; }; + static void validateTestObjectProperties(_qmf::TestObject& to) + { + // verify the default values are as expected. We don't check 'string1', + // as it is the object key, and is unique for each object (no default value). + BOOST_CHECK(to.get_bool1() == true); + BOOST_CHECK(to.get_map1().size() == 3); + qpid::types::Variant::Map mappy = to.get_map1(); + BOOST_CHECK(1 == (unsigned int)mappy["one"]); + BOOST_CHECK(mappy["two"].asString() == std::string("two")); + BOOST_CHECK(mappy["three"].asString() == std::string("whatever")); + }; +}; + + +// decode a V1 Content Indication message +// +void decodeV1ObjectUpdates(const Message& inMsg, TestObjectVector& objs, const size_t objLen) +{ + const size_t MAX_BUFFER_SIZE=65536; + char tmp[MAX_BUFFER_SIZE]; + + objs.clear(); + + BOOST_CHECK(inMsg.getContent().size() <= MAX_BUFFER_SIZE); + + ::memcpy(tmp, inMsg.getContent().data(), inMsg.getContent().size()); + Buffer buf(tmp, inMsg.getContent().size()); + + while (buf.available() > 8) { // 8 == qmf v1 header size + BOOST_CHECK_EQUAL(buf.getOctet(), 'A'); + BOOST_CHECK_EQUAL(buf.getOctet(), 'M'); + BOOST_CHECK_EQUAL(buf.getOctet(), '2'); + BOOST_CHECK_EQUAL(buf.getOctet(), 'c'); // opcode == content indication + // @@todo: kag: how do we skip 'i' entries??? + buf.getLong(); // ignore sequence + + std::string str1; // decode content body as string + buf.getRawData(str1, objLen); + + TestObjectPtr fake(new _qmf::TestObject(0,0)); + fake->readProperties( str1 ); + objs.push_back(fake); + } +} + + +// decode a V2 Content Indication message +// +void decodeV2ObjectUpdates(const qpid::messaging::Message& inMsg, TestObjectVector& objs) +{ + objs.clear(); + + BOOST_CHECK_EQUAL(inMsg.getContentType(), std::string("amqp/list")); + + const ::qpid::types::Variant::Map& m = inMsg.getProperties(); + Variant::Map::const_iterator iter = m.find(std::string("qmf.opcode")); + BOOST_CHECK(iter != m.end()); + BOOST_CHECK_EQUAL(iter->second.asString(), std::string("_data_indication")); + + Variant::List vList; + ::qpid::amqp_0_10::ListCodec::decode(inMsg.getContent(), vList); + + for (Variant::List::iterator lIter = vList.begin(); lIter != vList.end(); lIter++) { + TestObjectPtr fake(new _qmf::TestObject(0,0)); + fake->readTimestamps(lIter->asMap()); + fake->mapDecodeValues((lIter->asMap())["_values"].asMap()); + objs.push_back(fake); + } +} +} + +QPID_AUTO_TEST_SUITE(BrokerMgmtAgent) + +// verify that an object that is added to the broker's management database is +// published correctly. Furthermore, verify that it is published once after +// it has been deleted. +// +QPID_AUTO_TEST_CASE(v1ObjPublish) +{ + AgentFixture* fix = new AgentFixture(3); + management::ManagementAgent* agent; + agent = fix->getBrokerAgent(); + + // create a manageable test object + TestManageable *tm = new TestManageable(agent, std::string("obj1")); + uint32_t objLen = tm->GetManagementObject()->writePropertiesSize(); + + Receiver r1 = fix->createV1DataIndRcvr("org.apache.qpid.broker.mgmt.test", "#"); + + agent->addObject(tm->GetManagementObject(), 1); + + // wait for the object to be published + Message m1; + BOOST_CHECK(r1.fetch(m1, Duration::SECOND * 6)); + + TestObjectVector objs; + decodeV1ObjectUpdates(m1, objs, objLen); + BOOST_CHECK(objs.size() > 0); + + for (TestObjectVector::iterator oIter = objs.begin(); oIter != objs.end(); oIter++) { + + TestManageable::validateTestObjectProperties(**oIter); + + qpid::types::Variant::Map mappy; + (*oIter)->writeTimestamps(mappy); + BOOST_CHECK(0 == mappy["_delete_ts"].asUint64()); // not deleted + } + + // destroy the object + + tm->GetManagementObject()->resourceDestroy(); + + // wait for the deleted object to be published + + bool isDeleted = false; + while (!isDeleted && r1.fetch(m1, Duration::SECOND * 6)) { + + decodeV1ObjectUpdates(m1, objs, objLen); + BOOST_CHECK(objs.size() > 0); + + for (TestObjectVector::iterator oIter = objs.begin(); oIter != objs.end(); oIter++) { + + TestManageable::validateTestObjectProperties(**oIter); + + qpid::types::Variant::Map mappy; + (*oIter)->writeTimestamps(mappy); + if (mappy["_delete_ts"].asUint64() != 0) + isDeleted = true; + } + } + + BOOST_CHECK(isDeleted); + + r1.close(); + delete fix; + delete tm; +} + +// Repeat the previous test, but with V2-based object support +// +QPID_AUTO_TEST_CASE(v2ObjPublish) +{ + AgentFixture* fix = new AgentFixture(3, true); + management::ManagementAgent* agent; + agent = fix->getBrokerAgent(); + + TestManageable *tm = new TestManageable(agent, std::string("obj2")); + + Receiver r1 = fix->createV2DataIndRcvr(tm->GetManagementObject()->getPackageName(), "#"); + + agent->addObject(tm->GetManagementObject(), "testobj-1"); + + // wait for the object to be published + Message m1; + BOOST_CHECK(r1.fetch(m1, Duration::SECOND * 6)); + + TestObjectVector objs; + decodeV2ObjectUpdates(m1, objs); + BOOST_CHECK(objs.size() > 0); + + for (TestObjectVector::iterator oIter = objs.begin(); oIter != objs.end(); oIter++) { + + TestManageable::validateTestObjectProperties(**oIter); + + qpid::types::Variant::Map mappy; + (*oIter)->writeTimestamps(mappy); + BOOST_CHECK(0 == mappy["_delete_ts"].asUint64()); + } + + // destroy the object + + tm->GetManagementObject()->resourceDestroy(); + + // wait for the deleted object to be published + + bool isDeleted = false; + while (!isDeleted && r1.fetch(m1, Duration::SECOND * 6)) { + + decodeV2ObjectUpdates(m1, objs); + BOOST_CHECK(objs.size() > 0); + + for (TestObjectVector::iterator oIter = objs.begin(); oIter != objs.end(); oIter++) { + + TestManageable::validateTestObjectProperties(**oIter); + + qpid::types::Variant::Map mappy; + (*oIter)->writeTimestamps(mappy); + if (mappy["_delete_ts"].asUint64() != 0) + isDeleted = true; + } + } + + BOOST_CHECK(isDeleted); + + r1.close(); + delete fix; + delete tm; +} + +// See QPID-2997 +QPID_AUTO_TEST_CASE(v2RapidRestoreObj) +{ + AgentFixture* fix = new AgentFixture(3, true); + management::ManagementAgent* agent; + agent = fix->getBrokerAgent(); + + // two objects, same ObjID + TestManageable *tm1 = new TestManageable(agent, std::string("obj2")); + TestManageable *tm2 = new TestManageable(agent, std::string("obj2")); + + Receiver r1 = fix->createV2DataIndRcvr(tm1->GetManagementObject()->getPackageName(), "#"); + + // add, then immediately delete and re-add a copy of the object + agent->addObject(tm1->GetManagementObject(), "testobj-1"); + tm1->GetManagementObject()->resourceDestroy(); + agent->addObject(tm2->GetManagementObject(), "testobj-1"); + + // expect: a delete notification, then an update notification + TestObjectVector objs; + bool isDeleted = false; + bool isAdvertised = false; + size_t count = 0; + Message m1; + while (r1.fetch(m1, Duration::SECOND * 6)) { + + decodeV2ObjectUpdates(m1, objs); + BOOST_CHECK(objs.size() > 0); + + for (TestObjectVector::iterator oIter = objs.begin(); oIter != objs.end(); oIter++) { + count++; + TestManageable::validateTestObjectProperties(**oIter); + + qpid::types::Variant::Map mappy; + (*oIter)->writeTimestamps(mappy); + if (mappy["_delete_ts"].asUint64() != 0) { + isDeleted = true; + BOOST_CHECK(isAdvertised == false); // delete must be first + } else { + isAdvertised = true; + BOOST_CHECK(isDeleted == true); // delete must be first + } + } + } + + BOOST_CHECK(isDeleted); + BOOST_CHECK(isAdvertised); + BOOST_CHECK(count == 2); + + r1.close(); + delete fix; + delete tm1; + delete tm2; +} + +QPID_AUTO_TEST_SUITE_END() +} +} + + diff --git a/qpid/cpp/src/tests/BrokerMgmtAgent.xml b/qpid/cpp/src/tests/BrokerMgmtAgent.xml new file mode 100644 index 0000000000..202b8debf3 --- /dev/null +++ b/qpid/cpp/src/tests/BrokerMgmtAgent.xml @@ -0,0 +1,38 @@ +<schema package="org.apache.qpid.broker.mgmt.test"> + +<!-- + 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. +--> + + <!-- + =============================================================== + TestObject + =============================================================== + --> + <class name="TestObject"> + + A test object defined for the BrokerMgmtAgent unit test. + + <property name="string1" type="lstr" access="RW" index="y"/> + <property name="bool1" type="bool" access="RW"/> + <property name="map1" type="map" access="RW"/> + + </class> + +</schema> + diff --git a/qpid/cpp/src/tests/BrokerOptions.cpp b/qpid/cpp/src/tests/BrokerOptions.cpp new file mode 100644 index 0000000000..b36d96916a --- /dev/null +++ b/qpid/cpp/src/tests/BrokerOptions.cpp @@ -0,0 +1,79 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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. + * + */ + +/** Unit tests for various broker configuration options **/ + +#include "unit_test.h" +#include "test_tools.h" +#include "MessagingFixture.h" + +#include "qpid/messaging/Address.h" +#include "qpid/messaging/Connection.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/Receiver.h" +#include "qpid/messaging/Sender.h" +#include "qpid/messaging/Session.h" + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(BrokerOptionsTestSuite) + +using namespace qpid::broker; +using namespace qpid::messaging; +using namespace qpid::types; +using namespace qpid; + +QPID_AUTO_TEST_CASE(testDisabledTimestamp) +{ + // by default, there should be no timestamp added by the broker + MessagingFixture fix; + + Sender sender = fix.session.createSender("test-q; {create:always, delete:sender}"); + messaging::Message msg("hi"); + sender.send(msg); + + Receiver receiver = fix.session.createReceiver("test-q"); + messaging::Message in; + BOOST_CHECK(receiver.fetch(in, Duration::IMMEDIATE)); + Variant::Map props = in.getProperties(); + BOOST_CHECK(props.find("x-amqp-0-10.timestamp") == props.end()); +} + +QPID_AUTO_TEST_CASE(testEnabledTimestamp) +{ + // when enabled, the 0.10 timestamp is added by the broker + Broker::Options opts; + opts.timestampRcvMsgs = true; + MessagingFixture fix(opts, true); + + Sender sender = fix.session.createSender("test-q; {create:always, delete:sender}"); + messaging::Message msg("one"); + sender.send(msg); + + Receiver receiver = fix.session.createReceiver("test-q"); + messaging::Message in; + BOOST_CHECK(receiver.fetch(in, Duration::IMMEDIATE)); + Variant::Map props = in.getProperties(); + BOOST_CHECK(props.find("x-amqp-0-10.timestamp") != props.end()); + BOOST_CHECK(props["x-amqp-0-10.timestamp"]); +} + +QPID_AUTO_TEST_SUITE_END() + +}} diff --git a/qpid/cpp/src/tests/CMakeLists.txt b/qpid/cpp/src/tests/CMakeLists.txt new file mode 100644 index 0000000000..20f98204a4 --- /dev/null +++ b/qpid/cpp/src/tests/CMakeLists.txt @@ -0,0 +1,402 @@ +# +# 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. +# + +# Make sure that everything get built before the tests +# Need to create a var with all the necessary top level targets + +# If we're linking Boost for DLLs, turn that on for the unit test too. +if (QPID_LINK_BOOST_DYNAMIC) + add_definitions(-DBOOST_TEST_DYN_LINK) +endif (QPID_LINK_BOOST_DYNAMIC) + +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ) + +# Using the Boost DLLs triggers warning 4275 on Visual Studio +# (non dll-interface class used as base for dll-interface class). +# This is ok, so suppress the warning. +# Also, boost lengthy names trigger warning 4503, decorated name length exceeded +# and using getenv() triggers insecure CRT warnings which we can silence in the +# test environment. +if (MSVC) + add_definitions( /wd4275 /wd4503 /D_CRT_SECURE_NO_WARNINGS) +endif (MSVC) + +# Macro to make it easier to remember where the tests are built +macro(remember_location testname) + set (${testname}_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/${testname}${CMAKE_EXECUTABLE_SUFFIX}) +endmacro(remember_location) + +# If we're using GCC allow variadic macros (even though they're c99 not c++01) +if (CMAKE_COMPILER_IS_GNUCXX) + add_definitions(-Wno-variadic-macros) +endif (CMAKE_COMPILER_IS_GNUCXX) + +# Windows uses some process-startup calls to ensure that errors, etc. don't +# result in error boxes being thrown up. Since it's expected that most test +# runs will be in scripts, the default is to force these outputs to stderr +# instead of windows. If you want to remove this code, build without the +# QPID_WINDOWS_DEFAULT_TEST_OUTPUTS ON. +if (CMAKE_SYSTEM_NAME STREQUAL Windows) + option(QPID_WINDOWS_DEFAULT_TEST_OUTPUTS "Use default error-handling on Windows tests" OFF) + if (NOT QPID_WINDOWS_DEFAULT_TEST_OUTPUTS) + set(platform_test_additions windows/DisableWin32ErrorWindows.cpp) + endif (NOT QPID_WINDOWS_DEFAULT_TEST_OUTPUTS) +endif (CMAKE_SYSTEM_NAME STREQUAL Windows) + +# Some generally useful utilities that just happen to be built in the test area +add_executable (qpid-receive qpid-receive.cpp Statistics.cpp ${platform_test_additions}) +target_link_libraries (qpid-receive qpidmessaging qpidtypes qpidcommon) +remember_location(qpid-receive) + +add_executable (qpid-send qpid-send.cpp Statistics.cpp ${platform_test_additions}) +target_link_libraries (qpid-send qpidmessaging qpidtypes qpidcommon) +remember_location(qpid-send) + +install (TARGETS + qpid-receive qpid-send + RUNTIME DESTINATION ${QPID_INSTALL_BINDIR}) + +add_executable (qpid-perftest qpid-perftest.cpp ${platform_test_additions}) +target_link_libraries (qpid-perftest qpidclient qpidcommon ${Boost_PROGRAM_OPTIONS_LIBRARY}) +remember_location(qpid-perftest) + +add_executable (qpid-latency-test qpid-latency-test.cpp ${platform_test_additions}) +target_link_libraries (qpid-latency-test qpidclient qpidcommon) +remember_location(qpid-latency-test) + +add_executable (qpid-client-test qpid-client-test.cpp ${platform_test_additions}) +target_link_libraries (qpid-client-test qpidclient qpidcommon) +remember_location(qpid-client-test) + +add_executable (qpid-ping qpid-ping.cpp ${platform_test_additions}) +target_link_libraries (qpid-ping qpidmessaging qpidtypes qpidcommon) +remember_location(qpid-ping) + +add_executable (qpid-topic-listener qpid-topic-listener.cpp ${platform_test_additions}) +target_link_libraries (qpid-topic-listener qpidclient qpidcommon) +remember_location(qpid-topic-listener) + +add_executable (qpid-topic-publisher qpid-topic-publisher.cpp ${platform_test_additions}) +target_link_libraries (qpid-topic-publisher qpidclient qpidcommon) +remember_location(qpid-topic-publisher) + +add_executable (receiver receiver.cpp ${platform_test_additions}) +target_link_libraries (receiver qpidclient qpidcommon) +remember_location(receiver) + +# This is bizarre - using both messaging and client libraries +add_executable (sender sender.cpp Statistics.cpp ${platform_test_additions}) +target_link_libraries (sender qpidmessaging qpidtypes qpidclient qpidcommon) +remember_location(sender) + +add_executable (qpid-txtest qpid-txtest.cpp ${platform_test_additions}) +target_link_libraries (qpid-txtest qpidclient qpidcommon qpidtypes) +#qpid_txtest_SOURCES=qpid-txtest.cpp TestOptions.h ConnectionOptions.h +remember_location(qpid-txtest) + +add_executable (qpid-txtest2 qpid-txtest2.cpp ${platform_test_additions}) +target_link_libraries (qpid-txtest2 qpidmessaging qpidtypes qpidcommon) +remember_location(qpid-txtest2) + +install (TARGETS + qpid-perftest qpid-latency-test qpid-client-test + qpid-ping + qpid-topic-listener qpid-topic-publisher receiver sender + qpid-txtest qpid-txtest2 + RUNTIME DESTINATION ${QPID_INSTALL_TESTDIR}) + +# Only build test code if testing is turned on +if (BUILD_TESTING) + +# Create the environment scripts for tests +set (abs_srcdir ${CMAKE_CURRENT_SOURCE_DIR}) +set (abs_builddir ${CMAKE_CURRENT_BINARY_DIR}) +set (abs_top_srcdir ${CMAKE_SOURCE_DIR}) +set (abs_top_builddir ${CMAKE_BINARY_DIR}) +set (builddir_lib_suffix "") + +if (CMAKE_SYSTEM_NAME STREQUAL Windows) + configure_file (${CMAKE_CURRENT_SOURCE_DIR}/test_env.ps1.in + ${CMAKE_CURRENT_BINARY_DIR}/test_env.ps1 @ONLY) +else (CMAKE_SYSTEM_NAME STREQUAL Windows) + configure_file (${CMAKE_CURRENT_SOURCE_DIR}/test_env.sh.in + ${CMAKE_CURRENT_BINARY_DIR}/test_env.sh @ONLY) +endif (CMAKE_SYSTEM_NAME STREQUAL Windows) + +# Copy qpidd-p0 script to build directory so tests can find it. +configure_file (${CMAKE_CURRENT_SOURCE_DIR}/qpidd-p0 ${CMAKE_CURRENT_BINARY_DIR} COPYONLY) + +if (CMAKE_SYSTEM_NAME STREQUAL Windows) + set (ENV{OUTDIR} ${EXECUTABLE_OUTPUT_PATH}) + set (test_script_suffix ".ps1") + set (shell "powershell") +endif (CMAKE_SYSTEM_NAME STREQUAL Windows) + +set(test_wrap ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_test${test_script_suffix} -buildDir ${CMAKE_BINARY_DIR}) +set(python_wrap ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_test${test_script_suffix} -buildDir ${CMAKE_BINARY_DIR} -python) + +if (BUILD_TESTING_UNITTESTS) + +# +# Unit test program +# +# Unit tests are built as a single program to reduce valgrind overhead +# when running the tests. If you want to build a subset of the tests run +# ccmake and set unit_tests_to_build to the set you want to build. + +# Like this to work with cmake 2.4 on Unix +set (qpid_test_boost_libs + ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} ${Boost_SYSTEM_LIBRARY}) + +set(all_unit_tests + AccumulatedAckTest + Acl + AclHost + Array + AsyncCompletion + AtomicValue + ClientMessage + ClientMessageTest + ClientSessionTest + DeliveryRecordTest + DtxWorkRecordTest + exception_test + ExchangeTest + FieldTable + FieldValue + FrameDecoder + FramingTest + HeadersExchangeTest + HeaderTest + InlineAllocator + InlineVector + logging + ManagementTest + MessageReplayTracker + MessageTest + MessagingLogger + MessagingSessionTests + PollableCondition + ProxyTest + QueueDepth + QueueFlowLimitTest + QueueOptionsTest + QueuePolicyTest + QueueRegistryTest + QueueTest + RangeSet + RefCounted + RetryList + Selector + SequenceNumberTest + SequenceSet + SessionState + Shlib + StringUtils + SystemInfo + TimerTest + TopicExchangeTest + TxBufferTest + TransactionObserverTest + Url + Uuid + Variant + ${xml_tests} + ) + +set(unit_tests_to_build + "" + CACHE STRING "Which unit tests to build" + ) + +mark_as_advanced(unit_tests_to_build) + +# If no unit_test specifically set then use all unit tests +if (unit_tests_to_build) +set(actual_unit_tests ${unit_tests_to_build}) +else() +set(actual_unit_tests ${all_unit_tests}) +endif() + +add_executable (unit_test unit_test + ${actual_unit_tests} ${platform_test_additions}) +target_link_libraries (unit_test + ${qpid_test_boost_libs} + qpidmessaging qpidtypes qpidbroker qpidclient qpidcommon) +set_target_properties (unit_test PROPERTIES COMPILE_DEFINITIONS _IN_QPID_BROKER) +remember_location(unit_test) + +add_test (unit_test ${test_wrap} -boostTest -- ${unit_test_LOCATION}) + +endif (BUILD_TESTING_UNITTESTS) + +add_library (shlibtest MODULE shlibtest.cpp) + +if (BUILD_SASL) + add_custom_command( + OUTPUT sasl_config/qpidd.conf sasl_config/qpidd.sasldb + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/sasl_test_setup.sh) + + add_custom_target( + sasl_config ALL + DEPENDS sasl_config/qpidd.conf sasl_config/qpidd.sasldb) +endif (BUILD_SASL) + +# +# Other test programs +# +add_executable (echotest echotest.cpp ${platform_test_additions}) +target_link_libraries (echotest qpidclient qpidcommon) +remember_location(echotest) + +add_executable (publish publish.cpp ${platform_test_additions}) +target_link_libraries (publish qpidclient qpidcommon) +remember_location(publish) + +add_executable (consume consume.cpp ${platform_test_additions}) +target_link_libraries (consume qpidclient qpidcommon) +remember_location(consume) + +add_executable (header_test header_test.cpp ${platform_test_additions}) +target_link_libraries (header_test qpidclient qpidcommon) +remember_location(header_test) + +add_executable (declare_queues declare_queues.cpp ${platform_test_additions}) +target_link_libraries (declare_queues qpidclient qpidcommon) +remember_location(declare_queues) + +add_executable (replaying_sender replaying_sender.cpp ${platform_test_additions}) +target_link_libraries (replaying_sender qpidclient qpidcommon) +remember_location(replaying_sender) + +add_executable (resuming_receiver resuming_receiver.cpp ${platform_test_additions}) +target_link_libraries (resuming_receiver qpidclient qpidcommon) +remember_location(resuming_receiver) + +add_executable (txshift txshift.cpp ${platform_test_additions}) +target_link_libraries (txshift qpidclient qpidcommon) +remember_location(txshift) + +add_executable (txjob txjob.cpp ${platform_test_additions}) +target_link_libraries (txjob qpidclient qpidcommon) +remember_location(txjob) + +add_executable (datagen datagen.cpp ${platform_test_additions}) +target_link_libraries (datagen qpidclient qpidcommon) +remember_location(datagen) + +add_executable (msg_group_test msg_group_test.cpp ${platform_test_additions}) +target_link_libraries (msg_group_test qpidmessaging qpidtypes qpidcommon) +remember_location(msg_group_test) + +add_executable (ha_test_max_queues ha_test_max_queues.cpp ${platform_test_additions}) +target_link_libraries (ha_test_max_queues qpidclient qpidcommon) +remember_location(ha_test_max_queues) + +if (BUILD_SASL) + add_executable (sasl_version sasl_version.cpp ${platform_test_additions}) + remember_location(sasl_version) +endif (BUILD_SASL) + +set (python_src ${CMAKE_SOURCE_DIR}/../python) +if (EXISTS ${python_src}) + set (python_bld ${CMAKE_CURRENT_BINARY_DIR}/python) + # This will not pick up added or deleted python files + # In that case you need to rerun CMake + file(GLOB_RECURSE python_files ${python_src}/*.py) + + add_custom_command( + OUTPUT ${python_bld} + DEPENDS ${python_files} + COMMAND ${PYTHON_EXECUTABLE} + setup.py + build --build-base=${python_bld}/build + install --prefix=${python_bld} --install-lib=${python_bld} --install-scripts=${python_bld}/commands + WORKING_DIRECTORY ${python_src} + ) + + add_custom_target( + python_bld ALL + DEPENDS ${python_bld} + ) +endif (EXISTS ${python_src}) + +if (BUILD_SASL) + add_test (sasl_fed ${test_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/sasl_fed${test_script_suffix}) + add_test (sasl_fed_ex_dynamic ${test_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/sasl_fed_ex${test_script_suffix} dynamic) + add_test (sasl_fed_ex_link ${test_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/sasl_fed_ex${test_script_suffix} link) + add_test (sasl_fed_ex_queue ${test_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/sasl_fed_ex${test_script_suffix} queue) + add_test (sasl_fed_ex_route ${test_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/sasl_fed_ex${test_script_suffix} route) + add_test (sasl_no_dir ${test_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/sasl_no_dir${test_script_suffix}) + if (BUILD_SSL) + add_test(ssl_test ${test_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/ssl_test${test_script_suffix}) + endif (BUILD_SSL) +endif (BUILD_SASL) +add_test (qpid-client-test ${test_wrap} -startBroker -- ${qpid-client-test_LOCATION}) +add_test (quick_perftest ${test_wrap} -startBroker -- ${qpid-perftest_LOCATION} --summary --count 100) +add_test (quick_topictest ${test_wrap} -startBroker -- ${CMAKE_CURRENT_SOURCE_DIR}/quick_topictest${test_script_suffix}) +add_test (quick_txtest ${test_wrap} -startBroker -- ${qpid-txtest_LOCATION} --queues 4 --tx-count 10 --quiet) +add_test (quick_txtest2 ${test_wrap} -startBroker -- ${qpid-txtest2_LOCATION} --queues 4 --tx-count 10 --quiet) +add_test (msg_group_tests ${test_wrap} -startBroker -- ${CMAKE_CURRENT_SOURCE_DIR}/run_msg_group_tests${test_script_suffix}) +add_test (run_header_test ${test_wrap} -startBroker -- ${CMAKE_CURRENT_SOURCE_DIR}/run_header_test${test_script_suffix}) +add_test (python_tests ${test_wrap} -startBroker -- ${CMAKE_CURRENT_SOURCE_DIR}/python_tests${test_script_suffix}) +if (NOT CMAKE_SYSTEM_NAME STREQUAL Windows) + # paged queue not yet implemented for windows + add_test (paged_queue_tests ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_paged_queue_tests${test_script_suffix}) +endif (NOT CMAKE_SYSTEM_NAME STREQUAL Windows) + +if (BUILD_AMQP) + add_test (interop_tests ${python_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/interop_tests.py) +endif (BUILD_AMQP) + +add_test (ha_tests ${python_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/ha_tests.py) +add_test (qpidd_qmfv2_tests ${python_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/qpidd_qmfv2_tests.py) +if (BUILD_AMQP) + add_test (interlink_tests ${python_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/interlink_tests.py) + add_test (idle_timeout_tests ${python_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/idle_timeout_tests.py) +endif (BUILD_AMQP) +add_test (swig_python_tests ${test_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/swig_python_tests${test_script_suffix}) +add_test (ipv6_test ${test_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/ipv6_test${test_script_suffix}) +add_test (federation_tests ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_federation_tests${test_script_suffix}) +add_test (federation_sys_tests ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_federation_sys_tests${test_script_suffix}) +add_test (queue_flow_limit_tests + ${test_wrap} + -startBroker -brokerOptions "--default-flow-stop-threshold=80 --default-flow-resume-threshold=70" + -- ${CMAKE_CURRENT_SOURCE_DIR}/run_queue_flow_limit_tests${test_script_suffix}) +if (BUILD_ACL) + add_test (acl_tests ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_acl_tests${test_script_suffix}) +endif (BUILD_ACL) +add_test (cli_tests ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_cli_tests${test_script_suffix}) +add_test (dynamic_log_level_test ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/dynamic_log_level_test${test_script_suffix}) +add_test (dynamic_log_hires_timestamp ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/dynamic_log_hires_timestamp${test_script_suffix}) +if (BUILD_MSSQL) + add_test (store_tests ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_store_tests${test_script_suffix} MSSQL) +endif (BUILD_MSSQL) +if (BUILD_MSCLFS) + add_test (store_tests_clfs ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_store_tests${test_script_suffix} MSSQL-CLFS) +endif (BUILD_MSCLFS) +add_test (queue_redirect ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_queue_redirect${test_script_suffix}) + +add_library(test_store MODULE test_store.cpp) +target_link_libraries (test_store qpidbroker qpidcommon) +set_target_properties (test_store PROPERTIES PREFIX "" COMPILE_DEFINITIONS _IN_QPID_BROKER) + +add_library (dlclose_noop MODULE dlclose_noop.c) + +endif (BUILD_TESTING) diff --git a/qpid/cpp/src/tests/ClientMessage.cpp b/qpid/cpp/src/tests/ClientMessage.cpp new file mode 100644 index 0000000000..994c46552c --- /dev/null +++ b/qpid/cpp/src/tests/ClientMessage.cpp @@ -0,0 +1,46 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include <iostream> +#include "qpid/messaging/Message.h" + +#include "unit_test.h" + +using namespace qpid::messaging; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(ClientMessageSuite) + +QPID_AUTO_TEST_CASE(testCopyConstructor) +{ + Message m("my-data"); + m.setSubject("my-subject"); + m.getProperties()["a"] = "ABC"; + Message c(m); + BOOST_CHECK_EQUAL(m.getContent(), c.getContent()); + BOOST_CHECK_EQUAL(m.getSubject(), c.getSubject()); + BOOST_CHECK_EQUAL(m.getProperties()["a"], c.getProperties()["a"]); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/ClientMessageTest.cpp b/qpid/cpp/src/tests/ClientMessageTest.cpp new file mode 100644 index 0000000000..f925f1c234 --- /dev/null +++ b/qpid/cpp/src/tests/ClientMessageTest.cpp @@ -0,0 +1,51 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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. + * + */ + + +/**@file Unit tests for the client::Message class. */ + +#include "unit_test.h" +#include "qpid/client/Message.h" + +using namespace qpid::client; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(ClientMessageTestSuite) + +QPID_AUTO_TEST_CASE(MessageCopyAssign) { + // Verify that message has normal copy semantics. + Message m("foo"); + BOOST_CHECK_EQUAL("foo", m.getData()); + Message c(m); + BOOST_CHECK_EQUAL("foo", c.getData()); + Message a; + BOOST_CHECK_EQUAL("", a.getData()); + a = m; + BOOST_CHECK_EQUAL("foo", a.getData()); + a.setData("a"); + BOOST_CHECK_EQUAL("a", a.getData()); + c.setData("c"); + BOOST_CHECK_EQUAL("c", c.getData()); + BOOST_CHECK_EQUAL("foo", m.getData()); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/ClientSessionTest.cpp b/qpid/cpp/src/tests/ClientSessionTest.cpp new file mode 100644 index 0000000000..f35524c0c0 --- /dev/null +++ b/qpid/cpp/src/tests/ClientSessionTest.cpp @@ -0,0 +1,663 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "unit_test.h" +#include "test_tools.h" +#include "BrokerFixture.h" +#include "qpid/client/QueueOptions.h" +#include "qpid/client/MessageListener.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/sys/Monitor.h" +#include "qpid/sys/Thread.h" +#include "qpid/sys/Runnable.h" +#include "qpid/sys/Time.h" +#include "qpid/client/Session.h" +#include "qpid/client/Message.h" +#include "qpid/framing/reply_exceptions.h" + +#include <boost/optional.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/bind.hpp> +#include <boost/ptr_container/ptr_vector.hpp> +#include <boost/format.hpp> + +#include <vector> + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(ClientSessionTest) + +using namespace qpid::client; +using namespace qpid::framing; +using namespace qpid; +using qpid::sys::Monitor; +using qpid::sys::Thread; +using qpid::sys::TIME_SEC; +using qpid::broker::BrokerOptions; +using std::string; +using std::cout; +using std::endl; + + +struct DummyListener : public sys::Runnable, public MessageListener { + std::vector<Message> messages; + string name; + uint expected; + SubscriptionManager submgr; + + DummyListener(Session& session, const string& n, uint ex) : + name(n), expected(ex), submgr(session) {} + + void run() + { + submgr.subscribe(*this, name); + submgr.run(); + } + + void received(Message& msg) + { + messages.push_back(msg); + if (--expected == 0) { + submgr.stop(); + } + } +}; + +struct SimpleListener : public MessageListener +{ + Monitor lock; + std::vector<Message> messages; + + void received(Message& msg) + { + Monitor::ScopedLock l(lock); + messages.push_back(msg); + lock.notifyAll(); + } + + void waitFor(const uint n) + { + Monitor::ScopedLock l(lock); + while (messages.size() < n) { + lock.wait(); + } + } +}; + +struct ClientSessionFixture : public SessionFixture +{ + ClientSessionFixture(const BrokerOptions& opts = BrokerOptions()) : SessionFixture(opts) { + session.queueDeclare(arg::queue="my-queue"); + } +}; + +QPID_AUTO_TEST_CASE(testQueueQuery) { + ClientSessionFixture fix; + fix.session = fix.connection.newSession(); + fix.session.queueDeclare(arg::queue="q", arg::alternateExchange="amq.fanout", + arg::exclusive=true, arg::autoDelete=true); + QueueQueryResult result = fix.session.queueQuery("q"); + BOOST_CHECK_EQUAL(false, result.getDurable()); + BOOST_CHECK_EQUAL(true, result.getExclusive()); + BOOST_CHECK_EQUAL("amq.fanout", result.getAlternateExchange()); +} + +QPID_AUTO_TEST_CASE(testDispatcher) +{ + ClientSessionFixture fix; + fix.session =fix.connection.newSession(); + size_t count = 100; + for (size_t i = 0; i < count; ++i) + fix.session.messageTransfer(arg::content=Message(boost::lexical_cast<string>(i), "my-queue")); + DummyListener listener(fix.session, "my-queue", count); + listener.run(); + BOOST_CHECK_EQUAL(count, listener.messages.size()); + for (size_t i = 0; i < count; ++i) + BOOST_CHECK_EQUAL(boost::lexical_cast<string>(i), listener.messages[i].getData()); +} + +QPID_AUTO_TEST_CASE(testDispatcherThread) +{ + ClientSessionFixture fix; + fix.session =fix.connection.newSession(); + size_t count = 10; + DummyListener listener(fix.session, "my-queue", count); + sys::Thread t(listener); + for (size_t i = 0; i < count; ++i) { + fix.session.messageTransfer(arg::content=Message(boost::lexical_cast<string>(i), "my-queue")); + } + t.join(); + BOOST_CHECK_EQUAL(count, listener.messages.size()); + for (size_t i = 0; i < count; ++i) + BOOST_CHECK_EQUAL(boost::lexical_cast<string>(i), listener.messages[i].getData()); +} + +QPID_AUTO_TEST_CASE(testUseSuspendedError) +{ + ClientSessionFixture fix; + fix.session.timeout(60); + fix.session.suspend(); + try { + fix.session.exchangeQuery(arg::exchange="amq.fanout"); + BOOST_FAIL("Expected session suspended exception"); + } catch(const NotAttachedException&) {} +} + +QPID_AUTO_TEST_CASE(testSendToSelf) { + ClientSessionFixture fix; + SimpleListener mylistener; + fix.session.queueDeclare(arg::queue="myq", arg::exclusive=true, arg::autoDelete=true); + fix.subs.subscribe(mylistener, "myq"); + sys::Thread runner(fix.subs);//start dispatcher thread + string data("msg"); + Message msg(data, "myq"); + const uint count=10; + for (uint i = 0; i < count; ++i) { + fix.session.messageTransfer(arg::content=msg); + } + mylistener.waitFor(count); + fix.subs.cancel("myq"); + fix.subs.stop(); + runner.join(); + fix.session.close(); + BOOST_CHECK_EQUAL(mylistener.messages.size(), count); + for (uint j = 0; j < count; ++j) { + BOOST_CHECK_EQUAL(mylistener.messages[j].getData(), data); + } +} + +QPID_AUTO_TEST_CASE(testLocalQueue) { + ClientSessionFixture fix; + fix.session.queueDeclare(arg::queue="lq", arg::exclusive=true, arg::autoDelete=true); + LocalQueue lq; + fix.subs.subscribe(lq, "lq", FlowControl(2, FlowControl::UNLIMITED, false)); + fix.session.messageTransfer(arg::content=Message("foo0", "lq")); + fix.session.messageTransfer(arg::content=Message("foo1", "lq")); + fix.session.messageTransfer(arg::content=Message("foo2", "lq")); + BOOST_CHECK_EQUAL("foo0", lq.pop().getData()); + BOOST_CHECK_EQUAL("foo1", lq.pop().getData()); + BOOST_CHECK(lq.empty()); // Credit exhausted. + fix.subs.getSubscription("lq").setFlowControl(FlowControl::unlimited()); + BOOST_CHECK_EQUAL("foo2", lq.pop().getData()); +} + +struct DelayedTransfer : sys::Runnable +{ + ClientSessionFixture& fixture; + + DelayedTransfer(ClientSessionFixture& f) : fixture(f) {} + + void run() + { + qpid::sys::sleep(1); + fixture.session.messageTransfer(arg::content=Message("foo2", "getq")); + } +}; + +QPID_AUTO_TEST_CASE(testGet) { + ClientSessionFixture fix; + fix.session.queueDeclare(arg::queue="getq", arg::exclusive=true, arg::autoDelete=true); + fix.session.messageTransfer(arg::content=Message("foo0", "getq")); + fix.session.messageTransfer(arg::content=Message("foo1", "getq")); + Message got; + BOOST_CHECK(fix.subs.get(got, "getq", TIME_SEC)); + BOOST_CHECK_EQUAL("foo0", got.getData()); + BOOST_CHECK(fix.subs.get(got, "getq", TIME_SEC)); + BOOST_CHECK_EQUAL("foo1", got.getData()); + BOOST_CHECK(!fix.subs.get(got, "getq")); + DelayedTransfer sender(fix); + Thread t(sender); + //test timed get where message shows up after a short delay + BOOST_CHECK(fix.subs.get(got, "getq", 5*TIME_SEC)); + BOOST_CHECK_EQUAL("foo2", got.getData()); + t.join(); +} + +QPID_AUTO_TEST_CASE(testOpenFailure) { + BrokerFixture b; + Connection c; + string host("unknowable-host"); + try { + c.open(host); + } catch (const Exception&) { + BOOST_CHECK(!c.isOpen()); + } + b.open(c); + BOOST_CHECK(c.isOpen()); + c.close(); + BOOST_CHECK(!c.isOpen()); +} + +QPID_AUTO_TEST_CASE(testPeriodicExpiration) { + BrokerOptions opts; + opts.queueCleanInterval = 1*TIME_SEC; + opts.queueFlowStopRatio = 0; + opts.queueFlowResumeRatio = 0; + ClientSessionFixture fix(opts); + FieldTable args; + args.setInt("qpid.max_count",10); + fix.session.queueDeclare(arg::queue="my-queue", arg::exclusive=true, arg::autoDelete=true, arg::arguments=args); + + for (uint i = 0; i < 10; i++) { + Message m((boost::format("Message_%1%") % (i+1)).str(), "my-queue"); + if (i % 2) m.getDeliveryProperties().setTtl(500); + fix.session.messageTransfer(arg::content=m); + } + + BOOST_CHECK_EQUAL(fix.session.queueQuery(string("my-queue")).getMessageCount(), 10u); + qpid::sys::sleep(2); + BOOST_CHECK_EQUAL(fix.session.queueQuery(string("my-queue")).getMessageCount(), 5u); + fix.session.messageTransfer(arg::content=Message("Message_11", "my-queue"));//ensure policy is also updated +} + +QPID_AUTO_TEST_CASE(testExpirationOnPop) { + ClientSessionFixture fix; + fix.session.queueDeclare(arg::queue="my-queue", arg::exclusive=true, arg::autoDelete=true); + + for (uint i = 0; i < 10; i++) { + Message m((boost::format("Message_%1%") % (i+1)).str(), "my-queue"); + if (i % 2) m.getDeliveryProperties().setTtl(200); + fix.session.messageTransfer(arg::content=m); + } + + qpid::sys::usleep(300* 1000); + + for (uint i = 0; i < 10; i++) { + if (i % 2) continue; + Message m; + BOOST_CHECK(fix.subs.get(m, "my-queue", TIME_SEC)); + BOOST_CHECK_EQUAL((boost::format("Message_%1%") % (i+1)).str(), m.getData()); + } +} + +QPID_AUTO_TEST_CASE(testRelease) { + ClientSessionFixture fix; + + const uint count=10; + for (uint i = 0; i < count; i++) { + Message m((boost::format("Message_%1%") % (i+1)).str(), "my-queue"); + fix.session.messageTransfer(arg::content=m); + } + + fix.subs.setAutoStop(false); + fix.subs.start(); + SubscriptionSettings settings; + settings.autoAck = 0; + + SimpleListener l1; + Subscription s1 = fix.subs.subscribe(l1, "my-queue", settings); + l1.waitFor(count); + s1.cancel(); + + for (uint i = 0; i < count; i++) { + BOOST_CHECK_EQUAL((boost::format("Message_%1%") % (i+1)).str(), l1.messages[i].getData()); + } + s1.release(s1.getUnaccepted()); + + //check that released messages are redelivered + settings.autoAck = 1; + SimpleListener l2; + Subscription s2 = fix.subs.subscribe(l2, "my-queue", settings); + l2.waitFor(count); + for (uint i = 0; i < count; i++) { + BOOST_CHECK_EQUAL((boost::format("Message_%1%") % (i+1)).str(), l2.messages[i].getData()); + } + + fix.subs.stop(); + fix.subs.wait(); + fix.session.close(); +} + +QPID_AUTO_TEST_CASE(testCompleteOnAccept) { + ClientSessionFixture fix; + const uint count = 8; + const uint chunk = 4; + for (uint i = 0; i < count; i++) { + Message m((boost::format("Message_%1%") % (i+1)).str(), "my-queue"); + fix.session.messageTransfer(arg::content=m); + } + + SubscriptionSettings settings; + settings.autoAck = 0; + settings.completionMode = COMPLETE_ON_ACCEPT; + settings.flowControl = FlowControl::messageWindow(chunk); + + LocalQueue q; + Subscription s = fix.subs.subscribe(q, "my-queue", settings); + fix.session.messageFlush(arg::destination=s.getName()); + SequenceSet accepted; + for (uint i = 0; i < chunk; i++) { + Message m; + BOOST_CHECK(q.get(m)); + BOOST_CHECK_EQUAL((boost::format("Message_%1%") % (i+1)).str(), m.getData()); + accepted.add(m.getId()); + } + Message m; + BOOST_CHECK(!q.get(m)); + + s.accept(accepted); + //need to reallocate credit as we have flushed it all out + s.setFlowControl(FlowControl::messageWindow(chunk)); + fix.session.messageFlush(arg::destination=s.getName()); + accepted.clear(); + + for (uint i = chunk; i < count; i++) { + Message m; + BOOST_CHECK(q.get(m)); + BOOST_CHECK_EQUAL((boost::format("Message_%1%") % (i+1)).str(), m.getData()); + accepted.add(m.getId()); + } + fix.session.messageAccept(accepted); +} + +namespace +{ +struct Publisher : qpid::sys::Runnable +{ + AsyncSession session; + Message message; + uint count; + Thread thread; + + Publisher(Connection& con, Message m, uint c) : session(con.newSession()), message(m), count(c) {} + + void start() + { + thread = Thread(*this); + } + + void join() + { + thread.join(); + } + + void run() + { + for (uint i = 0; i < count; i++) { + session.messageTransfer(arg::content=message); + } + session.sync(); + session.close(); + } +}; +} + +QPID_AUTO_TEST_CASE(testConcurrentSenders) +{ + //Ensure concurrent publishing sessions on a connection don't + //cause assertions, deadlocks or other undesirables: + BrokerFixture fix; + Connection connection; + ConnectionSettings settings; + settings.maxFrameSize = 1024; + settings.port = fix.broker->getPort(qpid::broker::Broker::TCP_TRANSPORT); + connection.open(settings); + AsyncSession session = connection.newSession(); + Message message(string(512, 'X')); + + boost::ptr_vector<Publisher> publishers; + for (size_t i = 0; i < 5; i++) { + publishers.push_back(new Publisher(connection, message, 100)); + } + std::for_each(publishers.begin(), publishers.end(), boost::bind(&Publisher::start, _1)); + std::for_each(publishers.begin(), publishers.end(), boost::bind(&Publisher::join, _1)); + connection.close(); +} + + +QPID_AUTO_TEST_CASE(testExclusiveSubscribe) +{ + ClientSessionFixture fix; + fix.session.queueDeclare(arg::queue="myq", arg::exclusive=true, arg::autoDelete=true); + SubscriptionSettings settings; + settings.exclusive = true; + LocalQueue q; + fix.subs.subscribe(q, "myq", settings, "first"); + //attempt to create new subscriber should fail + ScopedSuppressLogging sl; + BOOST_CHECK_THROW(fix.subs.subscribe(q, "myq", "second"), ResourceLockedException); + ; + +} + +QPID_AUTO_TEST_CASE(testExclusiveBinding) { + FieldTable options; + options.setString("qpid.exclusive-binding", "anything"); + ClientSessionFixture fix; + fix.session.queueDeclare(arg::queue="queue-1", arg::exclusive=true, arg::autoDelete=true); + fix.session.queueDeclare(arg::queue="queue-2", arg::exclusive=true, arg::autoDelete=true); + fix.session.exchangeBind(arg::exchange="amq.direct", arg::queue="queue-1", arg::bindingKey="my-key", arg::arguments=options); + fix.session.messageTransfer(arg::destination="amq.direct", arg::content=Message("message1", "my-key")); + fix.session.exchangeBind(arg::exchange="amq.direct", arg::queue="queue-2", arg::bindingKey="my-key", arg::arguments=options); + fix.session.messageTransfer(arg::destination="amq.direct", arg::content=Message("message2", "my-key")); + + Message got; + BOOST_CHECK(fix.subs.get(got, "queue-1")); + BOOST_CHECK_EQUAL("message1", got.getData()); + BOOST_CHECK(!fix.subs.get(got, "queue-1")); + + BOOST_CHECK(fix.subs.get(got, "queue-2")); + BOOST_CHECK_EQUAL("message2", got.getData()); + BOOST_CHECK(!fix.subs.get(got, "queue-2")); +} + +QPID_AUTO_TEST_CASE(testResubscribeWithLocalQueue) { + ClientSessionFixture fix; + fix.session.queueDeclare(arg::queue="some-queue", arg::exclusive=true, arg::autoDelete=true); + LocalQueue p, q; + fix.subs.subscribe(p, "some-queue"); + fix.subs.cancel("some-queue"); + fix.subs.subscribe(q, "some-queue"); + + fix.session.messageTransfer(arg::content=Message("some-data", "some-queue")); + fix.session.messageFlush(arg::destination="some-queue"); + + Message got; + BOOST_CHECK(!p.get(got)); + + BOOST_CHECK(q.get(got)); + BOOST_CHECK_EQUAL("some-data", got.getData()); + BOOST_CHECK(!q.get(got)); +} + +QPID_AUTO_TEST_CASE(testReliableDispatch) { + ClientSessionFixture fix; + std::string queue("a-queue"); + fix.session.queueDeclare(arg::queue=queue, arg::autoDelete=true); + + ConnectionSettings settings; + settings.port = fix.broker->getPort(qpid::broker::Broker::TCP_TRANSPORT); + + Connection c1; + c1.open(settings); + Session s1 = c1.newSession(); + SubscriptionManager subs1(s1); + LocalQueue q1; + subs1.subscribe(q1, queue, FlowControl());//first subscriber has no credit + + Connection c2; + c2.open(settings); + Session s2 = c2.newSession(); + SubscriptionManager subs2(s2); + LocalQueue q2; + subs2.subscribe(q2, queue);//second subscriber has credit + + fix.session.messageTransfer(arg::content=Message("my-message", queue)); + + //check that the second consumer gets the message + Message got; + BOOST_CHECK(q2.get(got, 1*TIME_SEC)); + BOOST_CHECK_EQUAL("my-message", got.getData()); + + c1.close(); + c2.close(); +} + +QPID_AUTO_TEST_CASE(testSessionCloseOnInvalidSession) { + Session session; + session.close(); +} + +QPID_AUTO_TEST_CASE(testLVQVariedSize) { + ClientSessionFixture fix; + std::string queue("my-lvq"); + QueueOptions args; + args.setOrdering(LVQ_NO_BROWSE); + fix.session.queueDeclare(arg::queue=queue, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args); + + std::string key; + args.getLVQKey(key); + + for (size_t i = 0; i < 10; i++) { + std::ostringstream data; + size_t size = 100 - ((i % 10) * 10); + data << std::string(size, 'x'); + + Message m(data.str(), queue); + m.getHeaders().setString(key, "abc"); + fix.session.messageTransfer(arg::content=m); + } +} + +QPID_AUTO_TEST_CASE(testSessionManagerSetFlowControl) { + ClientSessionFixture fix; + std::string name("dummy"); + LocalQueue queue; + SubscriptionSettings settings; + settings.flowControl = FlowControl(); + fix.session.queueDeclare(arg::queue=name, arg::exclusive=true, arg::autoDelete=true); + fix.subs.subscribe(queue, name, settings); + fix.session.messageTransfer(arg::content=Message("my-message", name)); + fix.subs.setFlowControl(name, 1, FlowControl::UNLIMITED, false); + fix.session.messageFlush(name); + Message got; + BOOST_CHECK(queue.get(got, 0)); + BOOST_CHECK_EQUAL("my-message", got.getData()); +} + +QPID_AUTO_TEST_CASE(testGetThenSubscribe) { + ClientSessionFixture fix; + std::string name("myqueue"); + fix.session.queueDeclare(arg::queue=name, arg::exclusive=true, arg::autoDelete=true); + fix.session.messageTransfer(arg::content=Message("one", name)); + fix.session.messageTransfer(arg::content=Message("two", name)); + Message got; + BOOST_CHECK(fix.subs.get(got, name)); + BOOST_CHECK_EQUAL("one", got.getData()); + + DummyListener listener(fix.session, name, 1); + listener.run(); + BOOST_CHECK_EQUAL(1u, listener.messages.size()); + if (!listener.messages.empty()) { + BOOST_CHECK_EQUAL("two", listener.messages[0].getData()); + } +} + +QPID_AUTO_TEST_CASE(testSessionIsValid) { + ClientSessionFixture fix; + BOOST_CHECK(fix.session.isValid()); + Session session; + BOOST_CHECK(!session.isValid()); +} + +QPID_AUTO_TEST_CASE(testExpirationNotAltered) { + ClientSessionFixture fix; + fix.session.queueDeclare(arg::queue="my-queue", arg::exclusive=true, arg::autoDelete=true); + + Message m("my-message", "my-queue"); + m.getDeliveryProperties().setTtl(60000); + m.getDeliveryProperties().setExpiration(12345); + fix.session.messageTransfer(arg::content=m); + Message got; + BOOST_CHECK(fix.subs.get(got, "my-queue")); + BOOST_CHECK_EQUAL("my-message", got.getData()); + BOOST_CHECK_EQUAL(12345u, got.getDeliveryProperties().getExpiration()); +} + +QPID_AUTO_TEST_CASE(testGetConnectionFromSession) { + ClientSessionFixture fix; + FieldTable options; + options.setInt("no-local", 1); + fix.session.queueDeclare(arg::queue="a", arg::exclusive=true, arg::autoDelete=true, arg::arguments=options); + fix.session.queueDeclare(arg::queue="b", arg::exclusive=true, arg::autoDelete=true); + + Connection c = fix.session.getConnection(); + Session s = c.newSession(); + //If this new session was created as expected on the same connection as + //fix.session, then the no-local behaviour means that queue 'a' + //will not enqueue messages from this new session but queue 'b' + //will. + s.messageTransfer(arg::content=Message("a", "a")); + s.messageTransfer(arg::content=Message("b", "b")); + + Message got; + BOOST_CHECK(fix.subs.get(got, "b")); + BOOST_CHECK_EQUAL("b", got.getData()); + BOOST_CHECK(!fix.subs.get(got, "a")); +} + + +QPID_AUTO_TEST_CASE(testQueueDeleted) +{ + ClientSessionFixture fix; + fix.session.queueDeclare(arg::queue="my-queue"); + LocalQueue queue; + fix.subs.subscribe(queue, "my-queue"); + + ScopedSuppressLogging sl; + fix.session.queueDelete(arg::queue="my-queue"); + BOOST_CHECK_THROW(queue.get(1*qpid::sys::TIME_SEC), qpid::framing::ResourceDeletedException); +} + +QPID_AUTO_TEST_CASE(testTtl) +{ + const uint64_t ms = 1000ULL; // convert sec to ms + const uint64_t us = 1000ULL * 1000ULL; // convert sec to us + + ClientSessionFixture fix; + fix.session.queueDeclare(arg::queue="ttl-test", arg::exclusive=true, arg::autoDelete=true); + Message msg1 = Message("AAA", "ttl-test"); + uint64_t ttl = 2 * ms; // 2 sec + msg1.getDeliveryProperties().setTtl(ttl); + Connection c = fix.session.getConnection(); + Session s = c.newSession(); + s.messageTransfer(arg::content=msg1); + + Message msg2 = Message("BBB", "ttl-test"); + ttl = 10 * ms; // 10 sec + msg2.getDeliveryProperties().setTtl(ttl); + s.messageTransfer(arg::content=msg2); + + qpid::sys::usleep(5 * us); // 5 sec + + // Message "AAA" should be expired and never be delivered + // Check "BBB" has ttl somewhere between 1 and 5 secs + Message got; + BOOST_CHECK(fix.subs.get(got, "ttl-test")); + BOOST_CHECK_EQUAL("BBB", got.getData()); + BOOST_CHECK(got.getDeliveryProperties().getTtl() > 1 * ms); + BOOST_CHECK(got.getDeliveryProperties().getTtl() < ttl - (5 * ms)); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/ConnectionOptions.h b/qpid/cpp/src/tests/ConnectionOptions.h new file mode 100644 index 0000000000..fe945e9ddd --- /dev/null +++ b/qpid/cpp/src/tests/ConnectionOptions.h @@ -0,0 +1,62 @@ +#ifndef QPID_CLIENT_CONNECTIONOPTIONS_H +#define QPID_CLIENT_CONNECTIONOPTIONS_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/client/ConnectionSettings.h" +#include "qpid/Options.h" + +namespace qpid { + +/** + * Options parser for ConnectionOptions. + */ +struct ConnectionOptions : public qpid::Options, + public qpid::client::ConnectionSettings +{ + ConnectionOptions() : qpid::Options("Connection Settings") + { + using namespace qpid; + addOptions() + ("broker,b", optValue(host, "HOST"), "Broker host to connect to") + ("port,p", optValue(port, "PORT"), "Broker port to connect to") + ("protocol,P", optValue(protocol, "tcp|ssl|rdma"), "Protocol to use for broker connection") + ("virtualhost,v", optValue(virtualhost, "VHOST"), "virtual host") + ("username", optValue(username, "USER"), "user name for broker log in.") + ("password", optValue(password, "PASSWORD"), "password for broker log in.") + ("mechanism", optValue(mechanism, "MECH"), "SASL mechanism to use when authenticating.") + ("locale", optValue(locale, "LOCALE"), "locale to use.") + ("max-channels", optValue(maxChannels, "N"), "the maximum number of channels the client requires.") + ("heartbeat", optValue(heartbeat, "N"), "Desired heartbeat interval in seconds.") + ("max-frame-size", optValue(maxFrameSize, "N"), "the maximum frame size to request.") + ("bounds-multiplier", optValue(bounds, "N"), + "bound size of write queue (as a multiple of the max frame size).") + ("tcp-nodelay", optValue(tcpNoDelay), "Turn on tcp-nodelay") + ("service", optValue(service, "SERVICE-NAME"), "SASL service name.") + ("min-ssf", optValue(minSsf, "N"), "Minimum acceptable strength for SASL security layer") + ("max-ssf", optValue(maxSsf, "N"), "Maximum acceptable strength for SASL security layer"); + } +}; + +} // namespace qpid + +#endif /*!QPID_CLIENT_CONNECTIONOPTIONS_H*/ diff --git a/qpid/cpp/src/tests/DeliveryRecordTest.cpp b/qpid/cpp/src/tests/DeliveryRecordTest.cpp new file mode 100644 index 0000000000..37b3095f81 --- /dev/null +++ b/qpid/cpp/src/tests/DeliveryRecordTest.cpp @@ -0,0 +1,67 @@ + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/broker/DeliveryRecord.h" +#include "qpid/broker/Queue.h" +#include "unit_test.h" +#include <iostream> +#include <memory> +#include <boost/format.hpp> + +using namespace qpid::broker; +using namespace qpid::sys; +using namespace qpid::framing; +using boost::dynamic_pointer_cast; +using std::list; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(DeliveryRecordTestSuite) + +QPID_AUTO_TEST_CASE(testSort) +{ + list<SequenceNumber> ids; + ids.push_back(SequenceNumber(6)); + ids.push_back(SequenceNumber(2)); + ids.push_back(SequenceNumber(4)); + ids.push_back(SequenceNumber(5)); + ids.push_back(SequenceNumber(1)); + ids.push_back(SequenceNumber(3)); + + list<DeliveryRecord> records; + for (list<SequenceNumber>::iterator i = ids.begin(); i != ids.end(); i++) { + DeliveryRecord r(QueueCursor(CONSUMER), framing::SequenceNumber(), SequenceNumber(), Queue::shared_ptr(), "tag", Consumer::shared_ptr(), false, false, false); + r.setId(*i); + records.push_back(r); + } + records.sort(); + + SequenceNumber expected(0); + for (list<DeliveryRecord>::iterator i = records.begin(); i != records.end(); i++) { + BOOST_CHECK(i->getId() == ++expected); + } +} + + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/DispatcherTest.cpp b/qpid/cpp/src/tests/DispatcherTest.cpp new file mode 100644 index 0000000000..7312fe8d2e --- /dev/null +++ b/qpid/cpp/src/tests/DispatcherTest.cpp @@ -0,0 +1,240 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/sys/Poller.h" +#include "qpid/sys/Dispatcher.h" +#include "qpid/sys/DispatchHandle.h" +#include "qpid/sys/posix/PrivatePosix.h" +#include "qpid/sys/Thread.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <signal.h> + +#include <iostream> +#include <boost/bind.hpp> + +using namespace std; +using namespace qpid::sys; + +namespace qpid { +namespace tests { + +int writeALot(int fd, const string& s) { + int bytesWritten = 0; + do { + errno = 0; + int lastWrite = ::write(fd, s.c_str(), s.size()); + if ( lastWrite >= 0) { + bytesWritten += lastWrite; + } + } while (errno != EAGAIN); + return bytesWritten; +} + +int readALot(int fd) { + int bytesRead = 0; + char buf[10240]; + + do { + errno = 0; + int lastRead = ::read(fd, buf, sizeof(buf)); + if ( lastRead >= 0) { + bytesRead += lastRead; + } + } while (errno != EAGAIN); + return bytesRead; +} + +int64_t writtenBytes = 0; +int64_t readBytes = 0; + +void writer(DispatchHandle& h, int fd, const string& s) { + writtenBytes += writeALot(fd, s); + h.rewatch(); +} + +void reader(DispatchHandle& h, int fd) { + readBytes += readALot(fd); + h.rewatch(); +} + +void rInterrupt(DispatchHandle&) { + cerr << "R"; +} + +void wInterrupt(DispatchHandle&) { + cerr << "W"; +} + +DispatchHandle::Callback rcb = rInterrupt; +DispatchHandle::Callback wcb = wInterrupt; + +DispatchHandleRef *volatile rh = 0; +DispatchHandleRef *volatile wh = 0; + +volatile bool stopWait = false; +volatile bool phase1finished = false; + +timer_t timer; + +void stop_handler(int /*signo*/, siginfo_t* /*info*/, void* /*context*/) { + stopWait = true; +} + +void timer_handler(int /*signo*/, siginfo_t* /*info*/, void* /*context*/) { + static int count = 0; + if (count++ < 10) { + rh->call(rcb); + wh->call(wcb); + } else { + phase1finished = true; + assert(::timer_delete(timer) == 0); + } +} + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int /*argc*/, char** /*argv*/) +{ + // Create poller + Poller::shared_ptr poller(new Poller); + + // Create dispatcher thread + Thread dt(*poller); + Thread dt1(*poller); + Thread dt2(*poller); + Thread dt3(*poller); + + // Setup sender and receiver + int sv[2]; + int rc = ::socketpair(AF_UNIX, SOCK_STREAM, 0, sv); + assert(rc >= 0); + + // Set non-blocking + rc = ::fcntl(sv[0], F_SETFL, O_NONBLOCK); + assert(rc >= 0); + + rc = ::fcntl(sv[1], F_SETFL, O_NONBLOCK); + assert(rc >= 0); + + // Make up a large string + string testString = "This is only a test ... 1,2,3,4,5,6,7,8,9,10;"; + for (int i = 0; i < 8; i++) + testString += testString; + + IOHandle f0(sv[0]); + IOHandle f1(sv[1]); + + rh = new DispatchHandleRef(f0, boost::bind(reader, _1, sv[0]), 0, 0); + wh = new DispatchHandleRef(f1, 0, boost::bind(writer, _1, sv[1], testString), 0); + + rh->startWatch(poller); + wh->startWatch(poller); + + // Set up a regular itimer interupt + // We assume that this thread will handle the signals whilst sleeping + // as the Poller threads have signal handling blocked + + // Signal handling + struct ::sigaction sa; + sa.sa_sigaction = timer_handler; + sa.sa_flags = SA_RESTART | SA_SIGINFO; + ::sigemptyset(&sa.sa_mask); + rc = ::sigaction(SIGRTMIN, &sa,0); + assert(rc == 0); + + ::sigevent se; + ::memset(&se, 0, sizeof(se)); // Clear to make valgrind happy (this *is* the neatest way to do this portably - sigh) + se.sigev_notify = SIGEV_SIGNAL; + se.sigev_signo = SIGRTMIN; + rc = ::timer_create(CLOCK_REALTIME, &se, &timer); + assert(rc == 0); + + itimerspec ts = { + /*.it_value = */ {2, 0}, // s, ns + /*.it_interval = */ {2, 0}}; // s, ns + + rc = ::timer_settime(timer, 0, &ts, 0); + assert(rc == 0); + + // wait + while (!phase1finished) { + ::sleep(1); + } + + // Now test deleting/creating DispatchHandles in tight loop, so that we are likely to still be using the + // attached PollerHandles after deleting the DispatchHandle + DispatchHandleRef* t = wh; + wh = 0; + delete t; + t = rh; + rh = 0; + delete t; + + sa.sa_sigaction = stop_handler; + rc = ::sigaction(SIGRTMIN, &sa,0); + assert(rc == 0); + + itimerspec nts = { + /*.it_value = */ {30, 0}, // s, ns + /*.it_interval = */ {30, 0}}; // s, ns + + rc = ::timer_create(CLOCK_REALTIME, &se, &timer); + assert(rc == 0); + rc = ::timer_settime(timer, 0, &nts, 0); + assert(rc == 0); + + DispatchHandleRef* rh1; + DispatchHandleRef* wh1; + + struct timespec w = {0, 1000000}; + while (!stopWait) { + rh1 = new DispatchHandleRef(f0, boost::bind(reader, _1, sv[0]), 0, 0); + wh1 = new DispatchHandleRef(f1, 0, boost::bind(writer, _1, sv[1], testString), 0); + rh1->startWatch(poller); + wh1->startWatch(poller); + + ::nanosleep(&w, 0); + + delete wh1; + delete rh1; + } + + rc = ::timer_delete(timer); + assert(rc == 0); + + poller->shutdown(); + dt.join(); + dt1.join(); + dt2.join(); + dt3.join(); + + cout << "\nWrote: " << writtenBytes << "\n"; + cout << "Read: " << readBytes << "\n"; + + return 0; +} diff --git a/qpid/cpp/src/tests/DtxWorkRecordTest.cpp b/qpid/cpp/src/tests/DtxWorkRecordTest.cpp new file mode 100644 index 0000000000..bcb3fc14a1 --- /dev/null +++ b/qpid/cpp/src/tests/DtxWorkRecordTest.cpp @@ -0,0 +1,193 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/broker/DtxWorkRecord.h" +#include "unit_test.h" +#include <iostream> +#include <vector> +#include "TxMocks.h" + +using namespace qpid::broker; +using boost::static_pointer_cast; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(DtxWorkRecordTestSuite) + +QPID_AUTO_TEST_CASE(testOnePhaseCommit){ + MockTransactionalStore store; + store.expectBegin().expectCommit(); + + MockTxOp::shared_ptr opA(new MockTxOp()); + opA->expectPrepare().expectCommit(); + MockTxOp::shared_ptr opB(new MockTxOp()); + opB->expectPrepare().expectCommit(); + + boost::intrusive_ptr<DtxBuffer> bufferA(new DtxBuffer()); + bufferA->enlist(static_pointer_cast<TxOp>(opA)); + bufferA->markEnded(); + boost::intrusive_ptr<DtxBuffer> bufferB(new DtxBuffer()); + bufferB->enlist(static_pointer_cast<TxOp>(opB)); + bufferB->markEnded(); + + DtxWorkRecord work("my-xid", &store); + work.add(bufferA); + work.add(bufferB); + + work.commit(true); + + store.check(); + BOOST_CHECK(store.isCommitted()); + opA->check(); + opB->check(); +} + +QPID_AUTO_TEST_CASE(testFailOnOnePhaseCommit){ + MockTransactionalStore store; + store.expectBegin().expectAbort(); + + MockTxOp::shared_ptr opA(new MockTxOp()); + opA->expectPrepare().expectRollback(); + MockTxOp::shared_ptr opB(new MockTxOp(true)); + opB->expectPrepare().expectRollback(); + MockTxOp::shared_ptr opC(new MockTxOp()); + opC->expectRollback(); + + boost::intrusive_ptr<DtxBuffer> bufferA(new DtxBuffer()); + bufferA->enlist(static_pointer_cast<TxOp>(opA)); + bufferA->markEnded(); + boost::intrusive_ptr<DtxBuffer> bufferB(new DtxBuffer()); + bufferB->enlist(static_pointer_cast<TxOp>(opB)); + bufferB->markEnded(); + boost::intrusive_ptr<DtxBuffer> bufferC(new DtxBuffer()); + bufferC->enlist(static_pointer_cast<TxOp>(opC)); + bufferC->markEnded(); + + DtxWorkRecord work("my-xid", &store); + work.add(bufferA); + work.add(bufferB); + work.add(bufferC); + + work.commit(true); + + BOOST_CHECK(store.isAborted()); + store.check(); + + opA->check(); + opB->check(); + opC->check(); +} + +QPID_AUTO_TEST_CASE(testTwoPhaseCommit){ + MockTransactionalStore store; + store.expectBegin2PC().expectPrepare().expectCommit(); + + MockTxOp::shared_ptr opA(new MockTxOp()); + opA->expectPrepare().expectCommit(); + MockTxOp::shared_ptr opB(new MockTxOp()); + opB->expectPrepare().expectCommit(); + + boost::intrusive_ptr<DtxBuffer> bufferA(new DtxBuffer()); + bufferA->enlist(static_pointer_cast<TxOp>(opA)); + bufferA->markEnded(); + boost::intrusive_ptr<DtxBuffer> bufferB(new DtxBuffer()); + bufferB->enlist(static_pointer_cast<TxOp>(opB)); + bufferB->markEnded(); + + DtxWorkRecord work("my-xid", &store); + work.add(bufferA); + work.add(bufferB); + + BOOST_CHECK(work.prepare()); + BOOST_CHECK(store.isPrepared()); + work.commit(false); + store.check(); + BOOST_CHECK(store.isCommitted()); + opA->check(); + opB->check(); +} + +QPID_AUTO_TEST_CASE(testFailOnTwoPhaseCommit){ + MockTransactionalStore store; + store.expectBegin2PC().expectAbort(); + + MockTxOp::shared_ptr opA(new MockTxOp()); + opA->expectPrepare().expectRollback(); + MockTxOp::shared_ptr opB(new MockTxOp(true)); + opB->expectPrepare().expectRollback(); + MockTxOp::shared_ptr opC(new MockTxOp()); + opC->expectRollback(); + + boost::intrusive_ptr<DtxBuffer> bufferA(new DtxBuffer()); + bufferA->enlist(static_pointer_cast<TxOp>(opA)); + bufferA->markEnded(); + boost::intrusive_ptr<DtxBuffer> bufferB(new DtxBuffer()); + bufferB->enlist(static_pointer_cast<TxOp>(opB)); + bufferB->markEnded(); + boost::intrusive_ptr<DtxBuffer> bufferC(new DtxBuffer()); + bufferC->enlist(static_pointer_cast<TxOp>(opC)); + bufferC->markEnded(); + + DtxWorkRecord work("my-xid", &store); + work.add(bufferA); + work.add(bufferB); + work.add(bufferC); + + BOOST_CHECK(!work.prepare()); + BOOST_CHECK(store.isAborted()); + store.check(); + opA->check(); + opB->check(); + opC->check(); +} + +QPID_AUTO_TEST_CASE(testRollback){ + MockTransactionalStore store; + store.expectBegin2PC().expectPrepare().expectAbort(); + + MockTxOp::shared_ptr opA(new MockTxOp()); + opA->expectPrepare().expectRollback(); + MockTxOp::shared_ptr opB(new MockTxOp()); + opB->expectPrepare().expectRollback(); + + boost::intrusive_ptr<DtxBuffer> bufferA(new DtxBuffer()); + bufferA->enlist(static_pointer_cast<TxOp>(opA)); + bufferA->markEnded(); + boost::intrusive_ptr<DtxBuffer> bufferB(new DtxBuffer()); + bufferB->enlist(static_pointer_cast<TxOp>(opB)); + bufferB->markEnded(); + + DtxWorkRecord work("my-xid", &store); + work.add(bufferA); + work.add(bufferB); + + BOOST_CHECK(work.prepare()); + BOOST_CHECK(store.isPrepared()); + work.rollback(); + store.check(); + BOOST_CHECK(store.isAborted()); + opA->check(); + opB->check(); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/ExchangeTest.cpp b/qpid/cpp/src/tests/ExchangeTest.cpp new file mode 100644 index 0000000000..df0684e832 --- /dev/null +++ b/qpid/cpp/src/tests/ExchangeTest.cpp @@ -0,0 +1,267 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/Exception.h" +#include "qpid/broker/Exchange.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/DeliverableMessage.h" +#include "qpid/broker/DirectExchange.h" +#include "qpid/broker/ExchangeRegistry.h" +#include "qpid/broker/FanOutExchange.h" +#include "qpid/broker/HeadersExchange.h" +#include "qpid/broker/TopicExchange.h" +#include "qpid/framing/reply_exceptions.h" +#include "unit_test.h" +#include <iostream> +#include "MessageUtils.h" + +using std::string; + +using namespace qpid::broker; +using namespace qpid::framing; +using namespace qpid::sys; +using namespace qpid; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(ExchangeTestSuite) + +QPID_AUTO_TEST_CASE(testMe) +{ + Queue::shared_ptr queue(new Queue("queue", true)); + Queue::shared_ptr queue2(new Queue("queue2", true)); + + TopicExchange topic("topic"); + topic.bind(queue, "abc", 0); + topic.bind(queue2, "abc", 0); + + DirectExchange direct("direct"); + direct.bind(queue, "abc", 0); + direct.bind(queue2, "abc", 0); + + queue.reset(); + queue2.reset(); + + DeliverableMessage msg(MessageUtils::createMessage("exchange", "abc"), 0); + topic.route(msg); + direct.route(msg); +} + +QPID_AUTO_TEST_CASE(testIsBound) +{ + Queue::shared_ptr a(new Queue("a", true)); + Queue::shared_ptr b(new Queue("b", true)); + Queue::shared_ptr c(new Queue("c", true)); + Queue::shared_ptr d(new Queue("d", true)); + + string k1("abc"); + string k2("def"); + string k3("xyz"); + + FanOutExchange fanout("fanout"); + BOOST_CHECK(fanout.bind(a, "", 0)); + BOOST_CHECK(fanout.bind(b, "", 0)); + BOOST_CHECK(fanout.bind(c, "", 0)); + + BOOST_CHECK(fanout.isBound(a, 0, 0)); + BOOST_CHECK(fanout.isBound(b, 0, 0)); + BOOST_CHECK(fanout.isBound(c, 0, 0)); + BOOST_CHECK(!fanout.isBound(d, 0, 0)); + + DirectExchange direct("direct"); + BOOST_CHECK(direct.bind(a, k1, 0)); + BOOST_CHECK(direct.bind(a, k3, 0)); + BOOST_CHECK(direct.bind(b, k2, 0)); + BOOST_CHECK(direct.bind(c, k1, 0)); + + BOOST_CHECK(direct.isBound(a, 0, 0)); + BOOST_CHECK(direct.isBound(a, &k1, 0)); + BOOST_CHECK(direct.isBound(a, &k3, 0)); + BOOST_CHECK(!direct.isBound(a, &k2, 0)); + BOOST_CHECK(direct.isBound(b, 0, 0)); + BOOST_CHECK(direct.isBound(b, &k2, 0)); + BOOST_CHECK(direct.isBound(c, &k1, 0)); + BOOST_CHECK(!direct.isBound(d, 0, 0)); + BOOST_CHECK(!direct.isBound(d, &k1, 0)); + BOOST_CHECK(!direct.isBound(d, &k2, 0)); + BOOST_CHECK(!direct.isBound(d, &k3, 0)); + + TopicExchange topic("topic"); + BOOST_CHECK(topic.bind(a, k1, 0)); + BOOST_CHECK(topic.bind(a, k3, 0)); + BOOST_CHECK(topic.bind(b, k2, 0)); + BOOST_CHECK(topic.bind(c, k1, 0)); + + BOOST_CHECK(topic.isBound(a, 0, 0)); + BOOST_CHECK(topic.isBound(a, &k1, 0)); + BOOST_CHECK(topic.isBound(a, &k3, 0)); + BOOST_CHECK(!topic.isBound(a, &k2, 0)); + BOOST_CHECK(topic.isBound(b, 0, 0)); + BOOST_CHECK(topic.isBound(b, &k2, 0)); + BOOST_CHECK(topic.isBound(c, &k1, 0)); + BOOST_CHECK(!topic.isBound(d, 0, 0)); + BOOST_CHECK(!topic.isBound(d, &k1, 0)); + BOOST_CHECK(!topic.isBound(d, &k2, 0)); + BOOST_CHECK(!topic.isBound(d, &k3, 0)); + + HeadersExchange headers("headers"); + FieldTable args1; + args1.setString("x-match", "all"); + args1.setString("a", "A"); + args1.setInt("b", 1); + FieldTable args2; + args2.setString("x-match", "any"); + args2.setString("a", "A"); + args2.setInt("b", 1); + FieldTable args3; + args3.setString("x-match", "any"); + args3.setString("c", "C"); + args3.setInt("b", 6); + + headers.bind(a, "", &args1); + headers.bind(a, "other", &args3);//need to use different binding key to correctly identify second binding + headers.bind(b, "", &args2); + headers.bind(c, "", &args1); + + BOOST_CHECK(headers.isBound(a, 0, 0)); + BOOST_CHECK(headers.isBound(a, 0, &args1)); + BOOST_CHECK(headers.isBound(a, 0, &args3)); + BOOST_CHECK(!headers.isBound(a, 0, &args2)); + BOOST_CHECK(headers.isBound(b, 0, 0)); + BOOST_CHECK(headers.isBound(b, 0, &args2)); + BOOST_CHECK(headers.isBound(c, 0, &args1)); + BOOST_CHECK(!headers.isBound(d, 0, 0)); + BOOST_CHECK(!headers.isBound(d, 0, &args1)); + BOOST_CHECK(!headers.isBound(d, 0, &args2)); + BOOST_CHECK(!headers.isBound(d, 0, &args3)); +} + +QPID_AUTO_TEST_CASE(testDeleteGetAndRedeclare) +{ + ExchangeRegistry exchanges; + exchanges.declare("my-exchange", "direct", false, false, FieldTable()); + exchanges.destroy("my-exchange"); + try { + exchanges.get("my-exchange"); + } catch (const NotFoundException&) {} + std::pair<Exchange::shared_ptr, bool> response = exchanges.declare("my-exchange", "direct", false, false, FieldTable()); + BOOST_CHECK_EQUAL(string("direct"), response.first->getType()); +} + +QPID_AUTO_TEST_CASE(testSequenceOptions) +{ + FieldTable args; + args.setInt("qpid.msg_sequence",1); + char* buff = new char[10000]; + framing::Buffer buffer(buff,10000); + { + DirectExchange direct("direct1", false, false, args); + + DeliverableMessage msg1(MessageUtils::createMessage("e", "abc"), 0); + DeliverableMessage msg2(MessageUtils::createMessage("e", "abc"), 0); + DeliverableMessage msg3(MessageUtils::createMessage("e", "abc"), 0); + + direct.route(msg1); + direct.route(msg2); + direct.route(msg3); + + BOOST_CHECK_EQUAL(1, msg1.getMessage().getAnnotation("qpid.msg_sequence").asInt64()); + BOOST_CHECK_EQUAL(2, msg2.getMessage().getAnnotation("qpid.msg_sequence").asInt64()); + BOOST_CHECK_EQUAL(3, msg3.getMessage().getAnnotation("qpid.msg_sequence").asInt64()); + + FanOutExchange fanout("fanout1", false, false, args); + HeadersExchange header("headers1", false, false, args); + TopicExchange topic ("topic1", false, false, args); + + // check other exchanges, that they preroute + DeliverableMessage msg4(MessageUtils::createMessage("e", "abc"), 0); + DeliverableMessage msg5(MessageUtils::createMessage("e", "abc"), 0); + DeliverableMessage msg6(MessageUtils::createMessage("e", "abc"), 0); + + fanout.route(msg4); + BOOST_CHECK_EQUAL(1, msg4.getMessage().getAnnotation("qpid.msg_sequence").asInt64()); + + header.route(msg5); + BOOST_CHECK_EQUAL(1, msg5.getMessage().getAnnotation("qpid.msg_sequence").asInt64()); + + topic.route(msg6); + BOOST_CHECK_EQUAL(1, msg6.getMessage().getAnnotation("qpid.msg_sequence").asInt64()); + direct.encode(buffer); + } + { + + ExchangeRegistry exchanges; + buffer.reset(); + DirectExchange::shared_ptr exch_dec = Exchange::decode(exchanges, buffer); + + DeliverableMessage msg1(MessageUtils::createMessage("e", "abc"), 0); + exch_dec->route(msg1); + + BOOST_CHECK_EQUAL(4, msg1.getMessage().getAnnotation("qpid.msg_sequence").asInt64()); + + } + delete [] buff; +} + +QPID_AUTO_TEST_CASE(testIVEOption) +{ + FieldTable args; + args.setInt("qpid.ive",1); + DirectExchange direct("direct1", false, false, args); + FanOutExchange fanout("fanout1", false, false, args); + HeadersExchange header("headers1", false, false, args); + TopicExchange topic ("topic1", false, false, args); + + qpid::types::Variant::Map properties; + properties["routing-key"] = "abc"; + properties["a"] = "abc"; + Message msg1 = MessageUtils::createMessage(properties, "my-message", "direct1"); + DeliverableMessage dmsg1(msg1, 0); + + FieldTable args2; + args2.setString("x-match", "any"); + args2.setString("a", "abc"); + + direct.route(dmsg1); + fanout.route(dmsg1); + header.route(dmsg1); + topic.route(dmsg1); + Queue::shared_ptr queue(new Queue("queue", true)); + Queue::shared_ptr queue1(new Queue("queue1", true)); + Queue::shared_ptr queue2(new Queue("queue2", true)); + Queue::shared_ptr queue3(new Queue("queue3", true)); + + BOOST_CHECK(direct.bind(queue, "abc", 0)); + BOOST_CHECK(fanout.bind(queue1, "abc", 0)); + BOOST_CHECK(header.bind(queue2, "", &args2)); + BOOST_CHECK(topic.bind(queue3, "abc", 0)); + + BOOST_CHECK_EQUAL(1u,queue->getMessageCount()); + BOOST_CHECK_EQUAL(1u,queue1->getMessageCount()); + BOOST_CHECK_EQUAL(1u,queue2->getMessageCount()); + BOOST_CHECK_EQUAL(1u,queue3->getMessageCount()); + +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/FieldTable.cpp b/qpid/cpp/src/tests/FieldTable.cpp new file mode 100644 index 0000000000..c040f1d433 --- /dev/null +++ b/qpid/cpp/src/tests/FieldTable.cpp @@ -0,0 +1,215 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include <iostream> +#include <algorithm> +#include "qpid/framing/Array.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/framing/FieldValue.h" +#include "qpid/framing/List.h" + +#include "unit_test.h" + +using namespace qpid::framing; + +using std::string; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(FieldTableTestSuite) + +QPID_AUTO_TEST_CASE(testMe) +{ + FieldTable ft; + ft.setString("A", "BCDE"); + BOOST_CHECK(string("BCDE") == ft.getAsString("A")); + + char buff[100]; + Buffer wbuffer(buff, 100); + wbuffer.put(ft); + + Buffer rbuffer(buff, 100); + FieldTable ft2; + rbuffer.get(ft2); + BOOST_CHECK(string("BCDE") == ft2.getAsString("A")); + +} + +QPID_AUTO_TEST_CASE(testAssignment) +{ + FieldTable a; + FieldTable b; + + a.setString("A", "BBBB"); + a.setInt("B", 1234); + b = a; + a.setString("A", "CCCC"); + + BOOST_CHECK(string("CCCC") == a.getAsString("A")); + BOOST_CHECK(string("BBBB") == b.getAsString("A")); + BOOST_CHECK_EQUAL(1234, a.getAsInt("B")); + BOOST_CHECK_EQUAL(1234, b.getAsInt("B")); + BOOST_CHECK(IntegerValue(1234) == *a.get("B")); + BOOST_CHECK(IntegerValue(1234) == *b.get("B")); + + FieldTable d; + { + FieldTable c; + c = a; + + std::vector<char> buff(c.encodedSize()); + Buffer wbuffer(&buff[0], c.encodedSize()); + wbuffer.put(c); + + Buffer rbuffer(&buff[0], c.encodedSize()); + rbuffer.get(d); + BOOST_CHECK_EQUAL(c, d); + BOOST_CHECK(string("CCCC") == c.getAsString("A")); + BOOST_CHECK(IntegerValue(1234) == *c.get("B")); + } + BOOST_CHECK(string("CCCC") == d.getAsString("A")); + BOOST_CHECK(IntegerValue(1234) == *d.get("B")); +} + + +QPID_AUTO_TEST_CASE(testNestedValues) +{ + double d = 1.2345; + uint32_t u = 101; + char buff[1000]; + { + FieldTable a; + FieldTable b; + std::vector<std::string> items; + items.push_back("one"); + items.push_back("two"); + Array c(items); + List list; + list.push_back(List::ValuePtr(new Str16Value("red"))); + list.push_back(List::ValuePtr(new Unsigned32Value(u))); + list.push_back(List::ValuePtr(new Str8Value("yellow"))); + list.push_back(List::ValuePtr(new DoubleValue(d))); + + a.setString("id", "A"); + b.setString("id", "B"); + a.setTable("B", b); + a.setArray("C", c); + a.set("my-list", FieldTable::ValuePtr(new ListValue(list))); + + + Buffer wbuffer(buff, 100); + wbuffer.put(a); + } + { + Buffer rbuffer(buff, 100); + FieldTable a; + FieldTable b; + Array c; + rbuffer.get(a); + BOOST_CHECK(string("A") == a.getAsString("id")); + a.getTable("B", b); + BOOST_CHECK(string("B") == b.getAsString("id")); + a.getArray("C", c); + std::vector<std::string> items; + std::transform(c.begin(), c.end(), std::back_inserter(items), Array::get<std::string, Array::ValuePtr>); + BOOST_CHECK((uint) 2 == items.size()); + BOOST_CHECK(string("one") == items[0]); + BOOST_CHECK(string("two") == items[1]); + + List list; + BOOST_CHECK(a.get("my-list")->get<List>(list)); + List::const_iterator i = list.begin(); + BOOST_CHECK(i != list.end()); + BOOST_CHECK_EQUAL(std::string("red"), (*i)->get<std::string>()); + + i++; + BOOST_CHECK(i != list.end()); + BOOST_CHECK_EQUAL(u, (uint32_t) (*i)->get<int>()); + + i++; + BOOST_CHECK(i != list.end()); + BOOST_CHECK_EQUAL(std::string("yellow"), (*i)->get<std::string>()); + + i++; + BOOST_CHECK(i != list.end()); + BOOST_CHECK_EQUAL(d, (*i)->get<double>()); + + i++; + BOOST_CHECK(i == list.end()); + } +} + +QPID_AUTO_TEST_CASE(testFloatAndDouble) +{ + char buff[100]; + float f = 5.672f; + double d = 56.720001; + { + FieldTable a; + a.setString("string", "abc"); + a.setInt("int", 5672); + a.setFloat("float", f); + a.setDouble("double", d); + + Buffer wbuffer(buff, 100); + wbuffer.put(a); + } + { + Buffer rbuffer(buff, 100); + FieldTable a; + rbuffer.get(a); + BOOST_CHECK_EQUAL(string("abc"), a.getAsString("string")); + BOOST_CHECK_EQUAL(5672, a.getAsInt("int")); + float f2; + BOOST_CHECK(!a.getFloat("string", f2)); + BOOST_CHECK(!a.getFloat("int", f2)); + BOOST_CHECK(a.getFloat("float", f2)); + BOOST_CHECK_EQUAL(f2, f); + + double d2; + BOOST_CHECK(!a.getDouble("string", d2)); + BOOST_CHECK(!a.getDouble("int", d2)); + BOOST_CHECK(a.getDouble("double", d2)); + BOOST_CHECK_EQUAL(d2, d); + } +} + +QPID_AUTO_TEST_CASE(test64GetAndSetConverts) +{ + FieldTable args; + args.setInt64("a",100); + args.setInt64("b",-(int64_t) ((int64_t) 1<<34)); + + args.setUInt64("c",1u); + args.setUInt64("d",(uint64_t) ((uint64_t) 1<<34)); + BOOST_CHECK_EQUAL(1u, args.getAsUInt64("c")); + BOOST_CHECK_EQUAL(100u, args.getAsUInt64("a")); + BOOST_CHECK_EQUAL(1, args.getAsInt64("c")); + BOOST_CHECK_EQUAL(100, args.getAsInt64("a")); + BOOST_CHECK_EQUAL(-(int64_t) ((int64_t) 1<<34), args.getAsInt64("b")); + BOOST_CHECK_EQUAL((uint64_t) ((uint64_t) 1<<34), args.getAsUInt64("d")); + BOOST_CHECK_EQUAL((int64_t) ((int64_t) 1<<34), args.getAsInt64("d")); + +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/FieldValue.cpp b/qpid/cpp/src/tests/FieldValue.cpp new file mode 100644 index 0000000000..9cea48e3cf --- /dev/null +++ b/qpid/cpp/src/tests/FieldValue.cpp @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#include "qpid/framing/FieldValue.h" + +#include "unit_test.h" +#include <boost/test/floating_point_comparison.hpp> + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(FieldValueTestSuite) + +using namespace qpid::framing; + +Str16Value s("abc"); +IntegerValue i(42); +FloatValue f((float)42.42); +DoubleValue df(123.123); + +QPID_AUTO_TEST_CASE(testStr16ValueEquals) +{ + + BOOST_CHECK(Str16Value("abc") == s); + BOOST_CHECK(Str16Value("foo") != s); + BOOST_CHECK(s != i); + BOOST_CHECK(s.convertsTo<std::string>() == true); + BOOST_CHECK(s.convertsTo<int>() == false); + BOOST_CHECK(s.get<std::string>() == "abc"); + BOOST_CHECK_THROW(s.get<int>(), InvalidConversionException); + +} + +QPID_AUTO_TEST_CASE(testIntegerValueEquals) +{ + BOOST_CHECK(i.get<int>() == 42); + BOOST_CHECK(IntegerValue(42) == i); + BOOST_CHECK(IntegerValue(5) != i); + BOOST_CHECK(i != s); + BOOST_CHECK(i.convertsTo<std::string>() == false); + BOOST_CHECK(i.convertsTo<float>() == true); + BOOST_CHECK(i.convertsTo<int>() == true); + BOOST_CHECK_THROW(i.get<std::string>(), InvalidConversionException); + BOOST_CHECK_EQUAL(i.get<float>(), 42.0); +} + +QPID_AUTO_TEST_CASE(testFloatValueEquals) +{ + BOOST_CHECK(f.convertsTo<float>() == true); + BOOST_CHECK(FloatValue((float)42.42) == f); + BOOST_CHECK_CLOSE(double(f.get<float>()), 42.42, 0.001); + // Check twice, regression test for QPID-6470 where the value was corrupted during get. + BOOST_CHECK(FloatValue((float)42.42) == f); + BOOST_CHECK_CLOSE(f.get<double>(), 42.42, 0.001); + + // Float to double conversion + BOOST_CHECK(f.convertsTo<double>() == true); + BOOST_CHECK_CLOSE(f.get<double>(), 42.42, 0.001); + + // Double value + BOOST_CHECK(f.convertsTo<float>() == true); + BOOST_CHECK(f.convertsTo<double>() == true); + BOOST_CHECK_CLOSE(double(df.get<float>()), 123.123, 0.001); + BOOST_CHECK_CLOSE(df.get<double>(), 123.123, 0.001); + + // Invalid conversions should fail. + BOOST_CHECK(!f.convertsTo<std::string>()); + BOOST_CHECK(!f.convertsTo<int>()); + BOOST_CHECK_THROW(f.get<std::string>(), InvalidConversionException); + BOOST_CHECK_THROW(f.get<int>(), InvalidConversionException); + + // getFloatingPointValue: check twice, regression test for QPID-6470 + BOOST_CHECK_CLOSE((double(f.getFloatingPointValue<float,sizeof(float)>())), 42.42, 0.001); + BOOST_CHECK_CLOSE((double(f.getFloatingPointValue<float,sizeof(float)>())), 42.42, 0.001); + BOOST_CHECK_CLOSE((df.getFloatingPointValue<double,sizeof(double)>()), 123.123, 0.001); + // getFloatingPointValue should *not* convert float/double, require exact type. + BOOST_CHECK_THROW((f.getFloatingPointValue<double,sizeof(double)>()), InvalidConversionException); + BOOST_CHECK_THROW((double(df.getFloatingPointValue<float,sizeof(float)>())), InvalidConversionException); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/Frame.cpp b/qpid/cpp/src/tests/Frame.cpp new file mode 100644 index 0000000000..cfcfde04a7 --- /dev/null +++ b/qpid/cpp/src/tests/Frame.cpp @@ -0,0 +1,84 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/framing/Frame.h" + +#include <boost/lexical_cast.hpp> +#include "unit_test.h" + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(FrameTestSuite) + +using namespace std; +using namespace qpid::framing; + +QPID_AUTO_TEST_CASE(testContentBody) { + Frame f(42, AMQContentBody("foobar")); + AMQBody* body=f.getBody(); + BOOST_CHECK(dynamic_cast<AMQContentBody*>(body)); + Buffer b(f.encodedSize(); + f.encode(b); + b.flip(); + Frame g; + g.decode(b); + AMQContentBody* content=dynamic_cast<AMQContentBody*>(g.getBody()); + BOOST_REQUIRE(content); + BOOST_CHECK_EQUAL(content->getData(), "foobar"); +} + +QPID_AUTO_TEST_CASE(testMethodBody) { + FieldTable args; + args.setString("foo", "bar"); + Frame f( + 42, QueueDeclareBody(ProtocolVersion(), 1, "q", "altex", + true, false, true, false, true, args)); + BOOST_CHECK_EQUAL(f.getChannel(), 42); + Buffer b(f.encodedSize(); + f.encode(b); + b.flip(); + Frame g; + g.decode(b); + BOOST_CHECK_EQUAL(f.getChannel(), g.getChannel()); + QueueDeclareBody* declare=dynamic_cast<QueueDeclareBody*>(g.getBody()); + BOOST_REQUIRE(declare); + BOOST_CHECK_EQUAL(declare->getAlternateExchange(), "altex"); + BOOST_CHECK_EQUAL(lexical_cast<string>(*f.getBody()), lexical_cast<string>(*g.getBody())); +} + +QPID_AUTO_TEST_CASE(testLoop) { + // Run in a loop so heap profiler can spot any allocations. + Buffer b(1024); + for (int i = 0; i < 100; ++i) { + Frame ctor(2, AccessRequestOkBody(ProtocolVersion(), 42)); + Frame assign(3); + assign.body = AccessRequestOkBody(ProtocolVersion(), 42); + assign.encode(b); + b.flip(); + Frame g; + g.decode(b); + BOOST_REQUIRE(dynamic_cast<AccessRequestOkBody*>(g.getBody())->getTicket() == 42); + } +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/FrameDecoder.cpp b/qpid/cpp/src/tests/FrameDecoder.cpp new file mode 100644 index 0000000000..9eeff2a41e --- /dev/null +++ b/qpid/cpp/src/tests/FrameDecoder.cpp @@ -0,0 +1,78 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "unit_test.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/FrameDecoder.h" +#include "qpid/framing/AMQContentBody.h" +#include "qpid/framing/Buffer.h" +#include <string> + + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(FrameDecoderTest) + +using namespace std; +using namespace qpid::framing; + + +string makeData(int size) { + string data; + data.resize(size); + for (int i =0; i < size; ++i) + data[i] = 'a' + (i%26); + return data; +} +string encodeFrame(string data) { + AMQFrame f((AMQContentBody(data))); + string encoded; + encoded.resize(f.encodedSize()); + Buffer b(&encoded[0], encoded.size()); + f.encode(b); + return encoded; +} + +string getData(const AMQFrame& frame) { + const AMQContentBody* content = dynamic_cast<const AMQContentBody*>(frame.getBody()); + BOOST_CHECK(content); + return content->getData(); +} + +QPID_AUTO_TEST_CASE(testByteFragments) { + string data = makeData(42); + string encoded = encodeFrame(data); + FrameDecoder decoder; + for (size_t i = 0; i < encoded.size()-1; ++i) { + Buffer buf(&encoded[i], 1); + BOOST_CHECK(!decoder.decode(buf)); + } + Buffer buf(&encoded[encoded.size()-1], 1); + BOOST_CHECK(decoder.decode(buf)); + BOOST_CHECK_EQUAL(data, getData(decoder.getFrame())); +} + + + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/FramingTest.cpp b/qpid/cpp/src/tests/FramingTest.cpp new file mode 100644 index 0000000000..2392b6fec4 --- /dev/null +++ b/qpid/cpp/src/tests/FramingTest.cpp @@ -0,0 +1,168 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/client/Connection.h" +#include "qpid/client/Connector.h" +#include "qpid/framing/AMQP_HighestVersion.h" +#include "qpid/framing/ProtocolVersion.h" +#include "qpid/framing/all_method_bodies.h" +#include "qpid/framing/amqp_framing.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/framing/FieldValue.h" +#include "unit_test.h" + +#include <boost/bind.hpp> +#include <boost/lexical_cast.hpp> +#include <iostream> + +#include <memory> +#include <sstream> +#include <typeinfo> + +using namespace qpid; +using namespace qpid::framing; +using namespace std; + +namespace qpid { +namespace tests { + +template <class T> +std::string tostring(const T& x) +{ + std::ostringstream out; + out << x; + return out.str(); +} + +QPID_AUTO_TEST_SUITE(FramingTestSuite) + +QPID_AUTO_TEST_CASE(testMessageTransferBody) +{ + char buffer[1024]; + ProtocolVersion version(highestProtocolVersion); + Buffer wbuff(buffer, sizeof(buffer)); + MessageTransferBody in(version, "my-exchange", 1, 1); + in.encode(wbuff); + + Buffer rbuff(buffer, sizeof(buffer)); + MessageTransferBody out(version); + out.decode(rbuff); + BOOST_CHECK_EQUAL(tostring(in), tostring(out)); +} + +QPID_AUTO_TEST_CASE(testConnectionSecureBody) +{ + char buffer[1024]; + ProtocolVersion version(highestProtocolVersion); + Buffer wbuff(buffer, sizeof(buffer)); + std::string s = "security credential"; + ConnectionSecureBody in(version, s); + in.encode(wbuff); + + Buffer rbuff(buffer, sizeof(buffer)); + ConnectionSecureBody out(version); + out.decode(rbuff); + BOOST_CHECK_EQUAL(tostring(in), tostring(out)); +} + +QPID_AUTO_TEST_CASE(testConnectionRedirectBody) +{ + char buffer[1024]; + ProtocolVersion version(highestProtocolVersion); + Buffer wbuff(buffer, sizeof(buffer)); + std::string a = "hostA"; + std::string b = "hostB"; + Array hosts(0x95); + hosts.add(boost::shared_ptr<FieldValue>(new Str16Value(a))); + hosts.add(boost::shared_ptr<FieldValue>(new Str16Value(b))); + + ConnectionRedirectBody in(version, a, hosts); + in.encode(wbuff); + + Buffer rbuff(buffer, sizeof(buffer)); + ConnectionRedirectBody out(version); + out.decode(rbuff); + BOOST_CHECK_EQUAL(tostring(in), tostring(out)); +} + +QPID_AUTO_TEST_CASE(testQueueDeclareBody) +{ + char buffer[1024]; + ProtocolVersion version(highestProtocolVersion); + Buffer wbuff(buffer, sizeof(buffer)); + QueueDeclareBody in(version, "name", "dlq", true, false, true, false, FieldTable()); + in.encode(wbuff); + + Buffer rbuff(buffer, sizeof(buffer)); + QueueDeclareBody out(version); + out.decode(rbuff); + BOOST_CHECK_EQUAL(tostring(in), tostring(out)); +} + +QPID_AUTO_TEST_CASE(testConnectionRedirectBodyFrame) +{ + char buffer[1024]; + ProtocolVersion version(highestProtocolVersion); + Buffer wbuff(buffer, sizeof(buffer)); + std::string a = "hostA"; + std::string b = "hostB"; + Array hosts(0x95); + hosts.add(boost::shared_ptr<FieldValue>(new Str16Value(a))); + hosts.add(boost::shared_ptr<FieldValue>(new Str16Value(b))); + + AMQFrame in((ConnectionRedirectBody(version, a, hosts))); + in.setChannel(999); + in.encode(wbuff); + + Buffer rbuff(buffer, sizeof(buffer)); + AMQFrame out; + out.decode(rbuff); + BOOST_CHECK_EQUAL(tostring(in), tostring(out)); +} + +QPID_AUTO_TEST_CASE(testMessageCancelBodyFrame) +{ + char buffer[1024]; + ProtocolVersion version(highestProtocolVersion); + Buffer wbuff(buffer, sizeof(buffer)); + AMQFrame in((MessageCancelBody(version, "tag"))); + in.setChannel(999); + in.encode(wbuff); + + Buffer rbuff(buffer, sizeof(buffer)); + AMQFrame out; + out.decode(rbuff); + BOOST_CHECK_EQUAL(tostring(in), tostring(out)); +} + +QPID_AUTO_TEST_CASE(badStrings) { + char data[(65535 + 2) + (255 + 1)]; + Buffer b(data, sizeof(data)); + BOOST_CHECK_THROW(b.putShortString(std::string(256, 'X')), + Exception); + BOOST_CHECK_THROW(b.putMediumString(std::string(65536, 'X')), + Exception); + b.putShortString(std::string(255, 'X')); + b.putMediumString(std::string(65535, 'X')); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/HeaderTest.cpp b/qpid/cpp/src/tests/HeaderTest.cpp new file mode 100644 index 0000000000..4b16f3c793 --- /dev/null +++ b/qpid/cpp/src/tests/HeaderTest.cpp @@ -0,0 +1,114 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include <iostream> +#include "qpid/framing/amqp_framing.h" +#include "qpid/framing/FieldValue.h" +#include "unit_test.h" + +using namespace qpid::framing; +using namespace std; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(HeaderTestSuite) + +QPID_AUTO_TEST_CASE(testGenericProperties) +{ + AMQHeaderBody body; + body.get<MessageProperties>(true)->getApplicationHeaders().setString( + "A", "BCDE"); + char buff[100]; + Buffer wbuffer(buff, 100); + body.encode(wbuffer); + + Buffer rbuffer(buff, 100); + AMQHeaderBody body2; + body2.decode(rbuffer, body.encodedSize()); + MessageProperties* props = + body2.get<MessageProperties>(true); + BOOST_CHECK_EQUAL( + string("BCDE"), + props->getApplicationHeaders().get("A")->get<string>()); +} + +QPID_AUTO_TEST_CASE(testMessageProperties) +{ + AMQFrame out((AMQHeaderBody())); + MessageProperties* props1 = + out.castBody<AMQHeaderBody>()->get<MessageProperties>(true); + + props1->setContentLength(42); + props1->setMessageId(Uuid(true)); + props1->setCorrelationId("correlationId"); + props1->setReplyTo(ReplyTo("ex","key")); + props1->setContentType("contentType"); + props1->setContentEncoding("contentEncoding"); + props1->setUserId("userId"); + props1->setAppId("appId"); + + char buff[10000]; + Buffer wbuffer(buff, 10000); + out.encode(wbuffer); + + Buffer rbuffer(buff, 10000); + AMQFrame in; + in.decode(rbuffer); + MessageProperties* props2 = + in.castBody<AMQHeaderBody>()->get<MessageProperties>(true); + + BOOST_CHECK_EQUAL(props1->getContentLength(), props2->getContentLength()); + BOOST_CHECK_EQUAL(props1->getMessageId(), props2->getMessageId()); + BOOST_CHECK_EQUAL(props1->getCorrelationId(), props2->getCorrelationId()); + BOOST_CHECK_EQUAL(props1->getContentType(), props2->getContentType()); + BOOST_CHECK_EQUAL(props1->getContentEncoding(), props2->getContentEncoding()); + BOOST_CHECK_EQUAL(props1->getUserId(), props2->getUserId()); + BOOST_CHECK_EQUAL(props1->getAppId(), props2->getAppId()); + +} + +QPID_AUTO_TEST_CASE(testDeliveryProperies) +{ + AMQFrame out((AMQHeaderBody())); + DeliveryProperties* props1 = + out.castBody<AMQHeaderBody>()->get<DeliveryProperties>(true); + + props1->setDiscardUnroutable(true); + props1->setExchange("foo"); + + char buff[10000]; + Buffer wbuffer(buff, 10000); + out.encode(wbuffer); + + Buffer rbuffer(buff, 10000); + AMQFrame in; + in.decode(rbuffer); + DeliveryProperties* props2 = + in.castBody<AMQHeaderBody>()->get<DeliveryProperties>(true); + + BOOST_CHECK(props2->getDiscardUnroutable()); + BOOST_CHECK_EQUAL(string("foo"), props2->getExchange()); + BOOST_CHECK(!props2->hasTimestamp()); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/HeadersExchangeTest.cpp b/qpid/cpp/src/tests/HeadersExchangeTest.cpp new file mode 100644 index 0000000000..3e68b84bc3 --- /dev/null +++ b/qpid/cpp/src/tests/HeadersExchangeTest.cpp @@ -0,0 +1,194 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/Exception.h" +#include "qpid/broker/HeadersExchange.h" +#include "qpid/broker/Message.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/framing/FieldValue.h" +#include "MessageUtils.h" +#include "unit_test.h" + +using namespace qpid::broker; +using namespace qpid::framing; +using namespace qpid::types; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(HeadersExchangeTestSuite) + +QPID_AUTO_TEST_CASE(testMatchAll) +{ + FieldTable b; + b.setString("x-match", "all"); + b.setString("foo", "FOO"); + b.setInt("n", 42); + + Variant::Map m; + const int32_t int_n(42); + m["foo"] = "FOO"; + m["n"] = int_n; + BOOST_CHECK(HeadersExchange::match(b, MessageUtils::createMessage(m, "", "", true))); + + // Ignore extras. + m["extra"] = "x"; + BOOST_CHECK(HeadersExchange::match(b, MessageUtils::createMessage(m, "", "", true))); + + // Fail mismatch, wrong value. + m["foo"] = "NotFoo"; + BOOST_CHECK(!HeadersExchange::match(b, MessageUtils::createMessage(m, "", "", true))); + + // Fail mismatch, missing value + Variant::Map n; + n["n"] = int_n; + n["extra"] = "x"; + BOOST_CHECK(!HeadersExchange::match(b, MessageUtils::createMessage(n, "", "", true))); +} + +QPID_AUTO_TEST_CASE(testMatchAny) +{ + FieldTable b; + b.setString("x-match", "any"); + b.setString("foo", "FOO"); + b.setInt("n", 42); + + Variant::Map n; + Variant::Map m; + m["foo"] = "FOO"; + BOOST_CHECK(!HeadersExchange::match(b, MessageUtils::createMessage(n, "", "", true))); + BOOST_CHECK(HeadersExchange::match(b, MessageUtils::createMessage(m, "", "", true))); + const int32_t int_n(42); + m["n"] = int_n; + BOOST_CHECK(HeadersExchange::match(b, MessageUtils::createMessage(m, "", "", true))); +} + +QPID_AUTO_TEST_CASE(testMatchEmptyValue) +{ + FieldTable b; + b.setString("x-match", "all"); + b.set("foo", FieldTable::ValuePtr()); + b.set("n", FieldTable::ValuePtr()); + Variant::Map m; + BOOST_CHECK(!HeadersExchange::match(b, MessageUtils::createMessage(m, "", "", true))); +} + +QPID_AUTO_TEST_CASE(testMatchEmptyArgs) +{ + FieldTable b; + Variant::Map m; + m["foo"] = "FOO"; + Message msg = MessageUtils::createMessage(m, "", "", true); + + b.setString("x-match", "all"); + BOOST_CHECK(HeadersExchange::match(b, msg)); + b.setString("x-match", "any"); + BOOST_CHECK(!HeadersExchange::match(b, msg)); +} + + +QPID_AUTO_TEST_CASE(testMatchNoXMatch) +{ + FieldTable b; + b.setString("foo", "FOO"); + Variant::Map m; + m["foo"] = "FOO"; + BOOST_CHECK(!HeadersExchange::match(b, MessageUtils::createMessage(m, "", "", true))); +} + +QPID_AUTO_TEST_CASE(testBindNoXMatch) +{ + HeadersExchange exchange("test"); + Queue::shared_ptr queue; + std::string key; + FieldTable args; + try { + //just checking this doesn't cause assertion etc + exchange.bind(queue, key, &args); + } catch(qpid::Exception&) { + //expected + } +} + + +QPID_AUTO_TEST_CASE(testMatchSizedIntUint) +{ + typedef std::list<Variant::Map> vml; + + const int8_t i8(1); + const int16_t i16(1); + const int32_t i32(1); + const int64_t i64(1); + const uint8_t u8(1); + const uint16_t u16(1); + const uint32_t u32(1); + const uint64_t u64(1); + + Variant::Map mi8, mi16, mi32, mi64; + Variant::Map mu8, mu16, mu32, mu64; + + mi8["bk"] = i8; + mi16["bk"] = i16; + mi32["bk"] = i32; + mi64["bk"] = i64; + mu8["bk"] = u8; + mu16["bk"] = u16; + mu32["bk"] = u32; + mu64["bk"] = u64; + + vml mMap; + mMap.push_back(mi8); + mMap.push_back(mi16); + mMap.push_back(mi32); + mMap.push_back(mi64); + mMap.push_back(mu8); + mMap.push_back(mu16); + mMap.push_back(mu32); + mMap.push_back(mu64); + + for (vml::iterator bVal=mMap.begin(); bVal!=mMap.end(); ++bVal) { + FieldTable b; + qpid::amqp_0_10::translate(*bVal, b); + b.setString("x-match", "all"); + for (vml::iterator mVal=mMap.begin(); mVal!=mMap.end(); ++mVal) { + BOOST_CHECK(HeadersExchange::match(b, MessageUtils::createMessage(*mVal, "", "", true))); + } + } +} + +// TODO: Headers exchange match on single + +QPID_AUTO_TEST_CASE(testMatchFloatDouble) +{ + const double iFloat(1.0); + Variant::Map m; + m["bk"] = iFloat; + + FieldTable b; + qpid::amqp_0_10::translate(m, b); + b.setString("x-match", "all"); + BOOST_CHECK(HeadersExchange::match(b, MessageUtils::createMessage(m, "", "", true))); +} + + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/InlineAllocator.cpp b/qpid/cpp/src/tests/InlineAllocator.cpp new file mode 100644 index 0000000000..a4c4d64cea --- /dev/null +++ b/qpid/cpp/src/tests/InlineAllocator.cpp @@ -0,0 +1,68 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/InlineAllocator.h" +#include "unit_test.h" + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(InlineAllocatorTestSuite) + +using namespace qpid; +using namespace std; + +QPID_AUTO_TEST_CASE(testAllocate) { + InlineAllocator<std::allocator<char>, 2> alloc; + + char* p = alloc.allocate(1); + BOOST_CHECK(p == (char*)&alloc); + alloc.deallocate(p,1); + + p = alloc.allocate(2); + BOOST_CHECK(p == (char*)&alloc); + alloc.deallocate(p,2); + + p = alloc.allocate(3); + BOOST_CHECK(p != (char*)&alloc); + alloc.deallocate(p,3); +} + +QPID_AUTO_TEST_CASE(testAllocateFull) { + InlineAllocator<std::allocator<char>, 1> alloc; + + char* p = alloc.allocate(1); + BOOST_CHECK(p == (char*)&alloc); + + char* q = alloc.allocate(1); + BOOST_CHECK(q != (char*)&alloc); + + alloc.deallocate(p,1); + p = alloc.allocate(1); + BOOST_CHECK(p == (char*)&alloc); + + alloc.deallocate(p,1); + alloc.deallocate(q,1); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/InlineVector.cpp b/qpid/cpp/src/tests/InlineVector.cpp new file mode 100644 index 0000000000..ba5165886d --- /dev/null +++ b/qpid/cpp/src/tests/InlineVector.cpp @@ -0,0 +1,128 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/InlineVector.h" +#include "unit_test.h" + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(InlineVectorTestSuite) + +using namespace qpid; +using namespace std; + +typedef InlineVector<int, 3> Vec; + +bool isInline(const Vec& v) { + // If nothing, give it the benefit of the doubt; + // can't take address of nothing. + if (v.size() <= 0) + return true; + return (const char*)&v <= (const char*)(&v[0]) && + (const char*)(&v[0]) < (const char*)&v+sizeof(v); +} + +QPID_AUTO_TEST_CASE(testCtor) { + { + Vec v; + BOOST_CHECK(isInline(v)); + BOOST_CHECK(v.empty()); + } + { + Vec v(3, 42); + BOOST_CHECK(isInline(v)); + BOOST_CHECK_EQUAL(3u, v.size()); + BOOST_CHECK_EQUAL(v[0], 42); + BOOST_CHECK_EQUAL(v[2], 42); + + Vec u(v); + BOOST_CHECK(isInline(u)); + BOOST_CHECK_EQUAL(3u, u.size()); + BOOST_CHECK_EQUAL(u[0], 42); + BOOST_CHECK_EQUAL(u[2], 42); + } + + { + Vec v(4, 42); + + BOOST_CHECK_EQUAL(v.size(), 4u); + BOOST_CHECK(!isInline(v)); + Vec u(v); + BOOST_CHECK_EQUAL(u.size(), 4u); + BOOST_CHECK(!isInline(u)); + } +} + +QPID_AUTO_TEST_CASE(testInsert) { + { + Vec v; + v.push_back(1); + BOOST_CHECK_EQUAL(v.size(), 1u); + BOOST_CHECK_EQUAL(v.back(), 1); + BOOST_CHECK(isInline(v)); + + v.insert(v.begin(), 2); + BOOST_CHECK_EQUAL(v.size(), 2u); + BOOST_CHECK_EQUAL(v.back(), 1); + BOOST_CHECK(isInline(v)); + + v.push_back(3); + BOOST_CHECK(isInline(v)); + + v.push_back(4); + + BOOST_CHECK(!isInline(v)); + } + { + Vec v(3,42); + v.insert(v.begin(), 9); + BOOST_CHECK_EQUAL(v.size(), 4u); + BOOST_CHECK(!isInline(v)); + } + { + Vec v(3,42); + v.insert(v.begin()+1, 9); + BOOST_CHECK(!isInline(v)); + BOOST_CHECK_EQUAL(v.size(), 4u); + } +} + +QPID_AUTO_TEST_CASE(testAssign) { + Vec v(3,42); + Vec u; + u = v; + BOOST_CHECK(isInline(u)); + u.push_back(4); + BOOST_CHECK(!isInline(u)); + v = u; + BOOST_CHECK(!isInline(v)); +} + +QPID_AUTO_TEST_CASE(testResize) { + Vec v; + v.resize(5); + BOOST_CHECK(!isInline(v)); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/ManagementTest.cpp b/qpid/cpp/src/tests/ManagementTest.cpp new file mode 100644 index 0000000000..98ef591fae --- /dev/null +++ b/qpid/cpp/src/tests/ManagementTest.cpp @@ -0,0 +1,97 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/management/ManagementObject.h" +#include "qpid/framing/Buffer.h" +#include "unit_test.h" + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(ManagementTestSuite) + +using namespace qpid::framing; +using namespace qpid::management; + +QPID_AUTO_TEST_CASE(testObjectIdSerializeStream) { + std::string text("0-10-4-2500-80000000000()"); + std::stringstream input(text); + + ObjectId oid(input); + + std::stringstream output; + output << oid; + + BOOST_CHECK_EQUAL(text, output.str()); +} + +QPID_AUTO_TEST_CASE(testObjectIdSerializeString) { + std::string text("0-10-4-2500-80000000000()"); + + ObjectId oid(text); + + std::stringstream output; + output << oid; + + BOOST_CHECK_EQUAL(text, output.str()); +} + +QPID_AUTO_TEST_CASE(testObjectIdEncode) { + qpid::types::Variant::Map oidMap; + + ObjectId oid(1, 2, 3, 9999); + oid.setV2Key("testkey"); + oid.setAgentName("myAgent"); + + std::stringstream out1; + out1 << oid; + + BOOST_CHECK_EQUAL(out1.str(), "1-2-3-myAgent-9999(testkey)"); +} + +QPID_AUTO_TEST_CASE(testObjectIdAttach) { + AgentAttachment agent; + ObjectId oid(&agent, 10, 20); + oid.setV2Key("GabbaGabbaHey"); + oid.setAgentName("MrSmith"); + + std::stringstream out1; + out1 << oid; + + BOOST_CHECK_EQUAL(out1.str(), "10-20-0-MrSmith-0(GabbaGabbaHey)"); + + agent.setBanks(30, 40); + std::stringstream out2; + out2 << oid; + + BOOST_CHECK_EQUAL(out2.str(), "10-20-30-MrSmith-0(GabbaGabbaHey)"); +} + +QPID_AUTO_TEST_CASE(testObjectIdCreate) { + ObjectId oid("some-agent-name", "an-object-name"); + + BOOST_CHECK_EQUAL(oid.getAgentName(), "some-agent-name"); + BOOST_CHECK_EQUAL(oid.getV2Key(), "an-object-name"); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/MessageReplayTracker.cpp b/qpid/cpp/src/tests/MessageReplayTracker.cpp new file mode 100644 index 0000000000..c0778247f0 --- /dev/null +++ b/qpid/cpp/src/tests/MessageReplayTracker.cpp @@ -0,0 +1,104 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "unit_test.h" +#include "BrokerFixture.h" +#include "qpid/client/MessageReplayTracker.h" +#include "qpid/sys/Time.h" + +#include <boost/format.hpp> + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(MessageReplayTrackerTests) + +using namespace qpid::client; +using namespace qpid::sys; +using std::string; + +class ReplayBufferChecker +{ + public: + + ReplayBufferChecker(uint from, uint to) : end(to), i(from) {} + + void operator()(const Message& m) + { + if (i > end) BOOST_FAIL("Extra message found: " + m.getData()); + BOOST_CHECK_EQUAL((boost::format("Message_%1%") % (i++)).str(), m.getData()); + } + private: + const uint end; + uint i; + +}; + +QPID_AUTO_TEST_CASE(testReplay) +{ + SessionFixture fix; + fix.session.queueDeclare(arg::queue="my-queue", arg::exclusive=true, arg::autoDelete=true); + + MessageReplayTracker tracker(10); + tracker.init(fix.session); + for (uint i = 0; i < 5; i++) { + Message message((boost::format("Message_%1%") % (i+1)).str(), "my-queue"); + tracker.send(message); + } + ReplayBufferChecker checker(1, 10); + tracker.foreach(checker); + + tracker.replay(fix.session); + for (uint j = 0; j < 2; j++) {//each message should have been sent twice + for (uint i = 0; i < 5; i++) { + Message m; + BOOST_CHECK(fix.subs.get(m, "my-queue", TIME_SEC)); + BOOST_CHECK_EQUAL((boost::format("Message_%1%") % (i+1)).str(), m.getData()); + } + } + Message m; + BOOST_CHECK(!fix.subs.get(m, "my-queue")); +} + +QPID_AUTO_TEST_CASE(testCheckCompletion) +{ + SessionFixture fix; + fix.session.queueDeclare(arg::queue="my-queue", arg::exclusive=true, arg::autoDelete=true); + + MessageReplayTracker tracker(10); + tracker.init(fix.session); + for (uint i = 0; i < 5; i++) { + Message message((boost::format("Message_%1%") % (i+1)).str(), "my-queue"); + tracker.send(message); + } + fix.session.sync();//ensures all messages are complete + tracker.checkCompletion(); + tracker.replay(fix.session); + Message received; + for (uint i = 0; i < 5; i++) { + BOOST_CHECK(fix.subs.get(received, "my-queue")); + BOOST_CHECK_EQUAL((boost::format("Message_%1%") % (i+1)).str(), received.getData()); + } + BOOST_CHECK(!fix.subs.get(received, "my-queue")); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/MessageTest.cpp b/qpid/cpp/src/tests/MessageTest.cpp new file mode 100644 index 0000000000..a6c5157b47 --- /dev/null +++ b/qpid/cpp/src/tests/MessageTest.cpp @@ -0,0 +1,89 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/broker/Message.h" +#include "qpid/broker/Protocol.h" +#include "qpid/framing/AMQP_HighestVersion.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/framing/FieldValue.h" +#include "qpid/framing/Uuid.h" +#include "MessageUtils.h" + +#include "unit_test.h" + +#include <iostream> + +using namespace qpid::broker; +using namespace qpid::framing; + +using std::string; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(MessageTestSuite) + +QPID_AUTO_TEST_CASE(testEncodeDecode) +{ + string exchange = "MyExchange"; + string routingKey = "MyRoutingKey"; + uint64_t ttl(60); + Uuid messageId(true); + string data("abcdefghijklmn"); + + qpid::types::Variant::Map properties; + properties["routing-key"] = routingKey; + properties["ttl"] = ttl; + properties["durable"] = true; + properties["message-id"] = qpid::types::Uuid(messageId.data()); + properties["abc"] = "xyz"; + Message msg = MessageUtils::createMessage(properties, data); + + std::vector<char> bytes(msg.getPersistentContext()->encodedSize()); + qpid::framing::Buffer buffer(&bytes[0], bytes.size()); + msg.getPersistentContext()->encode(buffer); + buffer.reset(); + ProtocolRegistry registry(std::set<std::string>(), 0); + msg = registry.decode(buffer); + + BOOST_CHECK_EQUAL(routingKey, msg.getRoutingKey()); + BOOST_CHECK_EQUAL((uint64_t) data.size(), msg.getContent().size()); + BOOST_CHECK_EQUAL(data, msg.getContent()); + //BOOST_CHECK_EQUAL(messageId, msg->getProperties<MessageProperties>()->getMessageId()); + BOOST_CHECK_EQUAL(string("xyz"), msg.getPropertyAsString("abc")); + BOOST_CHECK(msg.isPersistent()); +} + +QPID_AUTO_TEST_CASE(testMessageProperties) +{ + string data("abcdefghijklmn"); + + qpid::types::Variant::Map properties; + properties["abc"] = "xyz"; + Message msg = MessageUtils::createMessage(properties, data); + + // Regression test that looking up a property doesn't return a prefix + BOOST_CHECK_EQUAL(msg.getProperty("abcdef").getType(), qpid::types::VAR_VOID); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/MessageUtils.h b/qpid/cpp/src/tests/MessageUtils.h new file mode 100644 index 0000000000..f05b0d8b20 --- /dev/null +++ b/qpid/cpp/src/tests/MessageUtils.h @@ -0,0 +1,117 @@ +#ifndef TESTS_MESSAGEUTILS_H +#define TESTS_MESSAGEUTILS_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/broker/Message.h" +#include "qpid/broker/amqp_0_10/MessageTransfer.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/framing/Uuid.h" +#include "qpid/types/Variant.h" +#include "qpid/amqp_0_10/Codecs.h" + +using namespace qpid; +using namespace broker; +using namespace framing; + +namespace qpid { +namespace tests { + +struct MessageUtils +{ + static Message createMessage(const qpid::types::Variant::Map& properties, + const std::string& content="", + const std::string& destination = "", + bool replaceHeaders = false + ) + { + boost::intrusive_ptr<broker::amqp_0_10::MessageTransfer> msg(new broker::amqp_0_10::MessageTransfer()); + + AMQFrame method(( MessageTransferBody(ProtocolVersion(), destination, 0, 0))); + AMQFrame header((AMQHeaderBody())); + + msg->getFrames().append(method); + msg->getFrames().append(header); + if (content.size()) { + msg->getFrames().getHeaders()->get<MessageProperties>(true)->setContentLength(content.size()); + AMQFrame data((AMQContentBody(content))); + msg->getFrames().append(data); + } + if (!replaceHeaders) { + for (qpid::types::Variant::Map::const_iterator i = properties.begin(); i != properties.end(); ++i) { + if (i->first == "routing-key" && !i->second.isVoid()) { + msg->getFrames().getHeaders()->get<DeliveryProperties>(true)->setRoutingKey(i->second); + } else if (i->first == "message-id" && !i->second.isVoid()) { + qpid::types::Uuid id = i->second; + qpid::framing::Uuid id2(id.data()); + msg->getFrames().getHeaders()->get<MessageProperties>(true)->setMessageId(id2); + } else if (i->first == "ttl" && !i->second.isVoid()) { + msg->getFrames().getHeaders()->get<DeliveryProperties>(true)->setTtl(i->second); + } else if (i->first == "priority" && !i->second.isVoid()) { + msg->getFrames().getHeaders()->get<DeliveryProperties>(true)->setPriority(i->second); + } else if (i->first == "durable" && !i->second.isVoid()) { + msg->getFrames().getHeaders()->get<DeliveryProperties>(true)->setDeliveryMode(i->second.asBool() ? 2 : 1); + } else { + msg->getFrames().getHeaders()->get<MessageProperties>(true)->getApplicationHeaders().setString(i->first, i->second); + } + } + } else { + framing::FieldTable newHeaders; + qpid::amqp_0_10::translate(properties, newHeaders); + msg->getFrames().getHeaders()->get<MessageProperties>(true)->getApplicationHeaders() = newHeaders; + } + return Message(msg, msg); + } + + + static Message createMessage(const std::string& exchange="", const std::string& routingKey="", + uint64_t ttl = 0, bool durable = false, const Uuid& messageId=Uuid(true), + const std::string& content="") + { + boost::intrusive_ptr<broker::amqp_0_10::MessageTransfer> msg(new broker::amqp_0_10::MessageTransfer()); + + AMQFrame method(( MessageTransferBody(ProtocolVersion(), exchange, 0, 0))); + AMQFrame header((AMQHeaderBody())); + + msg->getFrames().append(method); + msg->getFrames().append(header); + MessageProperties* props = msg->getFrames().getHeaders()->get<MessageProperties>(true); + props->setContentLength(content.size()); + props->setMessageId(messageId); + msg->getFrames().getHeaders()->get<DeliveryProperties>(true)->setRoutingKey(routingKey); + if (durable) + msg->getFrames().getHeaders()->get<DeliveryProperties>(true)->setDeliveryMode(2); + if (ttl) + msg->getFrames().getHeaders()->get<DeliveryProperties>(true)->setTtl(ttl); + if (content.size()) { + AMQFrame data((AMQContentBody(content))); + msg->getFrames().append(data); + } + if (ttl) msg->computeExpiration(); + return Message(msg, msg); + } +}; + +}} // namespace qpid::tests + +#endif /*!TESTS_MESSAGEUTILS_H*/ diff --git a/qpid/cpp/src/tests/MessagingFixture.h b/qpid/cpp/src/tests/MessagingFixture.h new file mode 100644 index 0000000000..165aefeeec --- /dev/null +++ b/qpid/cpp/src/tests/MessagingFixture.h @@ -0,0 +1,352 @@ +#ifndef TESTS_MESSAGINGFIXTURE_H +#define TESTS_MESSAGINGFIXTURE_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "BrokerFixture.h" +#include "unit_test.h" +#include "test_tools.h" +#include "qpid/client/Connection.h" +#include "qpid/client/Session.h" +#include "qpid/framing/Uuid.h" +#include "qpid/messaging/Address.h" +#include "qpid/messaging/Connection.h" +#include "qpid/messaging/Session.h" +#include "qpid/messaging/Sender.h" +#include "qpid/messaging/Receiver.h" +#include "qpid/messaging/Message.h" +#include "qpid/types/Variant.h" + +#include <boost/format.hpp> + +namespace qpid { +namespace tests { + +using qpid::types::Variant; + +struct BrokerAdmin +{ + qpid::client::Connection connection; + qpid::client::Session session; + + BrokerAdmin(uint16_t port) + { + connection.open("localhost", port); + session = connection.newSession(); + } + + void createQueue(const std::string& name) + { + session.queueDeclare(qpid::client::arg::queue=name); + } + + void deleteQueue(const std::string& name) + { + session.queueDelete(qpid::client::arg::queue=name); + } + + void createExchange(const std::string& name, const std::string& type) + { + session.exchangeDeclare(qpid::client::arg::exchange=name, qpid::client::arg::type=type); + } + + void deleteExchange(const std::string& name) + { + session.exchangeDelete(qpid::client::arg::exchange=name); + } + + bool checkQueueExists(const std::string& name) + { + return session.queueQuery(name).getQueue() == name; + } + + bool checkExchangeExists(const std::string& name, std::string& type) + { + qpid::framing::ExchangeQueryResult result = session.exchangeQuery(name); + type = result.getType(); + return !result.getNotFound(); + } + + void send(qpid::client::Message& message, const std::string& exchange=std::string()) + { + session.messageTransfer(qpid::client::arg::destination=exchange, qpid::client::arg::content=message); + } + + ~BrokerAdmin() + { + session.close(); + connection.close(); + } +}; + +struct MessagingFixture : public BrokerFixture +{ + messaging::Connection connection; + messaging::Session session; + BrokerAdmin admin; + + MessagingFixture(const BrokerOptions& opts = BrokerOptions(), bool mgmtEnabled=false) : + BrokerFixture(opts, mgmtEnabled), + connection(open(broker->getPort(Broker::TCP_TRANSPORT))), + session(connection.createSession()), + admin(broker->getPort(Broker::TCP_TRANSPORT)) + { + } + + static messaging::Connection open(uint16_t port) + { + messaging::Connection connection( + (boost::format("amqp:tcp:localhost:%1%") % (port)).str()); + connection.open(); + return connection; + + } + + /** Open a connection to the broker. */ + qpid::messaging::Connection newConnection() + { + qpid::messaging::Connection connection( + (boost::format("amqp:tcp:localhost:%1%") % (broker->getPort(qpid::broker::Broker::TCP_TRANSPORT))).str()); + return connection; + } + + void ping(const qpid::messaging::Address& address) + { + messaging::Receiver r = session.createReceiver(address); + messaging::Sender s = session.createSender(address); + messaging::Message out(framing::Uuid(true).str()); + s.send(out); + messaging::Message in; + BOOST_CHECK(r.fetch(in, 5*messaging::Duration::SECOND)); + BOOST_CHECK_EQUAL(out.getContent(), in.getContent()); + r.close(); + s.close(); + } + + ~MessagingFixture() + { + session.close(); + connection.close(); + } +}; + +struct QueueFixture : MessagingFixture +{ + std::string queue; + + QueueFixture(const std::string& name = "test-queue") : queue(name) + { + admin.createQueue(queue); + } + + ~QueueFixture() + { + admin.deleteQueue(queue); + } + +}; + +struct TopicFixture : MessagingFixture +{ + std::string topic; + + TopicFixture(const std::string& name = "test-topic", const std::string& type="fanout") : topic(name) + { + admin.createExchange(topic, type); + } + + ~TopicFixture() + { + admin.deleteExchange(topic); + } + +}; + +struct MultiQueueFixture : MessagingFixture +{ + typedef std::vector<std::string>::const_iterator const_iterator; + std::vector<std::string> queues; + + MultiQueueFixture(const std::vector<std::string>& names = boost::assign::list_of<std::string>("q1")("q2")("q3")) : queues(names) + { + for (const_iterator i = queues.begin(); i != queues.end(); ++i) { + admin.createQueue(*i); + } + } + + ~MultiQueueFixture() + { + connection.close(); + for (const_iterator i = queues.begin(); i != queues.end(); ++i) { + admin.deleteQueue(*i); + } + } + +}; + +inline std::vector<std::string> fetch(messaging::Receiver& receiver, int count, messaging::Duration timeout=messaging::Duration::SECOND*5) +{ + std::vector<std::string> data; + messaging::Message message; + for (int i = 0; i < count && receiver.fetch(message, timeout); i++) { + data.push_back(message.getContent()); + } + return data; +} + + +inline void send(messaging::Sender& sender, uint count = 1, uint start = 1, + const std::string& base = "Message") +{ + for (uint i = start; i < start + count; ++i) { + sender.send(messaging::Message((boost::format("%1%_%2%") % base % i).str())); + } +} + +inline void receive(messaging::Receiver& receiver, uint count = 1, uint start = 1, + const std::string& base = "Message", + messaging::Duration timeout=messaging::Duration::SECOND*5) +{ + for (uint i = start; i < start + count; ++i) { + BOOST_CHECK_EQUAL(receiver.fetch(timeout).getContent(), (boost::format("%1%_%2%") % base % i).str()); + } +} + + +class MethodInvoker +{ + public: + MethodInvoker(messaging::Session session) : + replyTo("#; {create:always, node:{x-declare:{auto-delete:true}}}"), + sender(session.createSender("qmf.default.direct/broker")), + receiver(session.createReceiver(replyTo)) {} + + void createExchange(const std::string& name, const std::string& type, bool durable=false) + { + Variant::Map params; + params["name"]=name; + params["type"]="exchange"; + params["properties"] = Variant::Map(); + params["properties"].asMap()["exchange-type"] = type; + params["properties"].asMap()["durable"] = durable; + methodRequest("create", params); + } + + void deleteExchange(const std::string& name) + { + Variant::Map params; + params["name"]=name; + params["type"]="exchange"; + methodRequest("delete", params); + } + + void createQueue(const std::string& name, bool durable=false, bool autodelete=false, + const Variant::Map& options=Variant::Map()) + { + Variant::Map params; + params["name"]=name; + params["type"]="queue"; + params["properties"] = options; + params["properties"].asMap()["durable"] = durable; + params["properties"].asMap()["auto-delete"] = autodelete; + methodRequest("create", params); + } + + void deleteQueue(const std::string& name) + { + Variant::Map params; + params["name"]=name; + params["type"]="queue"; + methodRequest("delete", params); + } + + void bind(const std::string& exchange, const std::string& queue, const std::string& key, + const Variant::Map& options=Variant::Map()) + { + Variant::Map params; + params["name"]=(boost::format("%1%/%2%/%3%") % (exchange) % (queue) % (key)).str(); + params["type"]="binding"; + params["properties"] = options; + methodRequest("create", params); + } + + void unbind(const std::string& exchange, const std::string& queue, const std::string& key) + { + Variant::Map params; + params["name"]=(boost::format("%1%/%2%/%3%") % (exchange) % (queue) % (key)).str(); + params["type"]="binding"; + methodRequest("delete", params); + } + + void methodRequest( + const std::string& method, + const Variant::Map& inParams, Variant::Map* outParams = 0, + const std::string& objectName="org.apache.qpid.broker:broker:amqp-broker") + { + Variant::Map content; + Variant::Map objectId; + objectId["_object_name"] = objectName;; + content["_object_id"] = objectId; + content["_method_name"] = method; + content["_arguments"] = inParams; + + messaging::Message request; + request.setReplyTo(replyTo); + request.getProperties()["x-amqp-0-10.app-id"] = "qmf2"; + request.getProperties()["qmf.opcode"] = "_method_request"; + encode(content, request); + + sender.send(request); + + messaging::Message response; + if (receiver.fetch(response, messaging::Duration::SECOND*5)) { + if (response.getProperties()["x-amqp-0-10.app-id"] == "qmf2") { + std::string opcode = response.getProperties()["qmf.opcode"]; + if (opcode == "_method_response") { + if (outParams) { + Variant::Map m; + decode(response, m); + *outParams = m["_arguments"].asMap(); + } + } else if (opcode == "_exception") { + Variant::Map m; + decode(response, m); + throw Exception(QPID_MSG("Error: " << m["_values"])); + } else { + throw Exception(QPID_MSG("Invalid response received, unexpected opcode: " << opcode)); + } + } else { + throw Exception(QPID_MSG("Invalid response received, not a qmfv2 message: app-id=" + << response.getProperties()["x-amqp-0-10.app-id"])); + } + } else { + throw Exception(QPID_MSG("No response received")); + } + } + private: + messaging::Address replyTo; + messaging::Sender sender; + messaging::Receiver receiver; +}; + +}} // namespace qpid::tests + +#endif /*!TESTS_MESSAGINGFIXTURE_H*/ diff --git a/qpid/cpp/src/tests/MessagingLogger.cpp b/qpid/cpp/src/tests/MessagingLogger.cpp new file mode 100644 index 0000000000..195a33db12 --- /dev/null +++ b/qpid/cpp/src/tests/MessagingLogger.cpp @@ -0,0 +1,149 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/log/Statement.h" +#include "qpid/messaging/Connection.h" +#include "qpid/messaging/exceptions.h" +#include "qpid/messaging/Logger.h" + +#include <iostream> +#include <memory> +#include <stdexcept> + +#include <vector> + +#include "unit_test.h" + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(MessagingLoggerSuite) + +class StringLogger : public qpid::messaging::LoggerOutput { + std::string& outString; + + void log(qpid::messaging::Level /*level*/, bool user, const char* /*file*/, int /*line*/, const char* /*function*/, const std::string& message){ + if (user) outString += "User "; + outString += message; + } + +public: + StringLogger(std::string& os) : + outString(os) + {} +}; + +#define SETUP_LOGGING(logger, ...) \ +do {\ + const char* args[]={"", __VA_ARGS__, 0};\ + qpid::messaging::Logger::configure((sizeof (args)/sizeof (char*))-1, args);\ + logOutput.clear();\ + qpid::messaging::Logger::setOutput(logger);\ +} while (0) +#define LOG_LEVEL(level)\ + QPID_LOG(level, #level " level output") +#define LOG_ALL_LOGGING_LEVELS \ +do { \ + LOG_LEVEL(trace); \ + LOG_LEVEL(debug); \ + LOG_LEVEL(info); \ + LOG_LEVEL(notice); \ + LOG_LEVEL(warning); \ + LOG_LEVEL(critical); \ +} while (0) +#define LOG_USER_LEVEL(level)\ + qpid::messaging::Logger::log(qpid::messaging::level, __FILE__, __LINE__, __FUNCTION__, #level " message") +#define LOG_ALL_USER_LOGGING_LEVELS \ +do { \ + LOG_USER_LEVEL(trace); \ + LOG_USER_LEVEL(debug); \ + LOG_USER_LEVEL(info); \ + LOG_USER_LEVEL(notice); \ + LOG_USER_LEVEL(warning); \ + LOG_USER_LEVEL(critical); \ +} while (0) + +std::string logOutput; + +QPID_AUTO_TEST_CASE(testLoggerLevels) +{ + StringLogger logger(logOutput); + + SETUP_LOGGING(logger, "--log-enable", "debug"); + LOG_ALL_LOGGING_LEVELS; + BOOST_CHECK_EQUAL(logOutput, "debug level output\ncritical level output\n"); + + SETUP_LOGGING(logger, "--log-enable", "trace+", "--log-disable", "notice"); + LOG_ALL_LOGGING_LEVELS; + BOOST_CHECK_EQUAL(logOutput, "trace level output\ndebug level output\ninfo level output\nwarning level output\ncritical level output\n"); + + SETUP_LOGGING(logger, "--log-enable", "info-"); + LOG_ALL_LOGGING_LEVELS; + BOOST_CHECK_EQUAL(logOutput, "trace level output\ndebug level output\ninfo level output\ncritical level output\n"); + + SETUP_LOGGING(logger, "--log-enable", "trace+", "--log-disable", "notice+"); + LOG_ALL_LOGGING_LEVELS; + BOOST_CHECK_EQUAL(logOutput, "trace level output\ndebug level output\ninfo level output\ncritical level output\n"); +} + +QPID_AUTO_TEST_CASE(testUserLoggerLevels) +{ + StringLogger logger(logOutput); + + SETUP_LOGGING(logger, "--log-enable", "debug"); + LOG_ALL_USER_LOGGING_LEVELS; + BOOST_CHECK_EQUAL(logOutput, "User debug message\nUser critical message\n"); + + SETUP_LOGGING(logger, "--log-enable", "trace+", "--log-disable", "notice"); + LOG_ALL_USER_LOGGING_LEVELS; + BOOST_CHECK_EQUAL(logOutput, "User trace message\nUser debug message\nUser info message\nUser warning message\nUser critical message\n"); + + SETUP_LOGGING(logger, "--log-enable", "info-"); + LOG_ALL_USER_LOGGING_LEVELS; + BOOST_CHECK_EQUAL(logOutput, "User trace message\nUser debug message\nUser info message\nUser critical message\n"); + + SETUP_LOGGING(logger, "--log-enable", "trace+", "--log-disable", "notice+"); + LOG_ALL_USER_LOGGING_LEVELS; + BOOST_CHECK_EQUAL(logOutput, "User trace message\nUser debug message\nUser info message\nUser critical message\n"); + + SETUP_LOGGING(logger, "--log-disable", "trace+"); + LOG_ALL_LOGGING_LEVELS; + LOG_ALL_USER_LOGGING_LEVELS; + BOOST_CHECK_EQUAL(logOutput, "critical level output\nUser critical message\n"); +} + +QPID_AUTO_TEST_CASE(testLoggerUsage) +{ + qpid::messaging::Logger::configure(0, 0, "blah"); + std::string u = qpid::messaging::Logger::usage(); + + BOOST_CHECK(!u.empty()); + BOOST_CHECK( u.find("--blah-log-enable")!=u.npos ); +} + +QPID_AUTO_TEST_CASE(testLoggerException) +{ + const char* args[]={"", "--blah-log-enable", "illegal", 0}; + BOOST_CHECK_THROW(qpid::messaging::Logger::configure(3, args, "blah"), qpid::messaging::MessagingException); +} + +QPID_AUTO_TEST_SUITE_END() +}} diff --git a/qpid/cpp/src/tests/MessagingSessionTests.cpp b/qpid/cpp/src/tests/MessagingSessionTests.cpp new file mode 100644 index 0000000000..d01dd69999 --- /dev/null +++ b/qpid/cpp/src/tests/MessagingSessionTests.cpp @@ -0,0 +1,1495 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "MessagingFixture.h" +#include "unit_test.h" +#include "test_tools.h" +#include "qpid/messaging/Address.h" +#include "qpid/messaging/Connection.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/Receiver.h" +#include "qpid/messaging/Sender.h" +#include "qpid/messaging/Session.h" +#include "qpid/client/Connection.h" +#include "qpid/client/Session.h" +#include "qpid/framing/ExchangeQueryResult.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/framing/Uuid.h" +#include "qpid/sys/Time.h" +#include <boost/assign.hpp> +#include <boost/format.hpp> +#include <string> +#include <vector> + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(MessagingSessionTests) + +using namespace qpid::messaging; +using namespace qpid::types; +using namespace qpid; +using qpid::broker::BrokerOptions; +using qpid::framing::Uuid; + + +QPID_AUTO_TEST_CASE(testSimpleSendReceive) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + Message out("test-message"); + out.setSubject("test-subject"); + sender.send(out); + Receiver receiver = fix.session.createReceiver(fix.queue); + Message in = receiver.fetch(Duration::SECOND * 5); + fix.session.acknowledge(); + BOOST_CHECK_EQUAL(in.getContent(), out.getContent()); + BOOST_CHECK_EQUAL(in.getSubject(), out.getSubject()); +} + +QPID_AUTO_TEST_CASE(testSyncSendReceive) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + Message out("test-message"); + sender.send(out, true); + Receiver receiver = fix.session.createReceiver(fix.queue); + Message in = receiver.fetch(Duration::IMMEDIATE); + fix.session.acknowledge(true); + BOOST_CHECK_EQUAL(in.getContent(), out.getContent()); +} + +QPID_AUTO_TEST_CASE(testSendReceiveHeaders) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + Message out("test-message"); + for (uint i = 0; i < 10; ++i) { + out.getProperties()["a"] = i; + out.setProperty("b", i + 100); + sender.send(out); + } + uint8_t v1(255u); + int8_t v2(-120); + out.getProperties()["c"] = v1; + out.getProperties()["d"] = v2; + sender.send(out); + Receiver receiver = fix.session.createReceiver(fix.queue); + Message in; + for (uint i = 0; i < 10; ++i) { + BOOST_CHECK(receiver.fetch(in, Duration::SECOND * 5)); + BOOST_CHECK_EQUAL(in.getContent(), out.getContent()); + BOOST_CHECK_EQUAL(in.getProperties()["a"].asUint32(), i); + BOOST_CHECK_EQUAL(in.getProperties()["b"].asUint32(), i + 100); + fix.session.acknowledge(); + } + BOOST_CHECK(receiver.fetch(in, Duration::SECOND * 5)); + Variant& c = in.getProperties()["c"]; + BOOST_CHECK_EQUAL(c.getType(), VAR_UINT8); + BOOST_CHECK_EQUAL(c.asUint8(), v1); + Variant& d = in.getProperties()["d"]; + BOOST_CHECK_EQUAL(d.getType(), VAR_INT8); + BOOST_CHECK_EQUAL(d.asInt8(), v2); +} + +QPID_AUTO_TEST_CASE(testSenderError) +{ + MessagingFixture fix; + ScopedSuppressLogging sl; + BOOST_CHECK_THROW(fix.session.createSender("NonExistentAddress"), qpid::messaging::NotFound); + fix.session = fix.connection.createSession(); + BOOST_CHECK_THROW(fix.session.createSender("NonExistentAddress; {create:receiver}"), + qpid::messaging::NotFound); +} + +QPID_AUTO_TEST_CASE(testReceiverError) +{ + MessagingFixture fix; + ScopedSuppressLogging sl; + BOOST_CHECK_THROW(fix.session.createReceiver("NonExistentAddress"), qpid::messaging::NotFound); + fix.session = fix.connection.createSession(); + BOOST_CHECK_THROW(fix.session.createReceiver("NonExistentAddress; {create:sender}"), + qpid::messaging::NotFound); +} + +QPID_AUTO_TEST_CASE(testSimpleTopic) +{ + TopicFixture fix; + + Sender sender = fix.session.createSender(fix.topic); + Message msg("one"); + sender.send(msg); + Receiver sub1 = fix.session.createReceiver(fix.topic); + sub1.setCapacity(10u); + msg.setContent("two"); + sender.send(msg); + Receiver sub2 = fix.session.createReceiver(fix.topic); + sub2.setCapacity(10u); + msg.setContent("three"); + sender.send(msg); + Receiver sub3 = fix.session.createReceiver(fix.topic); + sub3.setCapacity(10u); + msg.setContent("four"); + sender.send(msg); + BOOST_CHECK_EQUAL(fetch(sub2, 2), boost::assign::list_of<std::string>("three")("four")); + sub2.close(); + + msg.setContent("five"); + sender.send(msg); + BOOST_CHECK_EQUAL(fetch(sub1, 4), boost::assign::list_of<std::string>("two")("three")("four")("five")); + BOOST_CHECK_EQUAL(fetch(sub3, 2), boost::assign::list_of<std::string>("four")("five")); + Message in; + BOOST_CHECK(!sub2.fetch(in, Duration::IMMEDIATE));//TODO: or should this raise an error? + + + //TODO: check pending messages... +} + +QPID_AUTO_TEST_CASE(testNextReceiver) +{ + MultiQueueFixture fix; + + for (uint i = 0; i < fix.queues.size(); i++) { + Receiver r = fix.session.createReceiver(fix.queues[i]); + r.setCapacity(10u); + } + + for (uint i = 0; i < fix.queues.size(); i++) { + Sender s = fix.session.createSender(fix.queues[i]); + Message msg((boost::format("Message_%1%") % (i+1)).str()); + s.send(msg); + } + + for (uint i = 0; i < fix.queues.size(); i++) { + Message msg; + BOOST_CHECK(fix.session.nextReceiver().fetch(msg, Duration::SECOND)); + BOOST_CHECK_EQUAL(msg.getContent(), (boost::format("Message_%1%") % (i+1)).str()); + } +} + +QPID_AUTO_TEST_CASE(testMapMessage) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + Message out; + Variant::Map content; + content["abc"] = "def"; + content["pi"] = 3.14f; + Variant utf8("A utf 8 string"); + utf8.setEncoding("utf8"); + content["utf8"] = utf8; + Variant utf16("\x00\x61\x00\x62\x00\x63"); + utf16.setEncoding("utf16"); + content["utf16"] = utf16; + encode(content, out); + sender.send(out); + Receiver receiver = fix.session.createReceiver(fix.queue); + Message in = receiver.fetch(5 * Duration::SECOND); + Variant::Map view; + decode(in, view); + BOOST_CHECK_EQUAL(view["abc"].asString(), "def"); + BOOST_CHECK_EQUAL(view["pi"].asFloat(), 3.14f); + BOOST_CHECK_EQUAL(view["utf8"].asString(), utf8.asString()); + BOOST_CHECK_EQUAL(view["utf8"].getEncoding(), utf8.getEncoding()); + BOOST_CHECK_EQUAL(view["utf16"].asString(), utf16.asString()); + BOOST_CHECK_EQUAL(view["utf16"].getEncoding(), utf16.getEncoding()); + fix.session.acknowledge(); +} + +QPID_AUTO_TEST_CASE(testMapMessageWithInitial) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + Message out; + Variant::Map imap; + imap["abc"] = "def"; + imap["pi"] = 3.14f; + encode(imap, out); + sender.send(out); + Receiver receiver = fix.session.createReceiver(fix.queue); + Message in = receiver.fetch(5 * Duration::SECOND); + Variant::Map view; + decode(in, view); + BOOST_CHECK_EQUAL(view["abc"].asString(), "def"); + BOOST_CHECK_EQUAL(view["pi"].asFloat(), 3.14f); + fix.session.acknowledge(); +} + +QPID_AUTO_TEST_CASE(testListMessage) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + Message out; + Variant::List content; + content.push_back(Variant("abc")); + content.push_back(Variant(1234)); + content.push_back(Variant("def")); + content.push_back(Variant(56.789)); + encode(content, out); + sender.send(out); + Receiver receiver = fix.session.createReceiver(fix.queue); + Message in = receiver.fetch(5 * Duration::SECOND); + Variant::List view; + decode(in, view); + BOOST_CHECK_EQUAL(view.size(), content.size()); + BOOST_CHECK_EQUAL(view.front().asString(), "abc"); + BOOST_CHECK_EQUAL(view.back().asDouble(), 56.789); + + Variant::List::const_iterator i = view.begin(); + BOOST_CHECK(i != view.end()); + BOOST_CHECK_EQUAL(i->asString(), "abc"); + BOOST_CHECK(++i != view.end()); + BOOST_CHECK_EQUAL(i->asInt64(), 1234); + BOOST_CHECK(++i != view.end()); + BOOST_CHECK_EQUAL(i->asString(), "def"); + BOOST_CHECK(++i != view.end()); + BOOST_CHECK_EQUAL(i->asDouble(), 56.789); + BOOST_CHECK(++i == view.end()); + + fix.session.acknowledge(); +} + +QPID_AUTO_TEST_CASE(testListMessageWithInitial) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + Message out; + Variant::List ilist; + ilist.push_back(Variant("abc")); + ilist.push_back(Variant(1234)); + ilist.push_back(Variant("def")); + ilist.push_back(Variant(56.789)); + encode(ilist, out); + sender.send(out); + Receiver receiver = fix.session.createReceiver(fix.queue); + Message in = receiver.fetch(5 * Duration::SECOND); + Variant::List view; + decode(in, view); + BOOST_CHECK_EQUAL(view.size(), ilist.size()); + BOOST_CHECK_EQUAL(view.front().asString(), "abc"); + BOOST_CHECK_EQUAL(view.back().asDouble(), 56.789); + + Variant::List::const_iterator i = view.begin(); + BOOST_CHECK(i != view.end()); + BOOST_CHECK_EQUAL(i->asString(), "abc"); + BOOST_CHECK(++i != view.end()); + BOOST_CHECK_EQUAL(i->asInt64(), 1234); + BOOST_CHECK(++i != view.end()); + BOOST_CHECK_EQUAL(i->asString(), "def"); + BOOST_CHECK(++i != view.end()); + BOOST_CHECK_EQUAL(i->asDouble(), 56.789); + BOOST_CHECK(++i == view.end()); + + fix.session.acknowledge(); +} + +QPID_AUTO_TEST_CASE(testReject) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + Message m1("reject-me"); + sender.send(m1); + Message m2("accept-me"); + sender.send(m2); + Receiver receiver = fix.session.createReceiver(fix.queue); + Message in = receiver.fetch(5 * Duration::SECOND); + BOOST_CHECK_EQUAL(in.getContent(), m1.getContent()); + fix.session.reject(in); + in = receiver.fetch(5 * Duration::SECOND); + BOOST_CHECK_EQUAL(in.getContent(), m2.getContent()); + fix.session.acknowledge(); +} + +QPID_AUTO_TEST_CASE(testAvailable) +{ + MultiQueueFixture fix; + + Receiver r1 = fix.session.createReceiver(fix.queues[0]); + r1.setCapacity(100); + + Receiver r2 = fix.session.createReceiver(fix.queues[1]); + r2.setCapacity(100); + + Sender s1 = fix.session.createSender(fix.queues[0]); + Sender s2 = fix.session.createSender(fix.queues[1]); + + for (uint i = 0; i < 10; ++i) { + s1.send(Message((boost::format("A_%1%") % (i+1)).str())); + } + for (uint i = 0; i < 5; ++i) { + s2.send(Message((boost::format("B_%1%") % (i+1)).str())); + } + qpid::sys::sleep(1);//is there any avoid an arbitrary sleep while waiting for messages to be dispatched? + for (uint i = 0; i < 5; ++i) { + BOOST_CHECK_EQUAL(fix.session.getReceivable(), 15u - 2*i); + BOOST_CHECK_EQUAL(r1.getAvailable(), 10u - i); + BOOST_CHECK_EQUAL(r1.fetch().getContent(), (boost::format("A_%1%") % (i+1)).str()); + BOOST_CHECK_EQUAL(r2.getAvailable(), 5u - i); + BOOST_CHECK_EQUAL(r2.fetch().getContent(), (boost::format("B_%1%") % (i+1)).str()); + fix.session.acknowledge(); + } + for (uint i = 5; i < 10; ++i) { + BOOST_CHECK_EQUAL(fix.session.getReceivable(), 10u - i); + BOOST_CHECK_EQUAL(r1.getAvailable(), 10u - i); + BOOST_CHECK_EQUAL(r1.fetch().getContent(), (boost::format("A_%1%") % (i+1)).str()); + } +} + +QPID_AUTO_TEST_CASE(testUnsettledAcks) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + for (uint i = 0; i < 10; ++i) { + sender.send(Message((boost::format("Message_%1%") % (i+1)).str())); + } + Receiver receiver = fix.session.createReceiver(fix.queue); + for (uint i = 0; i < 10; ++i) { + BOOST_CHECK_EQUAL(receiver.fetch().getContent(), (boost::format("Message_%1%") % (i+1)).str()); + } + BOOST_CHECK_EQUAL(fix.session.getUnsettledAcks(), 0u); + fix.session.acknowledge(); + BOOST_CHECK_EQUAL(fix.session.getUnsettledAcks(), 10u); + fix.session.sync(); + BOOST_CHECK_EQUAL(fix.session.getUnsettledAcks(), 0u); +} + +QPID_AUTO_TEST_CASE(testUnsettledSend) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + send(sender, 10); + //Note: this test relies on 'inside knowledge' of the sender + //implementation and the fact that the simple test case makes it + //possible to predict when completion information will be sent to + //the client. TODO: is there a better way of testing this? + BOOST_CHECK_EQUAL(sender.getUnsettled(), 10u); + fix.session.sync(); + BOOST_CHECK_EQUAL(sender.getUnsettled(), 0u); + + Receiver receiver = fix.session.createReceiver(fix.queue); + receive(receiver, 10); + fix.session.acknowledge(); +} + +QPID_AUTO_TEST_CASE(testBrowse) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + send(sender, 10); + Receiver browser1 = fix.session.createReceiver(fix.queue + "; {mode:browse}"); + receive(browser1, 10); + Receiver browser2 = fix.session.createReceiver(fix.queue + "; {mode:browse}"); + receive(browser2, 10); + Receiver releaser1 = fix.session.createReceiver(fix.queue); + Message m1 = releaser1.fetch(messaging::Duration::SECOND*5); + BOOST_CHECK(!m1.getRedelivered()); + fix.session.release(m1); + Receiver releaser2 = fix.session.createReceiver(fix.queue); + Message m2 = releaser2.fetch(messaging::Duration::SECOND*5); + BOOST_CHECK(m2.getRedelivered()); + fix.session.release(m2); + Receiver consumer = fix.session.createReceiver(fix.queue); + receive(consumer, 10); + fix.session.acknowledge(); +} + +struct QueueCreatePolicyFixture : public MessagingFixture +{ + qpid::messaging::Address address; + + QueueCreatePolicyFixture(const std::string& a) : address(a) {} + + void test() + { + ping(address); + BOOST_CHECK(admin.checkQueueExists(address.getName())); + } + + ~QueueCreatePolicyFixture() + { + admin.deleteQueue(address.getName()); + } +}; + +QPID_AUTO_TEST_CASE(testCreatePolicyQueueAlways) +{ + QueueCreatePolicyFixture fix("#; {create:always, node:{type:queue}}"); + fix.test(); +} + +QPID_AUTO_TEST_CASE(testCreatePolicyQueueReceiver) +{ + QueueCreatePolicyFixture fix("#; {create:receiver, node:{type:queue}}"); + Receiver r = fix.session.createReceiver(fix.address); + fix.test(); + r.close(); +} + +QPID_AUTO_TEST_CASE(testCreatePolicyQueueSender) +{ + QueueCreatePolicyFixture fix("#; {create:sender, node:{type:queue}}"); + Sender s = fix.session.createSender(fix.address); + fix.test(); + s.close(); +} + +struct ExchangeCreatePolicyFixture : public MessagingFixture +{ + qpid::messaging::Address address; + const std::string exchangeType; + + ExchangeCreatePolicyFixture(const std::string& a, const std::string& t) : + address(a), exchangeType(t) {} + + void test() + { + ping(address); + std::string actualType; + BOOST_CHECK(admin.checkExchangeExists(address.getName(), actualType)); + BOOST_CHECK_EQUAL(exchangeType, actualType); + } + + ~ExchangeCreatePolicyFixture() + { + admin.deleteExchange(address.getName()); + } +}; + +QPID_AUTO_TEST_CASE(testCreatePolicyTopic) +{ + ExchangeCreatePolicyFixture fix("#; {create:always, node:{type:topic}}", + "topic"); + fix.test(); +} + +QPID_AUTO_TEST_CASE(testCreatePolicyTopicReceiverFanout) +{ + ExchangeCreatePolicyFixture fix("#/my-subject; {create:receiver, node:{type:topic, x-declare:{type:fanout}}}", "fanout"); + Receiver r = fix.session.createReceiver(fix.address); + fix.test(); + r.close(); +} + +QPID_AUTO_TEST_CASE(testCreatePolicyTopicSenderDirect) +{ + ExchangeCreatePolicyFixture fix("#/my-subject; {create:sender, node:{type:topic, x-declare:{type:direct}}}", "direct"); + Sender s = fix.session.createSender(fix.address); + fix.test(); + s.close(); +} + +struct DeletePolicyFixture : public MessagingFixture +{ + enum Mode {RECEIVER, SENDER, ALWAYS, NEVER}; + + std::string getPolicy(Mode mode) + { + switch (mode) { + case SENDER: + return "{delete:sender}"; + case RECEIVER: + return "{delete:receiver}"; + case ALWAYS: + return "{delete:always}"; + case NEVER: + return "{delete:never}"; + } + return ""; + } + + void testAll() + { + test(RECEIVER); + test(SENDER); + test(ALWAYS); + test(NEVER); + } + + virtual ~DeletePolicyFixture() {} + virtual void create(const qpid::messaging::Address&) = 0; + virtual void destroy(const qpid::messaging::Address&) = 0; + virtual bool exists(const qpid::messaging::Address&) = 0; + + void test(Mode mode) + { + qpid::messaging::Address address("testqueue#; " + getPolicy(mode)); + create(address); + + Sender s = session.createSender(address); + Receiver r = session.createReceiver(address); + switch (mode) { + case RECEIVER: + s.close(); + BOOST_CHECK(exists(address)); + r.close(); + BOOST_CHECK(!exists(address)); + break; + case SENDER: + r.close(); + BOOST_CHECK(exists(address)); + s.close(); + BOOST_CHECK(!exists(address)); + break; + case ALWAYS: + s.close(); + BOOST_CHECK(!exists(address)); + break; + case NEVER: + r.close(); + BOOST_CHECK(exists(address)); + s.close(); + BOOST_CHECK(exists(address)); + destroy(address); + } + } +}; + +struct QueueDeletePolicyFixture : DeletePolicyFixture +{ + void create(const qpid::messaging::Address& address) + { + admin.createQueue(address.getName()); + } + void destroy(const qpid::messaging::Address& address) + { + admin.deleteQueue(address.getName()); + } + bool exists(const qpid::messaging::Address& address) + { + return admin.checkQueueExists(address.getName()); + } +}; + +struct ExchangeDeletePolicyFixture : DeletePolicyFixture +{ + const std::string exchangeType; + ExchangeDeletePolicyFixture(const std::string type = "topic") : exchangeType(type) {} + + void create(const qpid::messaging::Address& address) + { + admin.createExchange(address.getName(), exchangeType); + } + void destroy(const qpid::messaging::Address& address) + { + admin.deleteExchange(address.getName()); + } + bool exists(const qpid::messaging::Address& address) + { + std::string actualType; + return admin.checkExchangeExists(address.getName(), actualType) && actualType == exchangeType; + } +}; + +QPID_AUTO_TEST_CASE(testDeletePolicyQueue) +{ + QueueDeletePolicyFixture fix; + fix.testAll(); +} + +QPID_AUTO_TEST_CASE(testDeletePolicyExchange) +{ + ExchangeDeletePolicyFixture fix; + fix.testAll(); +} + +QPID_AUTO_TEST_CASE(testAssertPolicyQueue) +{ + MessagingFixture fix; + std::string a1 = "q; {create:always, assert:always, node:{type:queue, durable:false, x-declare:{arguments:{qpid.max-count:100}}}}"; + Sender s1 = fix.session.createSender(a1); + s1.close(); + Receiver r1 = fix.session.createReceiver(a1); + r1.close(); + + std::string a2 = "q; {assert:receiver, node:{durable:true, x-declare:{arguments:{qpid.max-count:100}}}}"; + Sender s2 = fix.session.createSender(a2); + s2.close(); + BOOST_CHECK_THROW(fix.session.createReceiver(a2), qpid::messaging::AssertionFailed); + + std::string a3 = "q; {assert:sender, node:{x-declare:{arguments:{qpid.max-count:99}}}}"; + BOOST_CHECK_THROW(fix.session.createSender(a3), qpid::messaging::AssertionFailed); + Receiver r3 = fix.session.createReceiver(a3); + r3.close(); + + fix.admin.deleteQueue("q"); +} + +QPID_AUTO_TEST_CASE(testAssertExchangeOption) +{ + MessagingFixture fix; + std::string a1 = "e; {create:always, assert:always, node:{type:topic, x-declare:{type:direct, arguments:{qpid.msg_sequence:True}}}}"; + Sender s1 = fix.session.createSender(a1); + s1.close(); + Receiver r1 = fix.session.createReceiver(a1); + r1.close(); + + std::string a2 = "e; {assert:receiver, node:{type:topic, x-declare:{type:fanout, arguments:{qpid.msg_sequence:True}}}}"; + Sender s2 = fix.session.createSender(a2); + s2.close(); + BOOST_CHECK_THROW(fix.session.createReceiver(a2), qpid::messaging::AssertionFailed); + + std::string a3 = "e; {assert:sender, node:{x-declare:{arguments:{qpid.msg_sequence:False}}}}"; + BOOST_CHECK_THROW(fix.session.createSender(a3), qpid::messaging::AssertionFailed); + Receiver r3 = fix.session.createReceiver(a3); + r3.close(); + + fix.admin.deleteExchange("e"); +} + +QPID_AUTO_TEST_CASE(testGetSender) +{ + QueueFixture fix; + std::string name = fix.session.createSender(fix.queue).getName(); + Sender sender = fix.session.getSender(name); + BOOST_CHECK_EQUAL(name, sender.getName()); + Message out(Uuid(true).str()); + sender.send(out); + Message in; + BOOST_CHECK(fix.session.createReceiver(fix.queue).fetch(in)); + BOOST_CHECK_EQUAL(out.getContent(), in.getContent()); + BOOST_CHECK_THROW(fix.session.getSender("UnknownSender"), qpid::messaging::KeyError); +} + +QPID_AUTO_TEST_CASE(testGetReceiver) +{ + QueueFixture fix; + std::string name = fix.session.createReceiver(fix.queue).getName(); + Receiver receiver = fix.session.getReceiver(name); + BOOST_CHECK_EQUAL(name, receiver.getName()); + Message out(Uuid(true).str()); + fix.session.createSender(fix.queue).send(out); + Message in; + BOOST_CHECK(receiver.fetch(in)); + BOOST_CHECK_EQUAL(out.getContent(), in.getContent()); + BOOST_CHECK_THROW(fix.session.getReceiver("UnknownReceiver"), qpid::messaging::KeyError); +} + +QPID_AUTO_TEST_CASE(testGetSessionFromConnection) +{ + QueueFixture fix; + fix.connection.createSession("my-session"); + Session session = fix.connection.getSession("my-session"); + Message out(Uuid(true).str()); + session.createSender(fix.queue).send(out); + Message in; + BOOST_CHECK(session.createReceiver(fix.queue).fetch(in)); + BOOST_CHECK_EQUAL(out.getContent(), in.getContent()); + BOOST_CHECK_THROW(fix.connection.getSession("UnknownSession"), qpid::messaging::KeyError); +} + +QPID_AUTO_TEST_CASE(testGetConnectionFromSession) +{ + QueueFixture fix; + Message out(Uuid(true).str()); + Sender sender = fix.session.createSender(fix.queue); + sender.send(out); + Message in; + sender.getSession().getConnection().createSession("incoming"); + BOOST_CHECK(fix.connection.getSession("incoming").createReceiver(fix.queue).fetch(in)); + BOOST_CHECK_EQUAL(out.getContent(), in.getContent()); +} + +QPID_AUTO_TEST_CASE(testTx) +{ + QueueFixture fix; + Session ssn1 = fix.connection.createTransactionalSession(); + Session ssn2 = fix.connection.createTransactionalSession(); + Sender sender1 = ssn1.createSender(fix.queue); + Sender sender2 = ssn2.createSender(fix.queue); + Receiver receiver1 = ssn1.createReceiver(fix.queue); + Receiver receiver2 = ssn2.createReceiver(fix.queue); + Message in; + + send(sender1, 5, 1, "A"); + send(sender2, 5, 1, "B"); + ssn2.commit(); + receive(receiver1, 5, 1, "B");//(only those from sender2 should be received) + BOOST_CHECK(!receiver1.fetch(in, Duration::IMMEDIATE));//check there are no more messages + ssn1.rollback(); + receive(receiver2, 5, 1, "B"); + BOOST_CHECK(!receiver2.fetch(in, Duration::IMMEDIATE));//check there are no more messages + ssn2.rollback(); + receive(receiver1, 5, 1, "B"); + BOOST_CHECK(!receiver1.fetch(in, Duration::IMMEDIATE));//check there are no more messages + ssn1.commit(); + //check neither receiver gets any more messages: + BOOST_CHECK(!receiver1.fetch(in, Duration::IMMEDIATE)); + BOOST_CHECK(!receiver2.fetch(in, Duration::IMMEDIATE)); +} + +QPID_AUTO_TEST_CASE(testRelease) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + Message out("test-message"); + sender.send(out, true); + Receiver receiver = fix.session.createReceiver(fix.queue); + Message m1 = receiver.fetch(Duration::IMMEDIATE); + fix.session.release(m1); + Message m2 = receiver.fetch(Duration::SECOND * 1); + BOOST_CHECK_EQUAL(m1.getContent(), out.getContent()); + BOOST_CHECK_EQUAL(m1.getContent(), m2.getContent()); + BOOST_CHECK(m2.getRedelivered()); + fix.session.acknowledge(true); +} + +QPID_AUTO_TEST_CASE(testOptionVerification) +{ + MessagingFixture fix; + fix.session.createReceiver("my-queue; {create: always, assert: always, delete: always, node: {type: queue, durable: false, x-declare: {arguments: {a: b}}, x-bindings: [{exchange: amq.fanout}]}, link: {name: abc, durable: false, reliability: exactly-once, x-subscribe: {arguments:{a:b}}, x-bindings:[{exchange: amq.fanout}]}, mode: browse}"); + BOOST_CHECK_THROW(fix.session.createReceiver("my-queue; {invalid-option:blah}"), qpid::messaging::AddressError); +} + +QPID_AUTO_TEST_CASE(testReceiveSpecialProperties) +{ + QueueFixture fix; + + qpid::client::Message out; + out.getDeliveryProperties().setRoutingKey(fix.queue); + out.getMessageProperties().setAppId("my-app-id"); + out.getMessageProperties().setMessageId(qpid::framing::Uuid(true)); + out.getMessageProperties().setContentEncoding("my-content-encoding"); + fix.admin.send(out); + + Receiver receiver = fix.session.createReceiver(fix.queue); + Message in = receiver.fetch(Duration::SECOND * 5); + BOOST_CHECK_EQUAL(in.getProperties()["x-amqp-0-10.routing-key"].asString(), out.getDeliveryProperties().getRoutingKey()); + BOOST_CHECK_EQUAL(in.getProperties()["x-amqp-0-10.app-id"].asString(), out.getMessageProperties().getAppId()); + BOOST_CHECK_EQUAL(in.getProperties()["x-amqp-0-10.content-encoding"].asString(), out.getMessageProperties().getContentEncoding()); + BOOST_CHECK_EQUAL(in.getMessageId(), out.getMessageProperties().getMessageId().str()); + fix.session.acknowledge(true); +} + +QPID_AUTO_TEST_CASE(testSendSpecialProperties) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + Message out("test-message"); + std::string appId = "my-app-id"; + std::string contentEncoding = "my-content-encoding"; + out.getProperties()["x-amqp-0-10.app-id"] = appId; + out.getProperties()["x-amqp-0-10.content-encoding"] = contentEncoding; + out.setMessageId(qpid::framing::Uuid(true).str()); + sender.send(out, true); + + qpid::client::LocalQueue q; + qpid::client::SubscriptionManager subs(fix.admin.session); + qpid::client::Subscription s = subs.subscribe(q, fix.queue); + qpid::client::Message in = q.get(); + s.cancel(); + fix.admin.session.sync(); + + BOOST_CHECK_EQUAL(in.getMessageProperties().getAppId(), appId); + BOOST_CHECK_EQUAL(in.getMessageProperties().getContentEncoding(), contentEncoding); + BOOST_CHECK_EQUAL(in.getMessageProperties().getMessageId().str(), out.getMessageId()); +} + +QPID_AUTO_TEST_CASE(testExclusiveSubscriber) +{ + QueueFixture fix; + std::string address = (boost::format("%1%; { link: { x-subscribe : { exclusive:true } } }") % fix.queue).str(); + Receiver receiver = fix.session.createReceiver(address); + ScopedSuppressLogging sl; + try { + fix.session.createReceiver(address); + fix.session.sync(); + BOOST_FAIL("Expected exception."); + } catch (const MessagingException& /*e*/) {} +} + + +QPID_AUTO_TEST_CASE(testExclusiveQueueSubscriberAndBrowser) +{ + MessagingFixture fix; + + std::string address = "exclusive-queue; { create: receiver, node : { x-declare : { auto-delete: true, exclusive: true } } }"; + std::string browseAddress = "exclusive-queue; { mode: browse }"; + + Receiver receiver = fix.session.createReceiver(address); + fix.session.sync(); + + Connection c2 = fix.newConnection(); + c2.open(); + Session s2 = c2.createSession(); + + BOOST_CHECK_NO_THROW(Receiver browser = s2.createReceiver(browseAddress)); + c2.close(); +} + + +QPID_AUTO_TEST_CASE(testDeleteQueueWithUnackedMessages) +{ + MessagingFixture fix; + const uint capacity = 5; + + Sender sender = fix.session.createSender("test.ex;{create:always,node:{type:topic}}"); + Receiver receiver2 = fix.session.createReceiver("alternate.ex;{create:always,node:{type:topic}}"); + Receiver receiver1 = fix.session.createReceiver("test.q;{create:always, delete:always,node:{type:queue, x-declare:{alternate-exchange:alternate.ex}},link:{x-bindings:[{exchange:test.ex,queue:test.q,key:#}]}}"); + + receiver1.setCapacity(capacity); + receiver2.setCapacity(capacity*2); + + Message out("test-message"); + for (uint i = 0; i < capacity*2; ++i) { + sender.send(out); + } + + receiver1.close(); + + // Make sure all pending messages were sent to the alternate + // exchange when the queue was deleted. + Message in; + for (uint i = 0; i < capacity*2; ++i) { + in = receiver2.fetch(Duration::SECOND * 5); + BOOST_CHECK_EQUAL(in.getContent(), out.getContent()); + } +} + +QPID_AUTO_TEST_CASE(testAuthenticatedUsername) +{ + MessagingFixture fix; + Connection connection = fix.newConnection(); + connection.setOption("sasl-mechanism", "PLAIN"); + connection.setOption("username", "test-user"); + connection.setOption("password", "ignored"); + connection.open(); + BOOST_CHECK_EQUAL(connection.getAuthenticatedUsername(), std::string("test-user")); +} + +QPID_AUTO_TEST_CASE(testExceptionOnClosedConnection) +{ + MessagingFixture fix; + fix.connection.close(); + BOOST_CHECK_THROW(fix.connection.createSession(), MessagingException); + Connection connection("blah"); + BOOST_CHECK_THROW(connection.createSession(), MessagingException); +} + +QPID_AUTO_TEST_CASE(testAcknowledge) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + const uint count(20); + for (uint i = 0; i < count; ++i) { + sender.send(Message((boost::format("Message_%1%") % (i+1)).str())); + } + + Session other = fix.connection.createSession(); + Receiver receiver = other.createReceiver(fix.queue); + std::vector<Message> messages; + for (uint i = 0; i < count; ++i) { + Message msg = receiver.fetch(); + BOOST_CHECK_EQUAL(msg.getContent(), (boost::format("Message_%1%") % (i+1)).str()); + messages.push_back(msg); + } + const uint batch(10); //acknowledge first 10 messages only + for (uint i = 0; i < batch; ++i) { + other.acknowledge(messages[i]); + } + messages.clear(); + other.sync(); + other.close(); + + other = fix.connection.createSession(); + receiver = other.createReceiver(fix.queue); + for (uint i = 0; i < (count-batch); ++i) { + Message msg = receiver.fetch(); + BOOST_CHECK_EQUAL(msg.getContent(), (boost::format("Message_%1%") % (i+1+batch)).str()); + if (i % 2) other.acknowledge(msg); //acknowledge every other message + } + other.sync(); + other.close(); + + //check unacknowledged messages are still enqueued + other = fix.connection.createSession(); + receiver = other.createReceiver(fix.queue); + for (uint i = 0; i < ((count-batch)/2); ++i) { + Message msg = receiver.fetch(); + BOOST_CHECK_EQUAL(msg.getContent(), (boost::format("Message_%1%") % ((i*2)+1+batch)).str()); + } + other.acknowledge();//acknowledge all messages + other.sync(); + other.close(); + + Message m; + //check queue is empty + BOOST_CHECK(!fix.session.createReceiver(fix.queue).fetch(m, Duration::IMMEDIATE)); +} + +QPID_AUTO_TEST_CASE(testQmfCreateAndDelete) +{ + MessagingFixture fix(BrokerOptions(), true/*enable management*/); + MethodInvoker control(fix.session); + control.createQueue("my-queue"); + control.createExchange("my-exchange", "topic"); + control.bind("my-exchange", "my-queue", "subject1"); + + Sender sender = fix.session.createSender("my-exchange"); + Receiver receiver = fix.session.createReceiver("my-queue"); + Message out; + out.setSubject("subject1"); + out.setContent("one"); + sender.send(out); + Message in; + BOOST_CHECK(receiver.fetch(in, Duration::SECOND*5)); + BOOST_CHECK_EQUAL(out.getContent(), in.getContent()); + control.unbind("my-exchange", "my-queue", "subject1"); + control.bind("my-exchange", "my-queue", "subject2"); + + out.setContent("two"); + sender.send(out);//should be dropped + + out.setSubject("subject2"); + out.setContent("three"); + sender.send(out);//should not be dropped + + BOOST_CHECK(receiver.fetch(in, Duration::SECOND*5)); + BOOST_CHECK_EQUAL(out.getContent(), in.getContent()); + BOOST_CHECK(!receiver.fetch(in, Duration::IMMEDIATE)); + sender.close(); + receiver.close(); + + control.deleteExchange("my-exchange"); + messaging::Session other = fix.connection.createSession(); + { + ScopedSuppressLogging sl; + BOOST_CHECK_THROW(other.createSender("my-exchange"), qpid::messaging::NotFound); + } + control.deleteQueue("my-queue"); + other = fix.connection.createSession(); + { + ScopedSuppressLogging sl; + BOOST_CHECK_THROW(other.createReceiver("my-queue"), qpid::messaging::NotFound); + } +} + +QPID_AUTO_TEST_CASE(testRejectAndCredit) +{ + //Ensure credit is restored on completing rejected messages + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + Receiver receiver = fix.session.createReceiver(fix.queue); + + const uint count(10); + receiver.setCapacity(count); + for (uint i = 0; i < count; i++) { + sender.send(Message((boost::format("Message_%1%") % (i+1)).str())); + } + + Message in; + for (uint i = 0; i < count; ++i) { + if (receiver.fetch(in, Duration::SECOND)) { + BOOST_CHECK_EQUAL(in.getContent(), (boost::format("Message_%1%") % (i+1)).str()); + fix.session.reject(in); + } else { + BOOST_FAIL((boost::format("Message_%1% not received as expected") % (i+1)).str()); + break; + } + } + //send another batch of messages + for (uint i = 0; i < count; i++) { + sender.send(Message((boost::format("Message_%1%") % (i+count)).str())); + } + + for (uint i = 0; i < count; ++i) { + if (receiver.fetch(in, Duration::SECOND)) { + BOOST_CHECK_EQUAL(in.getContent(), (boost::format("Message_%1%") % (i+count)).str()); + } else { + BOOST_FAIL((boost::format("Message_%1% not received as expected") % (i+count)).str()); + break; + } + } + fix.session.acknowledge(); + receiver.close(); + sender.close(); +} + +QPID_AUTO_TEST_CASE(testTtlForever) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + Message out("I want to live forever!"); + out.setTtl(Duration::FOREVER); + sender.send(out, true); + Receiver receiver = fix.session.createReceiver(fix.queue); + Message in = receiver.fetch(Duration::IMMEDIATE); + fix.session.acknowledge(); + BOOST_CHECK_EQUAL(in.getContent(), out.getContent()); + BOOST_CHECK(in.getTtl() == Duration::FOREVER); +} + +QPID_AUTO_TEST_CASE(testExclusiveTopicSubscriber) +{ + TopicFixture fix; + std::string address = (boost::format("%1%; { link: { name: 'my-subscription', x-declare: { auto-delete: true, exclusive: true }}}") % fix.topic).str(); + Sender sender = fix.session.createSender(fix.topic); + Receiver receiver1 = fix.session.createReceiver(address); + { + ScopedSuppressLogging sl; + try { + fix.session.createReceiver(address); + fix.session.sync(); + BOOST_FAIL("Expected exception."); + } catch (const MessagingException& /*e*/) {} + } +} + +QPID_AUTO_TEST_CASE(testNonExclusiveSubscriber) +{ + TopicFixture fix; + std::string address = (boost::format("%1%; {node:{type:topic}, link:{name:'my-subscription', x-declare:{auto-delete:true, exclusive:false}}}") % fix.topic).str(); + Receiver receiver1 = fix.session.createReceiver(address); + Receiver receiver2 = fix.session.createReceiver(address); + Sender sender = fix.session.createSender(fix.topic); + sender.send(Message("one"), true); + Message in = receiver1.fetch(Duration::IMMEDIATE); + BOOST_CHECK_EQUAL(in.getContent(), std::string("one")); + sender.send(Message("two"), true); + in = receiver2.fetch(Duration::IMMEDIATE); + BOOST_CHECK_EQUAL(in.getContent(), std::string("two")); + fix.session.acknowledge(); +} + +QPID_AUTO_TEST_CASE(testAcknowledgeUpTo) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + const uint count(20); + for (uint i = 0; i < count; ++i) { + sender.send(Message((boost::format("Message_%1%") % (i+1)).str())); + } + + Session other = fix.connection.createSession(); + Receiver receiver = other.createReceiver(fix.queue); + std::vector<Message> messages; + for (uint i = 0; i < count; ++i) { + Message msg = receiver.fetch(); + BOOST_CHECK_EQUAL(msg.getContent(), (boost::format("Message_%1%") % (i+1)).str()); + messages.push_back(msg); + } + const uint batch = 10; + other.acknowledgeUpTo(messages[batch-1]);//acknowledge first 10 messages only + + messages.clear(); + other.sync(); + other.close(); + + other = fix.connection.createSession(); + receiver = other.createReceiver(fix.queue); + Message msg; + for (uint i = 0; i < (count-batch); ++i) { + msg = receiver.fetch(); + BOOST_CHECK_EQUAL(msg.getContent(), (boost::format("Message_%1%") % (i+1+batch)).str()); + } + other.acknowledgeUpTo(msg); + other.sync(); + other.close(); + + Message m; + //check queue is empty + BOOST_CHECK(!fix.session.createReceiver(fix.queue).fetch(m, Duration::IMMEDIATE)); +} + +QPID_AUTO_TEST_CASE(testCreateBindingsOnStandardExchange) +{ + QueueFixture fix; + Sender sender = fix.session.createSender((boost::format("amq.direct; {create:always, node:{type:topic, x-bindings:[{queue:%1%, key:my-subject}]}}") % fix.queue).str()); + Message out("test-message"); + out.setSubject("my-subject"); + sender.send(out); + Receiver receiver = fix.session.createReceiver(fix.queue); + Message in = receiver.fetch(Duration::SECOND * 5); + fix.session.acknowledge(); + BOOST_CHECK_EQUAL(in.getContent(), out.getContent()); + BOOST_CHECK_EQUAL(in.getSubject(), out.getSubject()); +} + +QPID_AUTO_TEST_CASE(testUnsubscribeOnClose) +{ + MessagingFixture fix; + Sender sender = fix.session.createSender("my-exchange/my-subject; {create: always, delete:sender, node:{type:topic, x-declare:{alternate-exchange:amq.fanout}}}"); + Receiver receiver = fix.session.createReceiver("my-exchange/my-subject"); + Receiver deadletters = fix.session.createReceiver("amq.fanout"); + + sender.send(Message("first")); + Message in = receiver.fetch(Duration::SECOND); + BOOST_CHECK_EQUAL(in.getContent(), std::string("first")); + fix.session.acknowledge(); + receiver.close(); + sender.send(Message("second")); + in = deadletters.fetch(Duration::SECOND); + BOOST_CHECK_EQUAL(in.getContent(), std::string("second")); + fix.session.acknowledge(); +} + +QPID_AUTO_TEST_CASE(testHeadersExchange) +{ + MessagingFixture fix; + //use both quoted and unquoted values + Receiver receiver = fix.session.createReceiver("amq.match; {link:{x-bindings:[{arguments:{x-match:all,qpid.subject:'abc',my-property:abc}}]}}"); + Sender sender = fix.session.createSender("amq.match"); + Message out("test-message"); + out.setSubject("abc"); + Variant& property = out.getProperties()["my-property"]; + property = "abc"; + property.setEncoding("utf8"); + sender.send(out, true); + Message in; + if (receiver.fetch(in, Duration::SECOND)) { + fix.session.acknowledge(); + BOOST_CHECK_EQUAL(in.getContent(), out.getContent()); + } else { + BOOST_FAIL("Message did not match as expected!"); + } +} + +QPID_AUTO_TEST_CASE(testLargeRoutingKey) +{ + MessagingFixture fix; + std::string address = "amq.direct/" + std::string(300, 'x');//routing/binding key can be at most 225 chars in 0-10 + BOOST_CHECK_THROW(fix.session.createReceiver(address), qpid::messaging::MessagingException); +} + +QPID_AUTO_TEST_CASE(testAlternateExchangeInLinkDeclare) +{ + MessagingFixture fix; + Sender s = fix.session.createSender("amq.direct/key"); + Receiver r1 = fix.session.createReceiver("amq.direct/key;{link:{x-declare:{alternate-exchange:'amq.fanout'}}}"); + Receiver r2 = fix.session.createReceiver("amq.fanout"); + + for (uint i = 0; i < 10; ++i) { + s.send(Message((boost::format("Message_%1%") % (i+1)).str()), true); + } + r1.close();//orphans all messages in subscription queue, which should then be routed through alternate exchange + for (uint i = 0; i < 10; ++i) { + Message received; + BOOST_CHECK(r2.fetch(received, Duration::SECOND)); + BOOST_CHECK_EQUAL(received.getContent(), (boost::format("Message_%1%") % (i+1)).str()); + } +} + +QPID_AUTO_TEST_CASE(testBrowseOnly) +{ + /* Set up a queue browse-only, and try to receive + the same messages twice with two different receivers. + This works because the browse-only queue does not + allow message acquisition. */ + + QueueFixture fix; + std::string addr = "q; {create:always, node:{type:queue, durable:false, x-declare:{arguments:{qpid.browse-only:1}}}}"; + Sender sender = fix.session.createSender(addr); + Message out("test-message"); + + int count = 10; + for ( int i = 0; i < count; ++ i ) { + sender.send(out); + } + + Message m; + + Receiver receiver_1 = fix.session.createReceiver(addr); + for ( int i = 0; i < count; ++ i ) { + BOOST_CHECK(receiver_1.fetch(m, Duration::SECOND)); + } + + Receiver receiver_2 = fix.session.createReceiver(addr); + for ( int i = 0; i < count; ++ i ) { + BOOST_CHECK(receiver_2.fetch(m, Duration::SECOND)); + } + + fix.session.acknowledge(); +} + +QPID_AUTO_TEST_CASE(testLinkBindingCleanup) +{ + MessagingFixture fix; + + Sender sender = fix.session.createSender("test.ex;{create:always,node:{type:topic}}"); + + Connection connection = fix.newConnection(); + connection.open(); + + Session session(connection.createSession()); + Receiver receiver1 = session.createReceiver("test.q;{create:always, node:{type:queue, x-bindings:[{exchange:test.ex,queue:test.q,key:#,arguments:{x-scope:session}}]}}"); + Receiver receiver2 = fix.session.createReceiver("test.q;{create:never, delete:always}"); + connection.close(); + + sender.send(Message("test-message"), true); + + // The session-scoped binding should be removed when receiver1's network connection is lost + Message in; + BOOST_CHECK(!receiver2.fetch(in, Duration::IMMEDIATE)); +} + +namespace { +struct Fetcher : public qpid::sys::Runnable { + Receiver receiver; + Message message; + bool result; + qpid::messaging::Duration timeout; + bool timedOut; + + Fetcher(Receiver r) : receiver(r), result(false), timeout(Duration::SECOND*10), timedOut(false) {} + void run() + { + qpid::sys::AbsTime start(qpid::sys::now()); + try { + result = receiver.fetch(message, timeout); + } catch (const MessagingException&) {} + qpid::sys::Duration timeTaken(start, qpid::sys::now()); + timedOut = (uint64_t) timeTaken >= timeout.getMilliseconds() * qpid::sys::TIME_MSEC; + } +}; +} + +QPID_AUTO_TEST_CASE(testConcurrentFetch) +{ + MessagingFixture fix; + Sender sender = fix.session.createSender("my-test-queue;{create:always, node : { x-declare : { auto-delete: true}}}"); + Receiver receiver = fix.session.createReceiver("my-test-queue"); + Fetcher fetcher(fix.session.createReceiver("amq.fanout")); + qpid::sys::Thread runner(fetcher); + Message out("test-message"); + for (int i = 0; i < 10; i++) {//try several times to make sure + sender.send(out, true); + //since the message is now on the queue, it should take less than the timeout to actually fetch it + qpid::sys::AbsTime start = qpid::sys::AbsTime::now(); + Message in; + BOOST_CHECK(receiver.fetch(in, qpid::messaging::Duration::SECOND*2)); + qpid::sys::Duration time(start, qpid::sys::AbsTime::now()); + BOOST_CHECK(time < qpid::sys::TIME_SEC*2); + if (time >= qpid::sys::TIME_SEC*2) break;//if we failed, no need to keep testing + } + fix.session.createSender("amq.fanout").send(out); + runner.join(); + BOOST_CHECK(fetcher.result); +} + +QPID_AUTO_TEST_CASE(testSimpleRequestResponse) +{ + QueueFixture fix; + //create receiver on temp queue for responses (using shorthand for temp queue) + Receiver r1 = fix.session.createReceiver("#"); + //send request + Sender s1 = fix.session.createSender(fix.queue); + Message original("test-message"); + original.setSubject("test-subject"); + original.setReplyTo(r1.getAddress()); + s1.send(original); + + //receive request and send response + Receiver r2 = fix.session.createReceiver(fix.queue); + Message m = r2.fetch(Duration::SECOND * 5); + Sender s2 = fix.session.createSender(m.getReplyTo()); + s2.send(m); + m = r1.fetch(Duration::SECOND * 5); + fix.session.acknowledge(); + BOOST_CHECK_EQUAL(m.getContent(), original.getContent()); + BOOST_CHECK_EQUAL(m.getSubject(), original.getSubject()); +} + +QPID_AUTO_TEST_CASE(testSelfDestructQueue) +{ + MessagingFixture fix; + Session other = fix.connection.createSession(); + Receiver r1 = other.createReceiver("amq.fanout; {link:{reliability:at-least-once, x-declare:{arguments:{qpid.max_count:10,qpid.policy_type:self-destruct}}}}"); + Receiver r2 = fix.session.createReceiver("amq.fanout"); + //send request + Sender s = fix.session.createSender("amq.fanout"); + for (uint i = 0; i < 20; ++i) { + s.send(Message((boost::format("MSG_%1%") % (i+1)).str())); + } + try { + ScopedSuppressLogging sl; + for (uint i = 0; i < 20; ++i) { + r1.fetch(Duration::SECOND); + } + BOOST_FAIL("Expected exception."); + } catch (const qpid::messaging::MessagingException&) { + } + + for (uint i = 0; i < 20; ++i) { + BOOST_CHECK_EQUAL(r2.fetch(Duration::SECOND).getContent(), (boost::format("MSG_%1%") % (i+1)).str()); + } +} + +QPID_AUTO_TEST_CASE(testReroutingRingQueue) +{ + MessagingFixture fix; + Receiver r1 = fix.session.createReceiver("my-queue; {create:always, node:{x-declare:{alternate-exchange:amq.fanout, auto-delete:True, arguments:{qpid.max_count:10,qpid.policy_type:ring}}}}"); + Receiver r2 = fix.session.createReceiver("amq.fanout"); + + Sender s = fix.session.createSender("my-queue"); + for (uint i = 0; i < 20; ++i) { + s.send(Message((boost::format("MSG_%1%") % (i+1)).str())); + } + for (uint i = 10; i < 20; ++i) { + BOOST_CHECK_EQUAL(r1.fetch(Duration::SECOND).getContent(), (boost::format("MSG_%1%") % (i+1)).str()); + } + for (uint i = 0; i < 10; ++i) { + BOOST_CHECK_EQUAL(r2.fetch(Duration::SECOND).getContent(), (boost::format("MSG_%1%") % (i+1)).str()); + } +} + +QPID_AUTO_TEST_CASE(testReleaseOnPriorityQueue) +{ + MessagingFixture fix; + std::string queue("queue; {create:always, node:{x-declare:{auto-delete:True, arguments:{qpid.priorities:10}}}}"); + std::string text("my message"); + Sender sender = fix.session.createSender(queue); + sender.send(Message(text)); + Receiver receiver = fix.session.createReceiver(queue); + Message msg; + for (uint i = 0; i < 10; ++i) { + if (receiver.fetch(msg, Duration::SECOND)) { + BOOST_CHECK_EQUAL(msg.getContent(), text); + fix.session.release(msg); + } else { + BOOST_FAIL("Released message not redelivered as expected."); + } + } + fix.session.acknowledge(); +} + +QPID_AUTO_TEST_CASE(testRollbackWithFullPrefetch) +{ + QueueFixture fix; + std::string first("first"); + std::string second("second"); + Sender sender = fix.session.createSender(fix.queue); + for (uint i = 0; i < 10; ++i) { + sender.send(Message((boost::format("MSG_%1%") % (i+1)).str())); + } + Session txsession = fix.connection.createTransactionalSession(); + Receiver receiver = txsession.createReceiver(fix.queue); + receiver.setCapacity(9); + Message msg; + for (uint i = 0; i < 10; ++i) { + if (receiver.fetch(msg, Duration::SECOND)) { + BOOST_CHECK_EQUAL(msg.getContent(), std::string("MSG_1")); + txsession.rollback(); + } else { + BOOST_FAIL("Released message not redelivered as expected."); + break; + } + } + txsession.acknowledge(); + txsession.commit(); +} + +QPID_AUTO_TEST_CASE(testCloseAndConcurrentFetch) +{ + QueueFixture fix; + Receiver receiver = fix.session.createReceiver(fix.queue); + Fetcher fetcher(receiver); + qpid::sys::Thread runner(fetcher); + qpid::sys::usleep(500); + receiver.close(); + runner.join(); + BOOST_CHECK(!fetcher.timedOut); +} + +QPID_AUTO_TEST_CASE(testCloseAndMultipleConcurrentFetches) +{ + QueueFixture fix; + Receiver receiver = fix.session.createReceiver(fix.queue); + Receiver receiver2 = fix.session.createReceiver("amq.fanout"); + Receiver receiver3 = fix.session.createReceiver("amq.fanout"); + Fetcher fetcher(receiver); + Fetcher fetcher2(receiver2); + Fetcher fetcher3(receiver3); + qpid::sys::Thread runner(fetcher); + qpid::sys::Thread runner2(fetcher2); + qpid::sys::Thread runner3(fetcher3); + qpid::sys::usleep(500); + receiver.close(); + Message message("Test"); + fix.session.createSender("amq.fanout").send(message); + runner2.join(); + BOOST_CHECK(fetcher2.result); + BOOST_CHECK_EQUAL(fetcher2.message.getContent(), message.getContent()); + runner3.join(); + BOOST_CHECK(fetcher3.result); + BOOST_CHECK_EQUAL(fetcher3.message.getContent(), message.getContent()); + runner.join(); + BOOST_CHECK(!fetcher.timedOut); +} + +QPID_AUTO_TEST_CASE(testSessionCheckError) +{ + MessagingFixture fix; + Session session = fix.connection.createSession(); + Sender sender = session.createSender("q; {create:always, node:{x-declare:{auto-delete:True, arguments:{qpid.max_count:1}}}}"); + ScopedSuppressLogging sl; + for (uint i = 0; i < 2; ++i) { + sender.send(Message((boost::format("A_%1%") % (i+1)).str())); + } + try { + while (true) session.checkError(); + } catch (const qpid::types::Exception&) { + //this is ok + } catch (const qpid::Exception&) { + BOOST_FAIL("Wrong exception type thrown"); + } +} + +QPID_AUTO_TEST_CASE(testImmediateNextReceiver) +{ + QueueFixture fix; + Sender sender = fix.session.createSender(fix.queue); + Message out("test message"); + sender.send(out); + fix.session.createReceiver(fix.queue).setCapacity(1); + Receiver next; + qpid::sys::AbsTime start = qpid::sys::now(); + try { + while (!fix.session.nextReceiver(next, qpid::messaging::Duration::IMMEDIATE)) { + qpid::sys::Duration running(start, qpid::sys::now()); + if (running > 5*qpid::sys::TIME_SEC) { + throw qpid::types::Exception("Timed out spinning on nextReceiver(IMMEDIATE)"); + } + qpid::sys::usleep(1); // for valgrind + } + Message in; + BOOST_CHECK(next.fetch(in, qpid::messaging::Duration::IMMEDIATE)); + BOOST_CHECK_EQUAL(in.getContent(), out.getContent()); + next.close(); + } catch (const std::exception& e) { + BOOST_FAIL(e.what()); + } +} + +QPID_AUTO_TEST_CASE(testImmediateNextReceiverNoMessage) +{ + QueueFixture fix; + Receiver r = fix.session.createReceiver(fix.queue); + r.setCapacity(1); + Receiver next; + try { + BOOST_CHECK(!fix.session.nextReceiver(next, qpid::messaging::Duration::IMMEDIATE)); + r.close(); + } catch (const std::exception& e) { + BOOST_FAIL(e.what()); + } +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/MessagingThreadTests.cpp b/qpid/cpp/src/tests/MessagingThreadTests.cpp new file mode 100644 index 0000000000..48264735b1 --- /dev/null +++ b/qpid/cpp/src/tests/MessagingThreadTests.cpp @@ -0,0 +1,144 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "MessagingFixture.h" +#include "qpid/sys/Runnable.h" +#include "qpid/sys/Thread.h" +#include <boost/lexical_cast.hpp> + +namespace qpid { +namespace tests { +QPID_AUTO_TEST_SUITE(MessagingThreadTests) + +using namespace messaging; +using namespace boost::assign; +using namespace std; + +struct ReceiveThread : public sys::Runnable { + Receiver receiver; + vector<string> received; + string error; + + ReceiveThread(Receiver s) : receiver(s) {} + void run() { + try { + while(true) { + Message m = receiver.fetch(Duration::SECOND*5); + if (m.getContent() == "END") break; + received.push_back(m.getContent()); + } + } catch (const NoMessageAvailable& e) { + // Indicates that fetch timed out OR receiver was closed by other thread. + if (!receiver.isClosed()) // timeout + error = e.what(); + } catch (const std::exception& e) { + error = e.what(); + } + } +}; + +struct NextReceiverThread : public sys::Runnable { + Session session; + vector<string> received; + string error; + + NextReceiverThread(Session s) : session(s) {} + void run() { + try { + while(true) { + Message m = session.nextReceiver(Duration::SECOND*5).fetch(); + if (m.getContent() == "END") break; + received.push_back(m.getContent()); + } + } catch (const std::exception& e) { + error = e.what(); + } + } +}; + + +QPID_AUTO_TEST_CASE(testConcurrentSendReceive) { + MessagingFixture fix; + Sender s = fix.session.createSender("concurrent;{create:always}"); + Receiver r = fix.session.createReceiver("concurrent;{create:always,link:{reliability:unreliable}}"); + ReceiveThread rt(r); + sys::Thread thread(rt); + const size_t COUNT=100; + for (size_t i = 0; i < COUNT; ++i) { + s.send(Message()); + } + s.send(Message("END")); + thread.join(); + BOOST_CHECK_EQUAL(rt.error, string()); + BOOST_CHECK_EQUAL(COUNT, rt.received.size()); +} + +QPID_AUTO_TEST_CASE(testCloseBusyReceiver) { + MessagingFixture fix; + Receiver r = fix.session.createReceiver("closeReceiver;{create:always}"); + ReceiveThread rt(r); + sys::Thread thread(rt); + sys::usleep(1000); // Give the receive thread time to block. + r.close(); + thread.join(); + BOOST_CHECK_EQUAL(rt.error, string()); + + // Fetching on closed receiver should fail. + Message m; + BOOST_CHECK(!r.fetch(m, Duration(0))); + BOOST_CHECK_THROW(r.fetch(Duration(0)), NoMessageAvailable); +} + +QPID_AUTO_TEST_CASE(testCloseSessionBusyReceiver) { + MessagingFixture fix; + Receiver r = fix.session.createReceiver("closeSession;{create:always}"); + ReceiveThread rt(r); + sys::Thread thread(rt); + sys::usleep(1000); // Give the receive thread time to block. + fix.session.close(); + thread.join(); + BOOST_CHECK_EQUAL(rt.error, string()); + + // Fetching on closed receiver should fail. + Message m; + BOOST_CHECK(!r.fetch(m, Duration(0))); + BOOST_CHECK_THROW(r.fetch(Duration(0)), NoMessageAvailable); +} + +QPID_AUTO_TEST_CASE(testConcurrentSendNextReceiver) { + MessagingFixture fix; + Receiver r = fix.session.createReceiver("concurrent;{create:always,link:{reliability:unreliable}}"); + const size_t COUNT=100; + r.setCapacity(COUNT); + NextReceiverThread rt(fix.session); + sys::Thread thread(rt); + sys::usleep(1000); // Give the receive thread time to block. + Sender s = fix.session.createSender("concurrent;{create:always}"); + for (size_t i = 0; i < COUNT; ++i) { + s.send(Message()); + } + s.send(Message("END")); + thread.join(); + BOOST_CHECK_EQUAL(rt.error, string()); + BOOST_CHECK_EQUAL(COUNT, rt.received.size()); +} + +QPID_AUTO_TEST_SUITE_END() +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/PollableCondition.cpp b/qpid/cpp/src/tests/PollableCondition.cpp new file mode 100644 index 0000000000..f9b3c25c93 --- /dev/null +++ b/qpid/cpp/src/tests/PollableCondition.cpp @@ -0,0 +1,109 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "test_tools.h" +#include "unit_test.h" +#include "qpid/sys/Poller.h" +#include "qpid/sys/PollableCondition.h" +#include "qpid/sys/Monitor.h" +#include "qpid/sys/Time.h" +#include "qpid/sys/Thread.h" +#include <boost/bind.hpp> + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(PollableConditionTest) + +using namespace qpid::sys; + +const Duration SHORT = TIME_SEC/100; +const Duration LONG = TIME_SEC/10; + +class Callback { + public: + enum Action { NONE, CLEAR }; + + Callback() : count(), action(NONE) {} + + void call(PollableCondition& pc) { + Mutex::ScopedLock l(lock); + ++count; + switch(action) { + case NONE: break; + case CLEAR: pc.clear(); break; + } + action = NONE; + lock.notify(); + } + + bool isCalling() { Mutex::ScopedLock l(lock); return wait(LONG); } + + bool isNotCalling() { Mutex::ScopedLock l(lock); return !wait(SHORT); } + + bool nextCall(Action a=NONE) { + Mutex::ScopedLock l(lock); + action = a; + return wait(LONG); + } + + private: + bool wait(Duration timeout) { + int n = count; + AbsTime deadline(now(), timeout); + while (n == count && lock.wait(deadline)) + ; + return n != count; + } + + Monitor lock; + int count; + Action action; +}; + +QPID_AUTO_TEST_CASE(testPollableCondition) { + boost::shared_ptr<Poller> poller(new Poller()); + Callback callback; + PollableCondition pc(boost::bind(&Callback::call, &callback, _1), poller); + + Thread runner = Thread(*poller); + + BOOST_CHECK(callback.isNotCalling()); // condition is not set. + + pc.set(); + BOOST_CHECK(callback.isCalling()); // Set. + BOOST_CHECK(callback.isCalling()); // Still set. + + callback.nextCall(Callback::CLEAR); + BOOST_CHECK(callback.isNotCalling()); // Cleared + + pc.set(); + BOOST_CHECK(callback.isCalling()); // Set. + callback.nextCall(Callback::CLEAR); + BOOST_CHECK(callback.isNotCalling()); // Cleared. + + poller->shutdown(); + runner.join(); +} + +QPID_AUTO_TEST_SUITE_END() + +}} //namespace qpid::tests diff --git a/qpid/cpp/src/tests/PollerTest.cpp b/qpid/cpp/src/tests/PollerTest.cpp new file mode 100644 index 0000000000..5a1d02964c --- /dev/null +++ b/qpid/cpp/src/tests/PollerTest.cpp @@ -0,0 +1,262 @@ +/* + * + * 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. + * + */ + +/** + * Use socketpair to test the poller + */ + +#include "qpid/sys/Poller.h" +#include "qpid/sys/posix/PrivatePosix.h" + +#include <string> +#include <iostream> +#include <memory> +#include <exception> + +#include <assert.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +using namespace std; +using namespace qpid::sys; + +int writeALot(int fd, const string& s) { + int bytesWritten = 0; + do { + errno = 0; + int lastWrite = ::write(fd, s.c_str(), s.size()); + if ( lastWrite >= 0) { + bytesWritten += lastWrite; + } + } while (errno != EAGAIN); + return bytesWritten; +} + +int readALot(int fd) { + int bytesRead = 0; + char buf[1024]; + + do { + errno = 0; + int lastRead = ::read(fd, buf, sizeof(buf)); + if ( lastRead >= 0) { + bytesRead += lastRead; + } + } while (errno != EAGAIN); + return bytesRead; +} + +void makesocketpair(int (&sv)[2]) { + int rc = ::socketpair(AF_UNIX, SOCK_STREAM, 0, sv); + assert(rc >= 0); + + // Set non-blocking + rc = ::fcntl(sv[0], F_SETFL, O_NONBLOCK); + assert(rc >= 0); + + rc = ::fcntl(sv[1], F_SETFL, O_NONBLOCK); + assert(rc >= 0); +} + +int main(int /*argc*/, char** /*argv*/) +{ + try + { + int sv[2]; + makesocketpair(sv); + + // Make up a large string + string testString = "This is only a test ... 1,2,3,4,5,6,7,8,9,10;"; + for (int i = 0; i < 6; i++) + testString += testString; + + // Read as much as we can from socket 0 + int bytesRead = readALot(sv[0]); + assert(bytesRead == 0); + + // Write as much as we can to socket 0 + int bytesWritten = writeALot(sv[0], testString); + + // Read as much as we can from socket 1 + bytesRead = readALot(sv[1]); + assert(bytesRead == bytesWritten); + + auto_ptr<Poller> poller(new Poller); + + IOHandle f0(sv[0]); + IOHandle f1(sv[1]); + + PollerHandle h0(f0); + PollerHandle h1(f1); + + poller->registerHandle(h0); + poller->monitorHandle(h0, Poller::INOUT); + + // h0 should be writable + Poller::Event event = poller->wait(); + assert(event.handle == &h0); + assert(event.type == Poller::WRITABLE); + + // Write as much as we can to socket 0 + bytesWritten = writeALot(sv[0], testString); + + // Wait for 500ms - h0 no longer writable + event = poller->wait(500000000); + assert(event.handle == 0); + + // Test we can read it all now + poller->registerHandle(h1); + poller->monitorHandle(h1, Poller::INOUT); + event = poller->wait(); + assert(event.handle == &h1); + assert(event.type == Poller::READ_WRITABLE); + + bytesRead = readALot(sv[1]); + assert(bytesRead == bytesWritten); + + // Test poller interrupt + assert(poller->interrupt(h0) == true); + event = poller->wait(); + assert(event.handle == &h0); + assert(event.type == Poller::INTERRUPTED); + + // Test multiple interrupts + assert(poller->interrupt(h0) == true); + assert(poller->interrupt(h1) == true); + + // Make sure we can interrupt them again + assert(poller->interrupt(h0) == true); + assert(poller->interrupt(h1) == true); + + // Make sure that they both come out + event = poller->wait(); + assert(event.type == Poller::INTERRUPTED); + assert(event.handle == &h0 || event.handle == &h1); + if (event.handle == &h0) { + event = poller->wait(); + assert(event.type == Poller::INTERRUPTED); + assert(event.handle == &h1); + } else { + event = poller->wait(); + assert(event.type == Poller::INTERRUPTED); + assert(event.handle == &h0); + } + + poller->unmonitorHandle(h1, Poller::INOUT); + + event = poller->wait(); + assert(event.handle == &h0); + assert(event.type == Poller::WRITABLE); + + // We didn't write anything so it should still be writable + event = poller->wait(); + assert(event.handle == &h0); + assert(event.type == Poller::WRITABLE); + + poller->unmonitorHandle(h0, Poller::INOUT); + + event = poller->wait(500000000); + assert(event.handle == 0); + + poller->unregisterHandle(h1); + assert(poller->interrupt(h1) == false); + + // close the other end to force a disconnect + ::close(sv[1]); + + // Now make sure that we are readable followed by disconnected + // and after that we never return again + poller->monitorHandle(h0, Poller::INOUT); + event = poller->wait(500000000); + assert(event.handle == &h0); + assert(event.type == Poller::READABLE); + event = poller->wait(500000000); + assert(event.handle == &h0); + assert(event.type == Poller::DISCONNECTED); + event = poller->wait(1500000000); + assert(event.handle == 0); + + // Now we're disconnected monitoring should have no effect at all + poller->unmonitorHandle(h0, Poller::INOUT); + event = poller->wait(1500000000); + assert(event.handle == 0); + + poller->unregisterHandle(h0); + assert(poller->interrupt(h0) == false); + + // Test shutdown + poller->shutdown(); + event = poller->wait(); + assert(event.handle == 0); + assert(event.type == Poller::SHUTDOWN); + + event = poller->wait(); + assert(event.handle == 0); + assert(event.type == Poller::SHUTDOWN); + + ::close(sv[0]); + + // Test for correct interaction of shutdown and interrupts - need to have new poller + // etc. for this + makesocketpair(sv); + + auto_ptr<Poller> poller1(new Poller); + + IOHandle f2(sv[0]); + IOHandle f3(sv[1]); + + PollerHandle h2(f2); + PollerHandle h3(f3); + + poller1->registerHandle(h2); + poller1->monitorHandle(h2, Poller::INOUT); + event = poller1->wait(); + assert(event.handle == &h2); + assert(event.type == Poller::WRITABLE); + + // Shutdown + poller1->shutdown(); + event = poller1->wait(); + assert(event.handle == 0); + assert(event.type == Poller::SHUTDOWN); + + assert(poller1->interrupt(h2) == true); + event = poller1->wait(); + assert(event.handle == &h2); + assert(event.type == Poller::INTERRUPTED); + poller1->unmonitorHandle(h2, Poller::INOUT); + + event = poller1->wait(); + assert(event.handle == 0); + assert(event.type == Poller::SHUTDOWN); + + poller1->unregisterHandle(h2); + return 0; + } catch (exception& e) { + cout << "Caught exception " << e.what() << "\n"; + } +} + + diff --git a/qpid/cpp/src/tests/ProxyTest.cpp b/qpid/cpp/src/tests/ProxyTest.cpp new file mode 100644 index 0000000000..a926b28395 --- /dev/null +++ b/qpid/cpp/src/tests/ProxyTest.cpp @@ -0,0 +1,56 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include <iostream> +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/AMQMethodBody.h" +#include "qpid/framing/ExecutionSyncBody.h" +#include "qpid/framing/Proxy.h" + +#include "unit_test.h" + +using namespace qpid::framing; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(ProxyTestSuite) + + +QPID_AUTO_TEST_CASE(testScopedSync) +{ + struct DummyHandler : FrameHandler + { + void handle(AMQFrame& f) { + AMQMethodBody* m = f.getMethod(); + BOOST_CHECK(m); + BOOST_CHECK(m->isA<ExecutionSyncBody>()); + BOOST_CHECK(m->isSync()); + } + }; + DummyHandler f; + Proxy p(f); + Proxy::ScopedSync s(p); + p.send(ExecutionSyncBody(p.getVersion())); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/Qmf2.cpp b/qpid/cpp/src/tests/Qmf2.cpp new file mode 100644 index 0000000000..bc263d5c6d --- /dev/null +++ b/qpid/cpp/src/tests/Qmf2.cpp @@ -0,0 +1,422 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include <iostream> +#include "qpid/types/Variant.h" +#include "qmf/QueryImpl.h" +#include "qmf/SchemaImpl.h" +#include "qmf/exceptions.h" +#include "qpid/messaging/Connection.h" +#include "qmf/PosixEventNotifierImpl.h" +#include "qmf/AgentSession.h" +#include "qmf/AgentSessionImpl.h" +#include "qmf/ConsoleSession.h" +#include "qmf/ConsoleSessionImpl.h" +#include "unit_test.h" + +using namespace std; +using namespace qpid::types; +using namespace qpid::messaging; +using namespace qmf; + +bool isReadable(int fd) +{ + fd_set rfds; + struct timeval tv; + int nfds, result; + + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + nfds = fd + 1; + tv.tv_sec = 0; + tv.tv_usec = 0; + + result = select(nfds, &rfds, NULL, NULL, &tv); + + return result > 0; +} + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(Qmf2Suite) + +QPID_AUTO_TEST_CASE(testQuery) +{ + Query query(QUERY_OBJECT, "class_name", "package_name", "[and, [eq, name, [quote, smith]], [lt, age, [quote, 27]]]"); + Query newQuery(new QueryImpl(QueryImplAccess::get(query).asMap())); + + BOOST_CHECK_EQUAL(newQuery.getTarget(), QUERY_OBJECT); + BOOST_CHECK_EQUAL(newQuery.getSchemaId().getName(), "class_name"); + BOOST_CHECK_EQUAL(newQuery.getSchemaId().getPackageName(), "package_name"); + + Variant::List pred(newQuery.getPredicate()); + BOOST_CHECK_EQUAL(pred.size(), size_t(3)); + + Variant::List::iterator iter(pred.begin()); + BOOST_CHECK_EQUAL(iter->asString(), "and"); + iter++; + BOOST_CHECK_EQUAL(iter->getType(), VAR_LIST); + iter++; + BOOST_CHECK_EQUAL(iter->getType(), VAR_LIST); + iter = iter->asList().begin(); + BOOST_CHECK_EQUAL(iter->asString(), "lt"); + iter++; + BOOST_CHECK_EQUAL(iter->asString(), "age"); + iter++; + BOOST_CHECK_EQUAL(iter->getType(), VAR_LIST); + iter = iter->asList().begin(); + BOOST_CHECK_EQUAL(iter->asString(), "quote"); + iter++; + BOOST_CHECK_EQUAL(iter->asUint32(), uint32_t(27)); + + Query query2(QUERY_OBJECT_ID); + Query newQuery2(new QueryImpl(QueryImplAccess::get(query2).asMap())); + BOOST_CHECK_EQUAL(newQuery2.getTarget(), QUERY_OBJECT_ID); + + Query query3(QUERY_SCHEMA); + Query newQuery3(new QueryImpl(QueryImplAccess::get(query3).asMap())); + BOOST_CHECK_EQUAL(newQuery3.getTarget(), QUERY_SCHEMA); + + Query query4(QUERY_SCHEMA_ID); + Query newQuery4(new QueryImpl(QueryImplAccess::get(query4).asMap())); + BOOST_CHECK_EQUAL(newQuery4.getTarget(), QUERY_SCHEMA_ID); + + DataAddr addr("name", "agent_name", 34); + Query query5(addr); + Query newQuery5(new QueryImpl(QueryImplAccess::get(query5).asMap())); + BOOST_CHECK_EQUAL(newQuery5.getTarget(), QUERY_OBJECT); + BOOST_CHECK_EQUAL(newQuery5.getDataAddr().getName(), "name"); + BOOST_CHECK_EQUAL(newQuery5.getDataAddr().getAgentName(), "agent_name"); + BOOST_CHECK_EQUAL(newQuery5.getDataAddr().getAgentEpoch(), uint32_t(34)); +} + +QPID_AUTO_TEST_CASE(testQueryPredicateErrors) +{ + Query query; + Variant::Map map; + + BOOST_CHECK_THROW(Query(QUERY_OBJECT, "INVALID"), QmfException); + query = Query(QUERY_OBJECT, "[unknown, one, two]"); + BOOST_CHECK_THROW(query.matchesPredicate(map), QmfException); + + query = Query(QUERY_OBJECT, "[eq, first]"); + BOOST_CHECK_THROW(query.matchesPredicate(map), QmfException); + + query = Query(QUERY_OBJECT, "[exists]"); + BOOST_CHECK_THROW(query.matchesPredicate(map), QmfException); + + query = Query(QUERY_OBJECT, "[eq, first, [quote, 1, 2, 3]]]"); + BOOST_CHECK_THROW(query.matchesPredicate(map), QmfException); + + query = Query(QUERY_OBJECT, "[eq, first, [unexpected, 3]]]"); + BOOST_CHECK_THROW(query.matchesPredicate(map), QmfException); + + query = Query(QUERY_OBJECT, "[eq, first, {}]]"); + BOOST_CHECK_THROW(query.matchesPredicate(map), QmfException); + + query = Query(QUERY_OBJECT, "[eq, first, second, third]"); + BOOST_CHECK_THROW(query.matchesPredicate(map), QmfException); + + query = Query(QUERY_OBJECT, "[and, first, second, third]"); + BOOST_CHECK_THROW(query.matchesPredicate(map), QmfException); +} + +QPID_AUTO_TEST_CASE(testQueryPredicate) +{ + Query query; + Variant::Map map; + + map["forty"] = 40; + map["fifty"] = 50; + map["minus_ten"] = -10; + map["pos_float"] = 100.05; + map["neg_float"] = -1000.33; + map["name"] = "jones"; + map["bool_t"] = true; + map["bool_f"] = false; + + BOOST_CHECK_THROW(Query(QUERY_OBJECT, "INVALID"), QmfException); + + query = Query(QUERY_OBJECT); + BOOST_CHECK_EQUAL(query.matchesPredicate(Variant::Map()), true); + + query = Query(QUERY_OBJECT, "[eq, forty, [quote, 40]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), true); + + query = Query(QUERY_OBJECT, "[eq, forty, [quote, 41]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), false); + + query = Query(QUERY_OBJECT, "[le, forty, fifty]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), true); + + query = Query(QUERY_OBJECT, "[and, [eq, forty, [quote, 40]], [eq, name, [quote, jones]]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), true); + + query = Query(QUERY_OBJECT, "[and, [eq, forty, [quote, 40]], [eq, name, [quote, smith]]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), false); + + query = Query(QUERY_OBJECT, "[or, [eq, forty, [quote, 40]], [eq, name, [quote, smith]]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), true); + + query = Query(QUERY_OBJECT, "[or, [eq, forty, [quote, 41]], [eq, name, [quote, smith]]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), false); + + query = Query(QUERY_OBJECT, "[not, [le, forty, [quote, 40]]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), false); + + query = Query(QUERY_OBJECT, "[le, forty, [quote, 40]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), true); + + query = Query(QUERY_OBJECT, "[ge, forty, [quote, 40]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), true); + + query = Query(QUERY_OBJECT, "[lt, forty, [quote, 45]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), true); + + query = Query(QUERY_OBJECT, "[lt, [quote, 45], forty]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), false); + + query = Query(QUERY_OBJECT, "[gt, forty, [quote, 45]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), false); + + query = Query(QUERY_OBJECT, "[gt, [quote, 45], forty]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), true); + + query = Query(QUERY_OBJECT, "[eq, bool_t, [quote, True]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), true); + + query = Query(QUERY_OBJECT, "[eq, bool_t, [quote, False]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), false); + + query = Query(QUERY_OBJECT, "[eq, bool_f, [quote, True]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), false); + + query = Query(QUERY_OBJECT, "[eq, bool_f, [quote, False]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), true); + + query = Query(QUERY_OBJECT, "[eq, minus_ten, [quote, -10]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), true); + + query = Query(QUERY_OBJECT, "[lt, minus_ten, [quote, -20]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), false); + + query = Query(QUERY_OBJECT, "[lt, [quote, -20], minus_ten]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), true); + + query = Query(QUERY_OBJECT, "[exists, name]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), true); + + query = Query(QUERY_OBJECT, "[exists, nonexfield]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), false); + + query = Query(QUERY_OBJECT, "[eq, pos_float, [quote, 100.05]]"); + BOOST_CHECK_EQUAL(query.matchesPredicate(map), true); +} + +QPID_AUTO_TEST_CASE(testSchema) +{ + Schema in(SCHEMA_TYPE_DATA, "package", "class"); + in.addProperty(SchemaProperty("prop1", SCHEMA_DATA_BOOL, "{desc:'Property One'}")); + in.addProperty(SchemaProperty("prop2", SCHEMA_DATA_INT, "{desc:'Property Two',unit:'Furlong'}")); + in.addProperty(SchemaProperty("prop3", SCHEMA_DATA_STRING, "{desc:'Property Three'}")); + + SchemaMethod method1("method1", "{desc:'Method One'}"); + method1.addArgument(SchemaProperty("arg1", SCHEMA_DATA_BOOL, "{desc:'Argument One',dir:IN}")); + method1.addArgument(SchemaProperty("arg2", SCHEMA_DATA_INT, "{desc:'Argument Two',dir:OUT}")); + method1.addArgument(SchemaProperty("arg3", SCHEMA_DATA_FLOAT, "{desc:'Argument Three',dir:INOUT}")); + in.addMethod(method1); + + SchemaMethod method2("method2", "{desc:'Method Two'}"); + method2.addArgument(SchemaProperty("arg21", SCHEMA_DATA_BOOL, "{desc:'Argument One',dir:IN}")); + method2.addArgument(SchemaProperty("arg22", SCHEMA_DATA_INT, "{desc:'Argument Two',dir:OUT}")); + method2.addArgument(SchemaProperty("arg23", SCHEMA_DATA_FLOAT, "{desc:'Argument Three',dir:INOUT}")); + in.addMethod(method2); + + BOOST_CHECK(!in.isFinalized()); + in.finalize(); + BOOST_CHECK(in.isFinalized()); + + Variant::Map map(SchemaImplAccess::get(in).asMap()); + Schema out(new SchemaImpl(map)); + + BOOST_CHECK(out.isFinalized()); + BOOST_CHECK_EQUAL(out.getSchemaId().getType(), SCHEMA_TYPE_DATA); + BOOST_CHECK_EQUAL(out.getSchemaId().getPackageName(), "package"); + BOOST_CHECK_EQUAL(out.getSchemaId().getName(), "class"); + BOOST_CHECK_EQUAL(out.getSchemaId().getHash(), in.getSchemaId().getHash()); + + BOOST_CHECK_EQUAL(out.getPropertyCount(), uint32_t(3)); + SchemaProperty prop; + + prop = out.getProperty(0); + BOOST_CHECK_EQUAL(prop.getName(), "prop1"); + BOOST_CHECK_EQUAL(prop.getType(), SCHEMA_DATA_BOOL); + BOOST_CHECK_EQUAL(prop.getDesc(), "Property One"); + + prop = out.getProperty(1); + BOOST_CHECK_EQUAL(prop.getName(), "prop2"); + BOOST_CHECK_EQUAL(prop.getType(), SCHEMA_DATA_INT); + BOOST_CHECK_EQUAL(prop.getDesc(), "Property Two"); + BOOST_CHECK_EQUAL(prop.getUnit(), "Furlong"); + BOOST_CHECK(!prop.isIndex()); + + prop = out.getProperty(2); + BOOST_CHECK_EQUAL(prop.getName(), "prop3"); + BOOST_CHECK_EQUAL(prop.getType(), SCHEMA_DATA_STRING); + BOOST_CHECK_EQUAL(prop.getDesc(), "Property Three"); + + BOOST_CHECK_THROW(out.getProperty(3), QmfException); + + BOOST_CHECK_EQUAL(out.getMethodCount(), uint32_t(2)); + SchemaMethod method; + + method = out.getMethod(0); + BOOST_CHECK_EQUAL(method.getName(), "method1"); + BOOST_CHECK_EQUAL(method.getDesc(), "Method One"); + BOOST_CHECK_EQUAL(method.getArgumentCount(), uint32_t(3)); + + prop = method.getArgument(0); + BOOST_CHECK_EQUAL(prop.getName(), "arg1"); + BOOST_CHECK_EQUAL(prop.getType(), SCHEMA_DATA_BOOL); + BOOST_CHECK_EQUAL(prop.getDesc(), "Argument One"); + BOOST_CHECK_EQUAL(prop.getDirection(), DIR_IN); + + prop = method.getArgument(1); + BOOST_CHECK_EQUAL(prop.getName(), "arg2"); + BOOST_CHECK_EQUAL(prop.getType(), SCHEMA_DATA_INT); + BOOST_CHECK_EQUAL(prop.getDesc(), "Argument Two"); + BOOST_CHECK_EQUAL(prop.getDirection(), DIR_OUT); + + prop = method.getArgument(2); + BOOST_CHECK_EQUAL(prop.getName(), "arg3"); + BOOST_CHECK_EQUAL(prop.getType(), SCHEMA_DATA_FLOAT); + BOOST_CHECK_EQUAL(prop.getDesc(), "Argument Three"); + BOOST_CHECK_EQUAL(prop.getDirection(), DIR_IN_OUT); + + BOOST_CHECK_THROW(method.getArgument(3), QmfException); + + method = out.getMethod(1); + BOOST_CHECK_EQUAL(method.getName(), "method2"); + BOOST_CHECK_EQUAL(method.getDesc(), "Method Two"); + BOOST_CHECK_EQUAL(method.getArgumentCount(), uint32_t(3)); + + prop = method.getArgument(0); + BOOST_CHECK_EQUAL(prop.getName(), "arg21"); + BOOST_CHECK_EQUAL(prop.getType(), SCHEMA_DATA_BOOL); + BOOST_CHECK_EQUAL(prop.getDesc(), "Argument One"); + BOOST_CHECK_EQUAL(prop.getDirection(), DIR_IN); + + prop = method.getArgument(1); + BOOST_CHECK_EQUAL(prop.getName(), "arg22"); + BOOST_CHECK_EQUAL(prop.getType(), SCHEMA_DATA_INT); + BOOST_CHECK_EQUAL(prop.getDesc(), "Argument Two"); + BOOST_CHECK_EQUAL(prop.getDirection(), DIR_OUT); + + prop = method.getArgument(2); + BOOST_CHECK_EQUAL(prop.getName(), "arg23"); + BOOST_CHECK_EQUAL(prop.getType(), SCHEMA_DATA_FLOAT); + BOOST_CHECK_EQUAL(prop.getDesc(), "Argument Three"); + BOOST_CHECK_EQUAL(prop.getDirection(), DIR_IN_OUT); + + BOOST_CHECK_THROW(method.getArgument(3), QmfException); +} + +QPID_AUTO_TEST_CASE(testAgentSessionEventListener) +{ + Connection connection("localhost"); + AgentSession session(connection, ""); + posix::EventNotifier notifier(session); + + AgentSessionImpl& sessionImpl = AgentSessionImplAccess::get(session); + + BOOST_CHECK(sessionImpl.getEventNotifier() != 0); +} + +QPID_AUTO_TEST_CASE(testConsoleSessionEventListener) +{ + Connection connection("localhost"); + ConsoleSession session(connection, ""); + posix::EventNotifier notifier(session); + + ConsoleSessionImpl& sessionImpl = ConsoleSessionImplAccess::get(session); + + BOOST_CHECK(sessionImpl.getEventNotifier() != 0); +} + +QPID_AUTO_TEST_CASE(testGetHandle) +{ + Connection connection("localhost"); + ConsoleSession session(connection, ""); + posix::EventNotifier notifier(session); + + BOOST_CHECK(notifier.getHandle() > 0); +} + +QPID_AUTO_TEST_CASE(testSetReadableToFalse) +{ + Connection connection("localhost"); + ConsoleSession session(connection, ""); + posix::EventNotifier notifier(session); + PosixEventNotifierImplAccess::get(notifier).setReadable(false); + + bool readable(isReadable(notifier.getHandle())); + BOOST_CHECK(!readable); +} + +QPID_AUTO_TEST_CASE(testSetReadable) +{ + Connection connection("localhost"); + ConsoleSession session(connection, ""); + posix::EventNotifier notifier(session); + PosixEventNotifierImplAccess::get(notifier).setReadable(true); + + bool readable(isReadable(notifier.getHandle())); + BOOST_CHECK(readable); +} + +QPID_AUTO_TEST_CASE(testSetReadableMultiple) +{ + Connection connection("localhost"); + ConsoleSession session(connection, ""); + posix::EventNotifier notifier(session); + for (int i = 0; i < 15; i++) + PosixEventNotifierImplAccess::get(notifier).setReadable(true); + PosixEventNotifierImplAccess::get(notifier).setReadable(false); + + bool readable(isReadable(notifier.getHandle())); + BOOST_CHECK(!readable); +} + +QPID_AUTO_TEST_CASE(testDeleteNotifier) +{ + Connection connection("localhost"); + ConsoleSession session(connection, ""); + ConsoleSessionImpl& sessionImpl = ConsoleSessionImplAccess::get(session); + { + posix::EventNotifier notifier(session); + BOOST_CHECK(sessionImpl.getEventNotifier() != 0); + } + BOOST_CHECK(sessionImpl.getEventNotifier() == 0); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/QueueDepth.cpp b/qpid/cpp/src/tests/QueueDepth.cpp new file mode 100644 index 0000000000..09b221b3a8 --- /dev/null +++ b/qpid/cpp/src/tests/QueueDepth.cpp @@ -0,0 +1,105 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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. + * + */ + +#include "qpid/broker/QueueDepth.h" + +#include "unit_test.h" + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(QueueDepthTestSuite) + +using namespace qpid::broker; + +QPID_AUTO_TEST_CASE(testCompare) +{ + QueueDepth a(0, 0); + QueueDepth b(1, 1); + QueueDepth c(2, 2); + QueueDepth d(1, 1); + + BOOST_CHECK(a < b); + BOOST_CHECK(b < c); + BOOST_CHECK(a < c); + + BOOST_CHECK(b > a); + BOOST_CHECK(c > b); + BOOST_CHECK(c > a); + + BOOST_CHECK(b == d); + BOOST_CHECK(d == b); + BOOST_CHECK(a != b); + BOOST_CHECK(b != a); + + QueueDepth e; e.setCount(1); + QueueDepth f; f.setCount(2); + BOOST_CHECK(e < f); + BOOST_CHECK(f > e); + + QueueDepth g; g.setSize(1); + QueueDepth h; h.setSize(2); + BOOST_CHECK(g < h); + BOOST_CHECK(h > g); +} + +QPID_AUTO_TEST_CASE(testIncrement) +{ + QueueDepth a(5, 10); + QueueDepth b(3, 6); + QueueDepth c(8, 16); + a += b; + BOOST_CHECK(a == c); + BOOST_CHECK_EQUAL(8u, a.getCount()); + BOOST_CHECK_EQUAL(16u, a.getSize()); +} + +QPID_AUTO_TEST_CASE(testDecrement) +{ + QueueDepth a(5, 10); + QueueDepth b(3, 6); + QueueDepth c(2, 4); + a -= b; + BOOST_CHECK(a == c); + BOOST_CHECK_EQUAL(2u, a.getCount()); + BOOST_CHECK_EQUAL(4u, a.getSize()); +} + +QPID_AUTO_TEST_CASE(testAddition) +{ + QueueDepth a(5, 10); + QueueDepth b(3, 6); + + QueueDepth c = a + b; + BOOST_CHECK_EQUAL(8u, c.getCount()); + BOOST_CHECK_EQUAL(16u, c.getSize()); +} + +QPID_AUTO_TEST_CASE(testSubtraction) +{ + QueueDepth a(5, 10); + QueueDepth b(3, 6); + + QueueDepth c = a - b; + BOOST_CHECK_EQUAL(2u, c.getCount()); + BOOST_CHECK_EQUAL(4u, c.getSize()); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/QueueFlowLimitTest.cpp b/qpid/cpp/src/tests/QueueFlowLimitTest.cpp new file mode 100644 index 0000000000..b35294922d --- /dev/null +++ b/qpid/cpp/src/tests/QueueFlowLimitTest.cpp @@ -0,0 +1,457 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include <sstream> +#include <deque> +#include "unit_test.h" +#include "test_tools.h" + +#include "qpid/broker/QueueFlowLimit.h" +#include "qpid/broker/QueueSettings.h" +#include "qpid/sys/Time.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/framing/FieldValue.h" +#include "MessageUtils.h" +#include "BrokerFixture.h" + +using namespace qpid::broker; +using namespace qpid::framing; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(QueueFlowLimitTestSuite) + +namespace { + +class TestFlow : public QueueFlowLimit +{ +public: + TestFlow(uint32_t flowStopCount, uint32_t flowResumeCount, + uint64_t flowStopSize, uint64_t flowResumeSize) : + QueueFlowLimit("", flowStopCount, flowResumeCount, flowStopSize, flowResumeSize) + {} + virtual ~TestFlow() {} + + static TestFlow *createTestFlow(const qpid::framing::FieldTable& settings) + { + FieldTable::ValuePtr v; + + v = settings.get(flowStopCountKey); + uint32_t flowStopCount = (v) ? (uint32_t)v->get<int64_t>() : 0; + v = settings.get(flowResumeCountKey); + uint32_t flowResumeCount = (v) ? (uint32_t)v->get<int64_t>() : 0; + v = settings.get(flowStopSizeKey); + uint64_t flowStopSize = (v) ? (uint64_t)v->get<int64_t>() : 0; + v = settings.get(flowResumeSizeKey); + uint64_t flowResumeSize = (v) ? (uint64_t)v->get<int64_t>() : 0; + + return new TestFlow(flowStopCount, flowResumeCount, flowStopSize, flowResumeSize); + } + + static boost::shared_ptr<qpid::broker::QueueFlowLimit> getQueueFlowLimit(const qpid::framing::FieldTable& arguments) + { + QueueSettings settings; + settings.populate(arguments, settings.storeSettings); + return QueueFlowLimit::createLimit("", settings); + } +}; + +Message createMessage(uint32_t size) +{ + static uint32_t seqNum; + //Need to compute what data size is required to make a given + //overall size (use one byte of content in test message to ensure + //content frame is added) + Message test = MessageUtils::createMessage(qpid::types::Variant::Map(), std::string("x")); + size_t min = test.getMessageSize() - 1; + if (min > size) throw qpid::Exception("Can't create message that small!"); + Message msg = MessageUtils::createMessage(qpid::types::Variant::Map(), std::string (size - min, 'x')); + msg.setSequence(++seqNum);//this doesn't affect message size + return msg; +} +} + +QPID_AUTO_TEST_CASE(testFlowCount) +{ + FieldTable args; + args.setInt(QueueFlowLimit::flowStopCountKey, 7); + args.setInt(QueueFlowLimit::flowResumeCountKey, 5); + + std::auto_ptr<TestFlow> flow(TestFlow::createTestFlow(args)); + + BOOST_CHECK_EQUAL((uint32_t) 7, flow->getFlowStopCount()); + BOOST_CHECK_EQUAL((uint32_t) 5, flow->getFlowResumeCount()); + BOOST_CHECK_EQUAL((uint32_t) 0, flow->getFlowStopSize()); + BOOST_CHECK_EQUAL((uint32_t) 0, flow->getFlowResumeSize()); + BOOST_CHECK(!flow->isFlowControlActive()); + BOOST_CHECK(flow->monitorFlowControl()); + + std::deque<Message> msgs; + for (size_t i = 0; i < 6; i++) { + msgs.push_back(createMessage(100)); + flow->enqueued(msgs.back()); + BOOST_CHECK(!flow->isFlowControlActive()); + } + BOOST_CHECK(!flow->isFlowControlActive()); // 6 on queue + msgs.push_back(createMessage(100)); + flow->enqueued(msgs.back()); + BOOST_CHECK(!flow->isFlowControlActive()); // 7 on queue + msgs.push_back(createMessage(100)); + flow->enqueued(msgs.back()); + BOOST_CHECK(flow->isFlowControlActive()); // 8 on queue, ON + msgs.push_back(createMessage(100)); + flow->enqueued(msgs.back()); + BOOST_CHECK(flow->isFlowControlActive()); // 9 on queue, no change to flow control + + flow->dequeued(msgs.front()); + msgs.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); // 8 on queue + flow->dequeued(msgs.front()); + msgs.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); // 7 on queue + flow->dequeued(msgs.front()); + msgs.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); // 6 on queue + flow->dequeued(msgs.front()); + msgs.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); // 5 on queue, no change + + flow->dequeued(msgs.front()); + msgs.pop_front(); + BOOST_CHECK(!flow->isFlowControlActive()); // 4 on queue, OFF +} + +QPID_AUTO_TEST_CASE(testFlowSize) +{ + FieldTable args; + args.setUInt64(QueueFlowLimit::flowStopSizeKey, 700); + args.setUInt64(QueueFlowLimit::flowResumeSizeKey, 460); + + std::auto_ptr<TestFlow> flow(TestFlow::createTestFlow(args)); + + BOOST_CHECK_EQUAL((uint32_t) 0, flow->getFlowStopCount()); + BOOST_CHECK_EQUAL((uint32_t) 0, flow->getFlowResumeCount()); + BOOST_CHECK_EQUAL((uint32_t) 700, flow->getFlowStopSize()); + BOOST_CHECK_EQUAL((uint32_t) 460, flow->getFlowResumeSize()); + BOOST_CHECK(!flow->isFlowControlActive()); + BOOST_CHECK(flow->monitorFlowControl()); + + std::deque<Message> msgs; + for (size_t i = 0; i < 6; i++) { + msgs.push_back(createMessage(100)); + flow->enqueued(msgs.back()); + BOOST_CHECK(!flow->isFlowControlActive()); + } + BOOST_CHECK(!flow->isFlowControlActive()); // 600 on queue + BOOST_CHECK_EQUAL(6u, flow->getFlowCount()); + BOOST_CHECK_EQUAL(600u, flow->getFlowSize()); + + Message msg_50 = createMessage(50); + flow->enqueued(msg_50); + BOOST_CHECK(!flow->isFlowControlActive()); // 650 on queue + Message tinyMsg_1 = createMessage(40); + flow->enqueued(tinyMsg_1); + BOOST_CHECK(!flow->isFlowControlActive()); // 690 on queue + + Message tinyMsg_2 = createMessage(40); + flow->enqueued(tinyMsg_2); + BOOST_CHECK(flow->isFlowControlActive()); // 730 on queue, ON + msgs.push_back(createMessage(100)); + flow->enqueued(msgs.back()); + BOOST_CHECK(flow->isFlowControlActive()); // 830 on queue + BOOST_CHECK_EQUAL(10u, flow->getFlowCount()); + BOOST_CHECK_EQUAL(830u, flow->getFlowSize()); + + flow->dequeued(msgs.front()); + msgs.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); // 730 on queue + flow->dequeued(msgs.front()); + msgs.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); // 630 on queue + flow->dequeued(msgs.front()); + msgs.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); // 530 on queue + + flow->dequeued(tinyMsg_1); + BOOST_CHECK(flow->isFlowControlActive()); // 490 on queue + flow->dequeued(tinyMsg_2); + BOOST_CHECK(!flow->isFlowControlActive()); // 450 on queue, OFF + + flow->dequeued(msg_50); + BOOST_CHECK(!flow->isFlowControlActive()); // 400 on queue + flow->dequeued(msgs.front()); + msgs.pop_front(); + BOOST_CHECK(!flow->isFlowControlActive()); // 300 on queue + flow->dequeued(msgs.front()); + msgs.pop_front(); + BOOST_CHECK(!flow->isFlowControlActive()); // 200 on queue + BOOST_CHECK_EQUAL(2u, flow->getFlowCount()); + BOOST_CHECK_EQUAL(200u, flow->getFlowSize()); +} + +QPID_AUTO_TEST_CASE(testFlowArgs) +{ + FieldTable args; + const uint64_t stop(0x2FFFFFFFFull); + const uint64_t resume(0x1FFFFFFFFull); + args.setInt(QueueFlowLimit::flowStopCountKey, 30); + args.setInt(QueueFlowLimit::flowResumeCountKey, 21); + args.setUInt64(QueueFlowLimit::flowStopSizeKey, stop); + args.setUInt64(QueueFlowLimit::flowResumeSizeKey, resume); + + std::auto_ptr<TestFlow> flow(TestFlow::createTestFlow(args)); + + BOOST_CHECK_EQUAL((uint32_t) 30, flow->getFlowStopCount()); + BOOST_CHECK_EQUAL((uint32_t) 21, flow->getFlowResumeCount()); + BOOST_CHECK_EQUAL(stop, flow->getFlowStopSize()); + BOOST_CHECK_EQUAL(resume, flow->getFlowResumeSize()); + BOOST_CHECK(!flow->isFlowControlActive()); + BOOST_CHECK(flow->monitorFlowControl()); +} + + +QPID_AUTO_TEST_CASE(testFlowCombo) +{ + FieldTable args; + args.setInt(QueueFlowLimit::flowStopCountKey, 10); + args.setInt(QueueFlowLimit::flowResumeCountKey, 5); + args.setUInt64(QueueFlowLimit::flowStopSizeKey, 2000); + args.setUInt64(QueueFlowLimit::flowResumeSizeKey, 1000); + + std::deque<Message> msgs_50; + std::deque<Message> msgs_100; + std::deque<Message> msgs_500; + std::deque<Message> msgs_1000; + + Message msg; + + std::auto_ptr<TestFlow> flow(TestFlow::createTestFlow(args)); + BOOST_CHECK(!flow->isFlowControlActive()); // count:0 size:0 + + // verify flow control comes ON when only count passes its stop point. + + for (size_t i = 0; i < 10; i++) { + msgs_100.push_back(createMessage(100)); + flow->enqueued(msgs_100.back()); + BOOST_CHECK(!flow->isFlowControlActive()); + } + // count:10 size:1000 + + msgs_50.push_back(createMessage(50)); + flow->enqueued(msgs_50.back()); // count:11 size: 1050 ->ON + BOOST_CHECK(flow->isFlowControlActive()); + + for (size_t i = 0; i < 6; i++) { + flow->dequeued(msgs_100.front()); + msgs_100.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); + } + // count:5 size: 450 + + flow->dequeued(msgs_50.front()); // count: 4 size: 400 ->OFF + msgs_50.pop_front(); + BOOST_CHECK(!flow->isFlowControlActive()); + + for (size_t i = 0; i < 4; i++) { + flow->dequeued(msgs_100.front()); + msgs_100.pop_front(); + BOOST_CHECK(!flow->isFlowControlActive()); + } + // count:0 size:0 + + // verify flow control comes ON when only size passes its stop point. + + msgs_1000.push_back(createMessage(1000)); + flow->enqueued(msgs_1000.back()); // count:1 size: 1000 + BOOST_CHECK(!flow->isFlowControlActive()); + + msgs_500.push_back(createMessage(500)); + flow->enqueued(msgs_500.back()); // count:2 size: 1500 + BOOST_CHECK(!flow->isFlowControlActive()); + + msgs_500.push_back(createMessage(500)); + flow->enqueued(msgs_500.back()); // count:3 size: 2000 + BOOST_CHECK(!flow->isFlowControlActive()); + + msgs_50.push_back(createMessage(50)); + flow->enqueued(msgs_50.back()); // count:4 size: 2050 ->ON + BOOST_CHECK(flow->isFlowControlActive()); + + flow->dequeued(msgs_1000.front()); // count:3 size:1050 + msgs_1000.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); + + flow->dequeued(msgs_50.front()); // count:2 size:1000 + msgs_50.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); + + flow->dequeued(msgs_500.front()); // count:1 size:500 ->OFF + msgs_500.pop_front(); + BOOST_CHECK(!flow->isFlowControlActive()); + + // verify flow control remains ON until both thresholds drop below their + // resume point. + + for (size_t i = 0; i < 8; i++) { + msgs_100.push_back(createMessage(100)); + flow->enqueued(msgs_100.back()); + BOOST_CHECK(!flow->isFlowControlActive()); + } + // count:9 size:1300 + + msgs_100.push_back(createMessage(100)); + flow->enqueued(msgs_100.back()); // count:10 size: 1400 + BOOST_CHECK(!flow->isFlowControlActive()); + + msgs_50.push_back(createMessage(50)); + flow->enqueued(msgs_50.back()); // count:11 size: 1450 ->ON + BOOST_CHECK(flow->isFlowControlActive()); + + msgs_1000.push_back(createMessage(1000)); + flow->enqueued(msgs_1000.back()); // count:12 size: 2450 (both thresholds crossed) + BOOST_CHECK(flow->isFlowControlActive()); + + // at this point: 9@100 + 1@500 + 1@1000 + 1@50 == 12@2450 + + flow->dequeued(msgs_500.front()); // count:11 size:1950 + msgs_500.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); + + for (size_t i = 0; i < 9; i++) { + flow->dequeued(msgs_100.front()); + msgs_100.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); + } + // count:2 size:1050 + flow->dequeued(msgs_50.front()); // count:1 size:1000 + msgs_50.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); // still active due to size + + flow->dequeued(msgs_1000.front()); // count:0 size:0 ->OFF + msgs_1000.pop_front(); + BOOST_CHECK(!flow->isFlowControlActive()); +} + + +QPID_AUTO_TEST_CASE(testFlowDefaultArgs) +{ + QueueFlowLimit::setDefaults(2950001, // max queue byte count + 80, // 80% stop threshold + 70); // 70% resume threshold + FieldTable args; + boost::shared_ptr<QueueFlowLimit> flow = TestFlow::getQueueFlowLimit(args); + BOOST_CHECK(flow); + + BOOST_CHECK_EQUAL((uint64_t) 2360001, flow->getFlowStopSize()); + BOOST_CHECK_EQUAL((uint64_t) 2065000, flow->getFlowResumeSize()); + BOOST_CHECK_EQUAL( 0u, flow->getFlowStopCount()); + BOOST_CHECK_EQUAL( 0u, flow->getFlowResumeCount()); + BOOST_CHECK(!flow->isFlowControlActive()); + BOOST_CHECK(flow->monitorFlowControl()); +} + + +QPID_AUTO_TEST_CASE(testFlowOverrideArgs) +{ + QueueFlowLimit::setDefaults(0, // max queue byte count + 80, // 80% stop threshold + 70); // 70% resume threshold + { + FieldTable args; + args.setInt(QueueFlowLimit::flowStopCountKey, 35000); + args.setInt(QueueFlowLimit::flowResumeCountKey, 30000); +// args.setInt(QueueFlowLimit::flowStopSizeKey, 0); + + boost::shared_ptr<QueueFlowLimit> flow = TestFlow::getQueueFlowLimit(args); + BOOST_CHECK(flow); + + BOOST_CHECK_EQUAL((uint32_t) 35000, flow->getFlowStopCount()); + BOOST_CHECK_EQUAL((uint32_t) 30000, flow->getFlowResumeCount()); + BOOST_CHECK_EQUAL((uint64_t) 0, flow->getFlowStopSize()); + BOOST_CHECK_EQUAL((uint64_t) 0, flow->getFlowResumeSize()); + BOOST_CHECK(!flow->isFlowControlActive()); + BOOST_CHECK(flow->monitorFlowControl()); + } + { + FieldTable args; + args.setInt(QueueFlowLimit::flowStopSizeKey, 350000); + args.setInt(QueueFlowLimit::flowResumeSizeKey, 300000); + + boost::shared_ptr<QueueFlowLimit> flow = TestFlow::getQueueFlowLimit(args); + BOOST_CHECK(flow); + + BOOST_CHECK_EQUAL((uint32_t) 0, flow->getFlowStopCount()); + BOOST_CHECK_EQUAL((uint32_t) 0, flow->getFlowResumeCount()); + BOOST_CHECK_EQUAL((uint64_t) 350000, flow->getFlowStopSize()); + BOOST_CHECK_EQUAL((uint64_t) 300000, flow->getFlowResumeSize()); + BOOST_CHECK(!flow->isFlowControlActive()); + BOOST_CHECK(flow->monitorFlowControl()); + } + { + FieldTable args; + args.setInt(QueueFlowLimit::flowStopCountKey, 35000); + args.setInt(QueueFlowLimit::flowResumeCountKey, 30000); + args.setInt(QueueFlowLimit::flowStopSizeKey, 350000); + args.setInt(QueueFlowLimit::flowResumeSizeKey, 300000); + + boost::shared_ptr<QueueFlowLimit> flow = TestFlow::getQueueFlowLimit(args); + BOOST_CHECK(flow); + + BOOST_CHECK_EQUAL((uint32_t) 35000, flow->getFlowStopCount()); + BOOST_CHECK_EQUAL((uint32_t) 30000, flow->getFlowResumeCount()); + BOOST_CHECK_EQUAL((uint64_t) 350000, flow->getFlowStopSize()); + BOOST_CHECK_EQUAL((uint64_t) 300000, flow->getFlowResumeSize()); + BOOST_CHECK(!flow->isFlowControlActive()); + BOOST_CHECK(flow->monitorFlowControl()); + } +} + + +QPID_AUTO_TEST_CASE(testFlowOverrideDefaults) +{ + QueueFlowLimit::setDefaults(2950001, // max queue byte count + 97, // stop threshold + 73); // resume threshold + FieldTable args; + boost::shared_ptr<QueueFlowLimit> flow = TestFlow::getQueueFlowLimit(args); + BOOST_CHECK(flow); + + BOOST_CHECK_EQUAL((uint32_t) 2861501, flow->getFlowStopSize()); + BOOST_CHECK_EQUAL((uint32_t) 2153500, flow->getFlowResumeSize()); + BOOST_CHECK(!flow->isFlowControlActive()); + BOOST_CHECK(flow->monitorFlowControl()); +} + + +QPID_AUTO_TEST_CASE(testFlowDisable) +{ + { + FieldTable args; + args.setInt(QueueFlowLimit::flowStopCountKey, 0); + args.setInt(QueueFlowLimit::flowStopSizeKey, 0); + boost::shared_ptr<QueueFlowLimit> flow = TestFlow::getQueueFlowLimit(args); + BOOST_CHECK(!flow); + } +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/QueueOptionsTest.cpp b/qpid/cpp/src/tests/QueueOptionsTest.cpp new file mode 100644 index 0000000000..bdb83d7d22 --- /dev/null +++ b/qpid/cpp/src/tests/QueueOptionsTest.cpp @@ -0,0 +1,85 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include <iostream> +#include "qpid/framing/Array.h" +#include "qpid/client/QueueOptions.h" + +#include "unit_test.h" + +using namespace qpid::client; + + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(QueueOptionsTestSuite) + +QPID_AUTO_TEST_CASE(testSizePolicy) +{ + QueueOptions ft; + + ft.setSizePolicy(REJECT,1,2); + + BOOST_CHECK(QueueOptions::strREJECT == ft.getAsString(QueueOptions::strTypeKey)); + BOOST_CHECK(1 == ft.getAsInt(QueueOptions::strMaxSizeKey)); + BOOST_CHECK(2 == ft.getAsInt(QueueOptions::strMaxCountKey)); + + ft.setSizePolicy(FLOW_TO_DISK,0,2); + BOOST_CHECK(QueueOptions::strFLOW_TO_DISK == ft.getAsString(QueueOptions::strTypeKey)); + BOOST_CHECK(1 == ft.getAsInt(QueueOptions::strMaxSizeKey)); + BOOST_CHECK(2 == ft.getAsInt(QueueOptions::strMaxCountKey)); + + ft.setSizePolicy(RING,1,0); + BOOST_CHECK(QueueOptions::strRING == ft.getAsString(QueueOptions::strTypeKey)); + + ft.setSizePolicy(RING_STRICT,1,0); + BOOST_CHECK(QueueOptions::strRING_STRICT == ft.getAsString(QueueOptions::strTypeKey)); + + ft.clearSizePolicy(); + BOOST_CHECK(!ft.isSet(QueueOptions::strTypeKey)); + BOOST_CHECK(!ft.isSet(QueueOptions::strMaxSizeKey)); + BOOST_CHECK(!ft.isSet(QueueOptions::strMaxCountKey)); +} + +QPID_AUTO_TEST_CASE(testFlags) +{ + QueueOptions ft; + + ft.setOrdering(LVQ); + BOOST_CHECK(1 == ft.getAsInt(QueueOptions::strLastValueQueue)); + ft.setOrdering(FIFO); + BOOST_CHECK(!ft.isSet(QueueOptions::strLastValueQueue)); + +} + +QPID_AUTO_TEST_CASE(testSetOrdering) +{ + //ensure setOrdering(FIFO) works even if not preceded by a call to + //setOrdering(LVQ) + QueueOptions ft; + ft.setOrdering(FIFO); + BOOST_CHECK(!ft.isSet(QueueOptions::strLastValueQueue)); + +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/QueuePolicyTest.cpp b/qpid/cpp/src/tests/QueuePolicyTest.cpp new file mode 100644 index 0000000000..f61c283fd4 --- /dev/null +++ b/qpid/cpp/src/tests/QueuePolicyTest.cpp @@ -0,0 +1,300 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include <sstream> +#include "unit_test.h" +#include "test_tools.h" + +#include "qpid/broker/QueueFlowLimit.h" +#include "qpid/client/QueueOptions.h" +#include "qpid/sys/Time.h" +#include "qpid/framing/reply_exceptions.h" +#include "BrokerFixture.h" + +#include <boost/format.hpp> + +using namespace qpid::broker; +using namespace qpid::client; +using namespace qpid::framing; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(QueuePolicyTestSuite) + +QPID_AUTO_TEST_CASE(testRingPolicyCount) +{ + QueueOptions args; + args.setSizePolicy(RING, 0, 5); + + SessionFixture f; + std::string q("my-ring-queue"); + f.session.queueDeclare(arg::queue=q, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args); + for (int i = 0; i < 10; i++) { + f.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q)); + } + client::Message msg; + for (int i = 5; i < 10; i++) { + BOOST_CHECK(f.subs.get(msg, q, qpid::sys::TIME_SEC)); + BOOST_CHECK_EQUAL((boost::format("%1%_%2%") % "Message" % (i+1)).str(), msg.getData()); + } + BOOST_CHECK(!f.subs.get(msg, q)); + + for (int i = 10; i < 20; i++) { + f.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q)); + } + for (int i = 15; i < 20; i++) { + BOOST_CHECK(f.subs.get(msg, q, qpid::sys::TIME_SEC)); + BOOST_CHECK_EQUAL((boost::format("%1%_%2%") % "Message" % (i+1)).str(), msg.getData()); + } + BOOST_CHECK(!f.subs.get(msg, q)); +} + +QPID_AUTO_TEST_CASE(testRingPolicySize) +{ + //The message size now includes all headers as well as the content + //aka body, so compute the amount of data needed to hit a given + //overall size + std::string q("my-ring-queue"); + size_t minMessageSize = 25/*minimum size of headers*/ + q.size()/*routing key length*/ + 4/*default exchange, added by broker*/; + + std::string hundredBytes = std::string(100 - minMessageSize, 'h'); + std::string fourHundredBytes = std::string (400 - minMessageSize, 'f'); + std::string thousandBytes = std::string(1000 - minMessageSize, 't'); + + // Ring queue, 500 bytes maxSize + + QueueOptions args; + args.setSizePolicy(RING, 500, 0); + + SessionFixture f; + f.session.queueDeclare(arg::queue=q, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args); + + // A. Send messages 0 .. 5, each 100 bytes + + client::Message m(hundredBytes, q); + + for (int i = 0; i < 6; i++) { + std::stringstream id; + id << i; + m.getMessageProperties().setCorrelationId(id.str()); + f.session.messageTransfer(arg::content=m); + } + + // should find 1 .. 5 on the queue, 0 is displaced by 5 + client::Message msg; + for (int i = 1; i < 6; i++) { + std::stringstream id; + id << i; + BOOST_CHECK(f.subs.get(msg, q, qpid::sys::TIME_SEC)); + BOOST_CHECK_EQUAL(msg.getMessageProperties().getCorrelationId(), id.str()); + } + BOOST_CHECK(!f.subs.get(msg, q)); + + // B. Now make sure that one 400 byte message displaces four 100 byte messages + + // Send messages 0 .. 5, each 100 bytes + for (int i = 0; i < 6; i++) { + client::Message m(hundredBytes, q); + std::stringstream id; + id << i; + m.getMessageProperties().setCorrelationId(id.str()); + f.session.messageTransfer(arg::content=m); + } + + // Now send one 400 byte message + client::Message m2(fourHundredBytes, q); + m2.getMessageProperties().setCorrelationId("6"); + f.session.messageTransfer(arg::content=m2); + + // expect to see 5, 6 on the queue + for (int i = 5; i < 7; i++) { + std::stringstream id; + id << i; + BOOST_CHECK(f.subs.get(msg, q, qpid::sys::TIME_SEC)); + BOOST_CHECK_EQUAL(msg.getMessageProperties().getCorrelationId(), id.str()); + } + BOOST_CHECK(!f.subs.get(msg, q)); + + + // C. Try sending a 1000-byte message, should fail - exceeds maxSize of queue + + client::Message m3(thousandBytes, q); + m3.getMessageProperties().setCorrelationId("6"); + try { + ScopedSuppressLogging sl; + f.session.messageTransfer(arg::content=m3); + BOOST_FAIL("Ooops - successfully added a 1000 byte message to a 512 byte ring queue ..."); + } + catch (...) { + } + +} + + +QPID_AUTO_TEST_CASE(testStrictRingPolicy) +{ + QueueOptions args; + args.setSizePolicy(RING_STRICT, 0, 5); + args.setString("qpid.flow_stop_count", "0"); + + SessionFixture f; + std::string q("my-ring-queue"); + f.session.queueDeclare(arg::queue=q, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args); + LocalQueue incoming; + SubscriptionSettings settings(FlowControl::unlimited()); + settings.autoAck = 0; // no auto ack. + Subscription sub = f.subs.subscribe(incoming, q, settings); + for (int i = 0; i < 5; i++) { + f.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q)); + } + for (int i = 0; i < 5; i++) { + BOOST_CHECK_EQUAL(incoming.pop().getData(), (boost::format("%1%_%2%") % "Message" % (i+1)).str()); + } + try { + ScopedSuppressLogging sl; // Suppress messages for expected errors. + f.session.messageTransfer(arg::content=client::Message("Message_6", q)); + BOOST_FAIL("expecting ResourceLimitExceededException."); + } catch (const ResourceLimitExceededException&) {} +} + +QPID_AUTO_TEST_CASE(testPolicyWithDtx) +{ + QueueOptions args; + args.setSizePolicy(REJECT, 0, 5); + + SessionFixture f; + std::string q("my-policy-queue"); + f.session.queueDeclare(arg::queue=q, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args); + LocalQueue incoming; + SubscriptionSettings settings(FlowControl::unlimited()); + settings.autoAck = 0; // no auto ack. + Subscription sub = f.subs.subscribe(incoming, q, settings); + f.session.dtxSelect(); + Xid tx1(1, "test-dtx-mgr", "tx1"); + f.session.dtxStart(arg::xid=tx1); + for (int i = 0; i < 5; i++) { + f.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q)); + } + f.session.dtxEnd(arg::xid=tx1); + f.session.dtxCommit(arg::xid=tx1, arg::onePhase=true); + + Xid tx2(1, "test-dtx-mgr", "tx2"); + f.session.dtxStart(arg::xid=tx2); + for (int i = 0; i < 5; i++) { + BOOST_CHECK_EQUAL(incoming.pop().getData(), (boost::format("%1%_%2%") % "Message" % (i+1)).str()); + } + SequenceSet accepting=sub.getUnaccepted(); + f.session.messageAccept(accepting); + f.session.dtxEnd(arg::xid=tx2); + f.session.dtxPrepare(arg::xid=tx2); + f.session.dtxRollback(arg::xid=tx2); + f.session.messageRelease(accepting); + + Xid tx3(1, "test-dtx-mgr", "tx3"); + f.session.dtxStart(arg::xid=tx3); + for (int i = 0; i < 5; i++) { + incoming.pop(); + } + accepting=sub.getUnaccepted(); + f.session.messageAccept(accepting); + f.session.dtxEnd(arg::xid=tx3); + f.session.dtxPrepare(arg::xid=tx3); + + Session other = f.connection.newSession(); + try { + ScopedSuppressLogging sl; // Suppress messages for expected errors. + other.messageTransfer(arg::content=client::Message("Message_6", q)); + BOOST_FAIL("expecting ResourceLimitExceededException."); + } catch (const ResourceLimitExceededException&) {} + + f.session.dtxCommit(arg::xid=tx3); + //now retry and this time should succeed + other = f.connection.newSession(); + other.messageTransfer(arg::content=client::Message("Message_6", q)); +} + +QPID_AUTO_TEST_CASE(testFlowToDiskWithNoStore) +{ + //Ensure that with no store loaded, we don't flow to disk but + //fallback to rejecting messages + QueueOptions args; + args.setSizePolicy(FLOW_TO_DISK, 0, 5); + // Disable flow control, or else we'll never hit the max limit + args.setInt(QueueFlowLimit::flowStopCountKey, 0); + + SessionFixture f; + std::string q("my-queue"); + f.session.queueDeclare(arg::queue=q, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args); + LocalQueue incoming; + SubscriptionSettings settings(FlowControl::unlimited()); + settings.autoAck = 0; // no auto ack. + Subscription sub = f.subs.subscribe(incoming, q, settings); + for (int i = 0; i < 5; i++) { + f.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q)); + } + for (int i = 0; i < 5; i++) { + BOOST_CHECK_EQUAL(incoming.pop().getData(), (boost::format("%1%_%2%") % "Message" % (i+1)).str()); + } + try { + ScopedSuppressLogging sl; // Suppress messages for expected errors. + f.session.messageTransfer(arg::content=client::Message("Message_6", q)); + BOOST_FAIL("expecting ResourceLimitExceededException."); + } catch (const ResourceLimitExceededException&) {} +} + +QPID_AUTO_TEST_CASE(testPolicyFailureOnCommit) +{ + QueueOptions args; + args.setSizePolicy(REJECT, 0, 5); + + SessionFixture f; + std::string q("q"); + f.session.queueDeclare(arg::queue=q, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args); + f.session.txSelect(); + for (int i = 0; i < 10; i++) { + f.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q)); + } + ScopedSuppressLogging sl; // Suppress messages for expected errors. + BOOST_CHECK_THROW(f.session.txCommit(), InternalErrorException); +} + +QPID_AUTO_TEST_CASE(testCapacityConversion) +{ + FieldTable args; + args.setString("qpid.max_count", "5"); + args.setString("qpid.flow_stop_count", "0"); + + SessionFixture f; + std::string q("q"); + f.session.queueDeclare(arg::queue=q, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args); + for (int i = 0; i < 5; i++) { + f.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q)); + } + try { + ScopedSuppressLogging sl; // Suppress messages for expected errors. + f.session.messageTransfer(arg::content=client::Message("Message_6", q)); + BOOST_FAIL("expecting ResourceLimitExceededException."); + } catch (const ResourceLimitExceededException&) {} +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/QueueRegistryTest.cpp b/qpid/cpp/src/tests/QueueRegistryTest.cpp new file mode 100644 index 0000000000..364d66c525 --- /dev/null +++ b/qpid/cpp/src/tests/QueueRegistryTest.cpp @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "qpid/broker/QueueRegistry.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/QueueSettings.h" +#include "unit_test.h" +#include <string> + +using namespace qpid::broker; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(QueueRegistryTest) + +QPID_AUTO_TEST_CASE(testDeclare) +{ + std::string foo("foo"); + std::string bar("bar"); + QueueRegistry reg; + std::pair<Queue::shared_ptr, bool> qc; + + qc = reg.declare(foo, QueueSettings()); + Queue::shared_ptr q = qc.first; + BOOST_CHECK(q); + BOOST_CHECK(qc.second); // New queue + BOOST_CHECK_EQUAL(foo, q->getName()); + + qc = reg.declare(foo, QueueSettings()); + BOOST_CHECK_EQUAL(q, qc.first); + BOOST_CHECK(!qc.second); + + qc = reg.declare(bar, QueueSettings()); + q = qc.first; + BOOST_CHECK(q); + BOOST_CHECK_EQUAL(true, qc.second); + BOOST_CHECK_EQUAL(bar, q->getName()); +} + +QPID_AUTO_TEST_CASE(testFind) +{ + std::string foo("foo"); + std::string bar("bar"); + QueueRegistry reg; + std::pair<Queue::shared_ptr, bool> qc; + + BOOST_CHECK(reg.find(foo) == 0); + + reg.declare(foo, QueueSettings()); + reg.declare(bar, QueueSettings()); + Queue::shared_ptr q = reg.find(bar); + BOOST_CHECK(q); + BOOST_CHECK_EQUAL(bar, q->getName()); +} + +QPID_AUTO_TEST_CASE(testDestroy) +{ + std::string foo("foo"); + QueueRegistry reg; + std::pair<Queue::shared_ptr, bool> qc; + + qc = reg.declare(foo, QueueSettings()); + reg.destroy(foo); + // Queue is gone from the registry. + BOOST_CHECK(reg.find(foo) == 0); + // Queue is not actually destroyed till we drop our reference. + BOOST_CHECK_EQUAL(foo, qc.first->getName()); + // We shoud be the only reference. + BOOST_CHECK_EQUAL(1L, qc.first.use_count()); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/QueueTest.cpp b/qpid/cpp/src/tests/QueueTest.cpp new file mode 100644 index 0000000000..ee9d37e76d --- /dev/null +++ b/qpid/cpp/src/tests/QueueTest.cpp @@ -0,0 +1,629 @@ + /* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "MessageUtils.h" +#include "unit_test.h" +#include "test_tools.h" +#include "qpid/Exception.h" +#include "qpid/broker/Broker.h" +#include "qpid/broker/DeliverableMessage.h" +#include "qpid/broker/FanOutExchange.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/Deliverable.h" +#include "qpid/broker/ExchangeRegistry.h" +#include "qpid/broker/QueueRegistry.h" +#include "qpid/broker/NullMessageStore.h" +#include "qpid/framing/DeliveryProperties.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/client/QueueOptions.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/broker/QueueFlowLimit.h" +#include "qpid/broker/QueueSettings.h" +#include "qpid/sys/Thread.h" +#include "qpid/sys/Timer.h" + +#include <iostream> +#include <vector> +#include <boost/format.hpp> +#include <boost/lexical_cast.hpp> + +using namespace std; +using boost::intrusive_ptr; +using namespace qpid; +using namespace qpid::broker; +using namespace qpid::client; +using namespace qpid::framing; +using namespace qpid::sys; + +namespace qpid { +namespace tests { +class TestConsumer : public virtual Consumer{ +public: + typedef boost::shared_ptr<TestConsumer> shared_ptr; + + QueueCursor lastCursor; + Message lastMessage; + bool received; + TestConsumer(std::string name="test", bool acquire = true) : Consumer(name, acquire ? CONSUMER : BROWSER, ""), received(false) {}; + + virtual bool deliver(const QueueCursor& cursor, const Message& message){ + lastCursor = cursor; + lastMessage = message; + received = true; + return true; + }; + void notify() {} + void cancel() {} + void acknowledged(const DeliveryRecord&) {} + OwnershipToken* getSession() { return 0; } +}; + +class FailOnDeliver : public Deliverable +{ + Message msg; +public: + FailOnDeliver() : msg(MessageUtils::createMessage()) {} + void deliverTo(const boost::shared_ptr<Queue>& queue) + { + throw Exception(QPID_MSG("Invalid delivery to " << queue->getName())); + } + Message& getMessage() { return msg; } +}; + +QPID_AUTO_TEST_SUITE(QueueTestSuite) + +QPID_AUTO_TEST_CASE(testBound){ + //test the recording of bindings, and use of those to allow a queue to be unbound + string key("my-key"); + FieldTable args; + + Queue::shared_ptr queue(new Queue("my-queue")); + ExchangeRegistry exchanges; + //establish bindings from exchange->queue and notify the queue as it is bound: + Exchange::shared_ptr exchange1 = exchanges.declare("my-exchange-1", "direct").first; + exchange1->bind(queue, key, &args); + queue->bound(exchange1->getName(), key, args); + + Exchange::shared_ptr exchange2 = exchanges.declare("my-exchange-2", "fanout").first; + exchange2->bind(queue, key, &args); + queue->bound(exchange2->getName(), key, args); + + Exchange::shared_ptr exchange3 = exchanges.declare("my-exchange-3", "topic").first; + exchange3->bind(queue, key, &args); + queue->bound(exchange3->getName(), key, args); + + //delete one of the exchanges: + exchanges.destroy(exchange2->getName()); + exchange2.reset(); + + //unbind the queue from all exchanges it knows it has been bound to: + queue->unbind(exchanges); + + //ensure the remaining exchanges don't still have the queue bound to them: + FailOnDeliver deliverable; + exchange1->route(deliverable); + exchange3->route(deliverable); +} + +QPID_AUTO_TEST_CASE(testLVQ){ + + QueueSettings settings; + string key="key"; + settings.lvqKey = key; + QueueFactory factory; + Queue::shared_ptr q(factory.create("my-queue", settings)); + + const char* values[] = { "a", "b", "c", "a"}; + for (size_t i = 0; i < sizeof(values)/sizeof(values[0]); ++i) { + qpid::types::Variant::Map properties; + properties[key] = values[i]; + q->deliver(MessageUtils::createMessage(properties, boost::lexical_cast<string>(i+1))); + } + BOOST_CHECK_EQUAL(q->getMessageCount(), 3u); + + TestConsumer::shared_ptr c(new TestConsumer("test", true)); + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(std::string("2"), c->lastMessage.getContent()); + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(std::string("3"), c->lastMessage.getContent()); + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(std::string("4"), c->lastMessage.getContent()); + + + const char* values2[] = { "a", "b", "c"}; + for (size_t i = 0; i < sizeof(values2)/sizeof(values2[0]); ++i) { + qpid::types::Variant::Map properties; + properties[key] = values[i]; + q->deliver(MessageUtils::createMessage(properties, boost::lexical_cast<string>(i+5))); + } + BOOST_CHECK_EQUAL(q->getMessageCount(), 3u); + + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(std::string("5"), c->lastMessage.getContent()); + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(std::string("6"), c->lastMessage.getContent()); + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(std::string("7"), c->lastMessage.getContent()); +} + +QPID_AUTO_TEST_CASE(testLVQEmptyKey){ + + QueueSettings settings; + string key="key"; + settings.lvqKey = key; + QueueFactory factory; + Queue::shared_ptr q(factory.create("my-queue", settings)); + + + qpid::types::Variant::Map properties; + properties["key"] = "a"; + q->deliver(MessageUtils::createMessage(properties, "one")); + properties.clear(); + q->deliver(MessageUtils::createMessage(properties, "two")); + BOOST_CHECK_EQUAL(q->getMessageCount(), 2u); +} + +void addMessagesToQueue(uint count, Queue& queue, uint oddTtl = 200, uint evenTtl = 0) +{ + for (uint i = 0; i < count; i++) { + Message m = MessageUtils::createMessage("exchange", "key", i % 2 ? oddTtl : evenTtl); + queue.deliver(m); + } +} + +QPID_AUTO_TEST_CASE(testPurgeExpired) { + Queue queue("my-queue"); + addMessagesToQueue(10, queue); + BOOST_CHECK_EQUAL(queue.getMessageCount(), 10u); + ::usleep(300*1000); + queue.purgeExpired(0); + BOOST_CHECK_EQUAL(queue.getMessageCount(), 5u); +} + +QPID_AUTO_TEST_CASE(testQueueCleaner) { + boost::shared_ptr<Poller> poller(new Poller); + Thread runner(poller.get()); + Timer timer; + QueueRegistry queues; + Queue::shared_ptr queue = queues.declare("my-queue", QueueSettings()).first; + addMessagesToQueue(10, *queue, 200, 400); + BOOST_CHECK_EQUAL(queue->getMessageCount(), 10u); + + QueueCleaner cleaner(queues, poller, &timer); + cleaner.start(100 * qpid::sys::TIME_MSEC); + ::usleep(300*1000); + BOOST_CHECK_EQUAL(queue->getMessageCount(), 5u); + ::usleep(300*1000); + BOOST_CHECK_EQUAL(queue->getMessageCount(), 0u); + poller->shutdown(); + runner.join(); +} +namespace { +int getIntProperty(const Message& message, const std::string& key) +{ + qpid::types::Variant v = message.getProperty(key); + int i(0); + if (!v.isVoid()) i = v; + return i; +} +// helper for group tests +void verifyAcquire( Queue::shared_ptr queue, + TestConsumer::shared_ptr c, + std::deque<QueueCursor>& results, + const std::string& expectedGroup, + const int expectedId ) +{ + bool success = queue->dispatch(c); + BOOST_CHECK(success); + if (success) { + results.push_back(c->lastCursor); + std::string group = c->lastMessage.getPropertyAsString("GROUP-ID"); + int id = getIntProperty(c->lastMessage, "MY-ID"); + BOOST_CHECK_EQUAL( group, expectedGroup ); + BOOST_CHECK_EQUAL( id, expectedId ); + } +} + +Message createGroupMessage(int id, const std::string& group) +{ + qpid::types::Variant::Map properties; + properties["GROUP-ID"] = group; + properties["MY-ID"] = id; + return MessageUtils::createMessage(properties); +} +} + +QPID_AUTO_TEST_CASE(testGroupsMultiConsumer) { + // + // Verify that consumers of grouped messages own the groups once a message is acquired, + // and release the groups once all acquired messages have been dequeued or requeued + // + QueueSettings settings; + settings.shareGroups = 1; + settings.groupKey = "GROUP-ID"; + QueueFactory factory; + Queue::shared_ptr queue(factory.create("my_queue", settings)); + + std::string groups[] = { std::string("a"), std::string("a"), std::string("a"), + std::string("b"), std::string("b"), std::string("b"), + std::string("c"), std::string("c"), std::string("c") }; + for (int i = 0; i < 9; ++i) { + queue->deliver(createGroupMessage(i, groups[i])); + } + + // Queue = a-0, a-1, a-2, b-3, b-4, b-5, c-6, c-7, c-8... + // Owners= ---, ---, ---, ---, ---, ---, ---, ---, ---, + + BOOST_CHECK_EQUAL(uint32_t(9), queue->getMessageCount()); + + TestConsumer::shared_ptr c1(new TestConsumer("C1")); + TestConsumer::shared_ptr c2(new TestConsumer("C2")); + + queue->consume(c1); + queue->consume(c2); + + std::deque<QueueCursor> dequeMeC1; + std::deque<QueueCursor> dequeMeC2; + + + verifyAcquire(queue, c1, dequeMeC1, "a", 0 ); // c1 now owns group "a" (acquire a-0) + verifyAcquire(queue, c2, dequeMeC2, "b", 3 ); // c2 should now own group "b" (acquire b-3) + + // now let c1 complete the 'a-0' message - this should free the 'a' group + queue->dequeue( 0, dequeMeC1.front() ); + dequeMeC1.pop_front(); + + // Queue = a-1, a-2, b-3, b-4, b-5, c-6, c-7, c-8... + // Owners= ---, ---, ^C2, ^C2, ^C2, ---, ---, --- + + // now c2 should pick up the next 'a-1', since it is oldest free + verifyAcquire(queue, c2, dequeMeC2, "a", 1 ); // c2 should now own groups "a" and "b" + + // Queue = a-1, a-2, b-3, b-4, b-5, c-6, c-7, c-8... + // Owners= ^C2, ^C2, ^C2, ^C2, ^C2, ---, ---, --- + + // c1 should only be able to snarf up the first "c" message now... + verifyAcquire(queue, c1, dequeMeC1, "c", 6 ); // should skip to the first "c" + + // Queue = a-1, a-2, b-3, b-4, b-5, c-6, c-7, c-8... + // Owners= ^C2, ^C2, ^C2, ^C2, ^C2, ^C1, ^C1, ^C1 + + // hmmm... what if c2 now dequeues "b-3"? (now only has a-1 acquired) + queue->dequeue( 0, dequeMeC2.front() ); + dequeMeC2.pop_front(); + + // Queue = a-1, a-2, b-4, b-5, c-6, c-7, c-8... + // Owners= ^C2, ^C2, ---, ---, ^C1, ^C1, ^C1 + + // b group is free, c is owned by c1 - c1's next get should grab 'b-4' + verifyAcquire(queue, c1, dequeMeC1, "b", 4 ); + + // Queue = a-1, a-2, b-4, b-5, c-6, c-7, c-8... + // Owners= ^C2, ^C2, ^C1, ^C1, ^C1, ^C1, ^C1 + + // c2 can now only grab a-2, and that's all + verifyAcquire(queue, c2, dequeMeC2, "a", 2 ); + + // now C2 can't get any more, since C1 owns "b" and "c" group... + bool gotOne = queue->dispatch(c2); + BOOST_CHECK( !gotOne ); + + // hmmm... what if c1 now dequeues "c-6"? (now only own's b-4) + queue->dequeue( 0, dequeMeC1.front() ); + dequeMeC1.pop_front(); + + // Queue = a-1, a-2, b-4, b-5, c-7, c-8... + // Owners= ^C2, ^C2, ^C1, ^C1, ---, --- + + // c2 can now grab c-7 + verifyAcquire(queue, c2, dequeMeC2, "c", 7 ); + + // Queue = a-1, a-2, b-4, b-5, c-7, c-8... + // Owners= ^C2, ^C2, ^C1, ^C1, ^C2, ^C2 + + // what happens if C-2 "requeues" a-1 and a-2? + queue->release( dequeMeC2.front() ); + dequeMeC2.pop_front(); + queue->release( dequeMeC2.front() ); + dequeMeC2.pop_front(); // now just has c-7 acquired + + // Queue = a-1, a-2, b-4, b-5, c-7, c-8... + // Owners= ---, ---, ^C1, ^C1, ^C2, ^C2 + + // now c1 will grab a-1 and a-2... + verifyAcquire(queue, c1, dequeMeC1, "a", 1 ); + verifyAcquire(queue, c1, dequeMeC1, "a", 2 ); + + // Queue = a-1, a-2, b-4, b-5, c-7, c-8... + // Owners= ^C1, ^C1, ^C1, ^C1, ^C2, ^C2 + + // c2 can now acquire c-8 only + verifyAcquire(queue, c2, dequeMeC2, "c", 8 ); + + // and c1 can get b-5 + verifyAcquire(queue, c1, dequeMeC1, "b", 5 ); + + // should be no more acquire-able for anyone now: + gotOne = queue->dispatch(c1); + BOOST_CHECK( !gotOne ); + gotOne = queue->dispatch(c2); + BOOST_CHECK( !gotOne ); + + // release all of C1's acquired messages, then cancel C1 + while (!dequeMeC1.empty()) { + queue->release(dequeMeC1.front()); + dequeMeC1.pop_front(); + } + queue->cancel(c1); + + // Queue = a-1, a-2, b-4, b-5, c-7, c-8... + // Owners= ---, ---, ---, ---, ^C2, ^C2 + + // b-4, a-1, a-2, b-5 all should be available, right? + verifyAcquire(queue, c2, dequeMeC2, "a", 1 ); + + while (!dequeMeC2.empty()) { + queue->dequeue(0, dequeMeC2.front()); + dequeMeC2.pop_front(); + } + + // Queue = a-2, b-4, b-5 + // Owners= ---, ---, --- + + TestConsumer::shared_ptr c3(new TestConsumer("C3")); + queue->consume(c3); + std::deque<QueueCursor> dequeMeC3; + + verifyAcquire(queue, c3, dequeMeC3, "a", 2 ); + verifyAcquire(queue, c2, dequeMeC2, "b", 4 ); + + // Queue = a-2, b-4, b-5 + // Owners= ^C3, ^C2, ^C2 + + gotOne = queue->dispatch(c3); + BOOST_CHECK( !gotOne ); + + verifyAcquire(queue, c2, dequeMeC2, "b", 5 ); + + while (!dequeMeC2.empty()) { + queue->dequeue(0, dequeMeC2.front()); + dequeMeC2.pop_front(); + } + + // Queue = a-2, + // Owners= ^C3, + queue->deliver(createGroupMessage(9, "a")); + + // Queue = a-2, a-9 + // Owners= ^C3, ^C3 + + gotOne = queue->dispatch(c2); + BOOST_CHECK( !gotOne ); + + queue->deliver(createGroupMessage(10, "b")); + + // Queue = a-2, a-9, b-10 + // Owners= ^C3, ^C3, ---- + + verifyAcquire(queue, c2, dequeMeC2, "b", 10 ); + verifyAcquire(queue, c3, dequeMeC3, "a", 9 ); + + gotOne = queue->dispatch(c3); + BOOST_CHECK( !gotOne ); + + queue->cancel(c2); + queue->cancel(c3); +} + + +QPID_AUTO_TEST_CASE(testGroupsMultiConsumerDefaults) { + // + // Verify that the same default group name is automatically applied to messages that + // do not specify a group name. + // + QueueSettings settings; + settings.shareGroups = 1; + settings.groupKey = "GROUP-ID"; + QueueFactory factory; + Queue::shared_ptr queue(factory.create("my_queue", settings)); + + for (int i = 0; i < 3; ++i) { + qpid::types::Variant::Map properties; + // no "GROUP-ID" header + properties["MY-ID"] = i; + queue->deliver(MessageUtils::createMessage(properties)); + } + + // Queue = 0, 1, 2 + + BOOST_CHECK_EQUAL(uint32_t(3), queue->getMessageCount()); + + TestConsumer::shared_ptr c1(new TestConsumer("C1")); + TestConsumer::shared_ptr c2(new TestConsumer("C2")); + + queue->consume(c1); + queue->consume(c2); + + std::deque<QueueCursor> dequeMeC1; + std::deque<QueueCursor> dequeMeC2; + + queue->dispatch(c1); // c1 now owns default group (acquired 0) + dequeMeC1.push_back(c1->lastCursor); + int id = getIntProperty(c1->lastMessage, "MY-ID"); + BOOST_CHECK_EQUAL( id, 0 ); + + bool gotOne = queue->dispatch(c2); // c2 should get nothing + BOOST_CHECK( !gotOne ); + + queue->dispatch(c1); // c1 now acquires 1 + dequeMeC1.push_back(c1->lastCursor); + id = getIntProperty(c1->lastMessage, "MY-ID"); + BOOST_CHECK_EQUAL( id, 1 ); + + gotOne = queue->dispatch(c2); // c2 should still get nothing + BOOST_CHECK( !gotOne ); + + while (!dequeMeC1.empty()) { + queue->dequeue(0, dequeMeC1.front()); + dequeMeC1.pop_front(); + } + + // now default group should be available... + queue->dispatch(c2); // c2 now owns default group (acquired 2) + id = c2->lastMessage.getProperty("MY-ID"); + BOOST_CHECK_EQUAL( id, 2 ); + + gotOne = queue->dispatch(c1); // c1 should get nothing + BOOST_CHECK( !gotOne ); + + queue->cancel(c1); + queue->cancel(c2); +} + +QPID_AUTO_TEST_CASE(testSetPositionFifo) { + Queue::shared_ptr q(new Queue("my-queue", true)); + BOOST_CHECK_EQUAL(q->getPosition(), SequenceNumber(0)); + for (int i = 0; i < 10; ++i) + q->deliver(MessageUtils::createMessage(qpid::types::Variant::Map(), boost::lexical_cast<string>(i+1))); + + // Verify the front of the queue + TestConsumer::shared_ptr c(new TestConsumer("test", false)); // Don't acquire + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(1u, c->lastMessage.getSequence()); // Numbered from 1 + BOOST_CHECK_EQUAL("1", c->lastMessage.getContent()); + + // Verify the back of the queue + BOOST_CHECK_EQUAL(10u, q->getPosition()); + BOOST_CHECK_EQUAL(10u, q->getMessageCount()); + + // Using setPosition to introduce a gap in sequence numbers. + q->setPosition(15); + BOOST_CHECK_EQUAL(10u, q->getMessageCount()); + BOOST_CHECK_EQUAL(15u, q->getPosition()); + q->deliver(MessageUtils::createMessage(qpid::types::Variant::Map(), "16")); + + q->seek(*c, Queue::MessagePredicate(), 9); + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(10u, c->lastMessage.getSequence()); + BOOST_CHECK_EQUAL("10", c->lastMessage.getContent()); + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(16u, c->lastMessage.getSequence()); + BOOST_CHECK_EQUAL("16", c->lastMessage.getContent()); + + // Using setPosition to trunkcate the queue + q->setPosition(5); + BOOST_CHECK_EQUAL(5u, q->getMessageCount()); + q->deliver(MessageUtils::createMessage(qpid::types::Variant::Map(), "6a")); + c = boost::shared_ptr<TestConsumer>(new TestConsumer("test", false)); // Don't acquire + q->seek(*c, Queue::MessagePredicate(), 4); + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(5u, c->lastMessage.getSequence()); + BOOST_CHECK_EQUAL("5", c->lastMessage.getContent()); + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(6u, c->lastMessage.getSequence()); + BOOST_CHECK_EQUAL("6a", c->lastMessage.getContent()); + BOOST_CHECK(!q->dispatch(c)); // No more messages. +} + +QPID_AUTO_TEST_CASE(testSetPositionLvq) { + QueueSettings settings; + string key="key"; + settings.lvqKey = key; + QueueFactory factory; + Queue::shared_ptr q(factory.create("my-queue", settings)); + + const char* values[] = { "a", "b", "c", "a", "b", "c" }; + for (size_t i = 0; i < sizeof(values)/sizeof(values[0]); ++i) { + qpid::types::Variant::Map properties; + properties[key] = values[i]; + q->deliver(MessageUtils::createMessage(properties, boost::lexical_cast<string>(i+1))); + } + BOOST_CHECK_EQUAL(3u, q->getMessageCount()); + // Verify the front of the queue + TestConsumer::shared_ptr c(new TestConsumer("test", false)); // Don't acquire + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(4u, c->lastMessage.getSequence()); // Numbered from 1 + BOOST_CHECK_EQUAL("4", c->lastMessage.getContent()); + // Verify the back of the queue + BOOST_CHECK_EQUAL(6u, q->getPosition()); + + q->setPosition(5); + + c = boost::shared_ptr<TestConsumer>(new TestConsumer("test", false)); // Don't acquire + q->seek(*c, Queue::MessagePredicate(), 4); + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(5u, c->lastMessage.getSequence()); // Numbered from 1 + BOOST_CHECK(!q->dispatch(c)); +} + +QPID_AUTO_TEST_CASE(testSetPositionPriority) { + QueueSettings settings; + settings.priorities = 10; + QueueFactory factory; + Queue::shared_ptr q(factory.create("my-queue", settings)); + + const int priorities[] = { 1, 2, 3, 2, 1, 3 }; + for (size_t i = 0; i < sizeof(priorities)/sizeof(priorities[0]); ++i) { + qpid::types::Variant::Map properties; + properties["priority"] = priorities[i]; + q->deliver(MessageUtils::createMessage(properties, boost::lexical_cast<string>(i+1))); + } + + // Truncation removes messages in fifo order, not priority order. + q->setPosition(3); + TestConsumer::shared_ptr c(new TestConsumer("test", false)); // Browse in priority order + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(3u, c->lastMessage.getSequence()); + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(2u, c->lastMessage.getSequence()); + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(1u, c->lastMessage.getSequence()); + BOOST_CHECK(!q->dispatch(c)); + + qpid::types::Variant::Map properties; + properties["priority"] = 4; + q->deliver(MessageUtils::createMessage(properties, "4a")); + + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(4u, c->lastMessage.getSequence()); + BOOST_CHECK_EQUAL("4a", c->lastMessage.getContent()); + + // But consumers see priority order + c.reset(new TestConsumer("test", true)); + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(4u, c->lastMessage.getSequence()); + BOOST_CHECK_EQUAL("4a", c->lastMessage.getContent()); + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(3u, c->lastMessage.getSequence()); + BOOST_CHECK_EQUAL("3", c->lastMessage.getContent()); + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(2u, c->lastMessage.getSequence()); + BOOST_CHECK_EQUAL("2", c->lastMessage.getContent()); + BOOST_CHECK(q->dispatch(c)); + BOOST_CHECK_EQUAL(1u, c->lastMessage.getSequence()); + BOOST_CHECK_EQUAL("1", c->lastMessage.getContent()); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/README.txt b/qpid/cpp/src/tests/README.txt new file mode 100644 index 0000000000..8eaa5bbd25 --- /dev/null +++ b/qpid/cpp/src/tests/README.txt @@ -0,0 +1,37 @@ += Running Qpid C++ tests = + +General philosophy is that "make test" run all tests by default, but +developers can run tests selectively as explained below. + +== Unit Tests == + +Unit tests use the boost test framework, and are compiled to the programd +unit_test + +There are several options to control how test results are displayed, see + http://www.boost.org/doc/libs/1_35_0/libs/test/doc/components/utf/parameters/index.html + +== System Tests == + +System tests are executables or scripts. You can run executable tests directly +as well as via "make test" or "ctest". Some tests require environment settings +which are set by src/tests/test_env.sh on Unix or by src/tests/test_env.ps1 on +Windows. + +./python_tests: runs ../python/run_tests. This is the main set of +system tests for the broker. + +Other C++ client test executables and scripts under client/test are +system tests for the client. + +== Running selected tests == + +The make target "make test" simply runs the command "ctest". Running ctest +directly gives you additional options, e.g. + + ctest -R <regexp> -VV + +This runs tests with names matching the regular expression <regexp> and will +print the full output of the tests rather than just listing which tests pass or +fail. + diff --git a/qpid/cpp/src/tests/RangeSet.cpp b/qpid/cpp/src/tests/RangeSet.cpp new file mode 100644 index 0000000000..285f432bf7 --- /dev/null +++ b/qpid/cpp/src/tests/RangeSet.cpp @@ -0,0 +1,154 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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. + * + */ + + +#include "unit_test.h" +#include "test_tools.h" +#include "qpid/RangeSet.h" + +using namespace std; +using namespace qpid; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(RangeSetTestSuite) + +typedef qpid::Range<int> TR; // Test Range +typedef RangeSet<int> TRSet; + +QPID_AUTO_TEST_CASE(testEmptyRange) { + TR r; + BOOST_CHECK_EQUAL(r, TR(0,0)); + BOOST_CHECK(r.empty()); + BOOST_CHECK(!r.contains(0)); +} + +QPID_AUTO_TEST_CASE(testRangeSetAddPoint) { + TRSet r; + BOOST_CHECK(r.empty()); + r += 3; + BOOST_CHECK_MESSAGE(r.contains(3), r); + BOOST_CHECK_MESSAGE(r.contains(TR(3,4)), r); + BOOST_CHECK(!r.empty()); + r += 5; + BOOST_CHECK_MESSAGE(r.contains(5), r); + BOOST_CHECK_MESSAGE(r.contains(TR(5,6)), r); + BOOST_CHECK_MESSAGE(!r.contains(TR(3,6)), r); + r += 4; + BOOST_CHECK_MESSAGE(r.contains(TR(3,6)), r); +} + +QPID_AUTO_TEST_CASE(testRangeSetAddRange) { + TRSet r; + r += TR(0,3); + BOOST_CHECK(r.contains(TR(0,3))); + BOOST_CHECK(r.contiguous()); + r += TR(4,6); + BOOST_CHECK(!r.contiguous()); + BOOST_CHECK_MESSAGE(r.contains(TR(4,6)), r); + r += 3; + BOOST_CHECK_MESSAGE(r.contains(TR(0,6)), r); + BOOST_CHECK(r.front() == 0); + BOOST_CHECK(r.back() == 6); + + // Merging additions + r = TRSet(0,3)+TR(5,6); + TRSet e(0,6); + BOOST_CHECK_EQUAL(r + TR(3,5), e); + BOOST_CHECK(e.contiguous()); + r = TRSet(0,5)+TR(10,15)+TR(20,25)+TR(30,35)+TR(40,45); + BOOST_CHECK_EQUAL(r + TR(11,37), TRSet(0,5)+TR(11,37)+TR(40,45)); +} + +QPID_AUTO_TEST_CASE(testRangeSetAddSet) { + TRSet r; + TRSet s = TRSet(0,3)+TR(5,10); + r += s; + BOOST_CHECK_EQUAL(r,s); + r += TRSet(3,5) + TR(7,12) + 15; + BOOST_CHECK_EQUAL(r, TRSet(0,12) + 15); + + r.clear(); + BOOST_CHECK(r.empty()); + r += TR::makeClosed(6,10); + BOOST_CHECK_EQUAL(r, TRSet(6,11)); + r += TRSet(2,6)+8; + BOOST_CHECK_EQUAL(r, TRSet(2,11)); +} + +QPID_AUTO_TEST_CASE(testRangeSetIterate) { + TRSet r = TRSet(1,3)+TR(4,7)+TR(10,11); + std::vector<int> actual; + std::copy(r.begin(), r.end(), std::back_inserter(actual)); + std::vector<int> expect = boost::assign::list_of(1)(2)(4)(5)(6)(10); + BOOST_CHECK_EQUAL(expect, actual); +} + +QPID_AUTO_TEST_CASE(testRangeSetRemove) { + // points + BOOST_CHECK_EQUAL(TRSet(0,5)-3, TRSet(0,3)+TR(4,5)); + BOOST_CHECK_EQUAL(TRSet(1,5)-5, TRSet(1,5)); + BOOST_CHECK_EQUAL(TRSet(1,5)-0, TRSet(1,5)); + + TRSet r(TRSet(0,5)+TR(10,15)+TR(20,25)); + + // TRs + BOOST_CHECK_EQUAL(r-TR(0,5), TRSet(10,15)+TR(20,25)); + BOOST_CHECK_EQUAL(r-TR(10,15), TRSet(0,5)+TR(20,25)); + BOOST_CHECK_EQUAL(r-TR(20,25), TRSet(0,5)+TR(10,15)); + + BOOST_CHECK_EQUAL(r-TR(-5, 30), TRSet()); + + BOOST_CHECK_EQUAL(r-TR(-5, 7), TRSet(10,15)+TR(20,25)); + BOOST_CHECK_EQUAL(r-TR(8,19), TRSet(0,5)+TR(20,25)); + BOOST_CHECK_EQUAL(r-TR(17,30), TRSet(0,5)+TR(10,15)); + + BOOST_CHECK_EQUAL(r-TR(-5, 5), TRSet(10,15)+TR(20,25)); + BOOST_CHECK_EQUAL(r-TR(10,19), TRSet(0,5)+TR(20,25)); + BOOST_CHECK_EQUAL(r-TR(18,25), TRSet(0,5)+TR(10,15)); + BOOST_CHECK_EQUAL(r-TR(23,25), TRSet(0,5)+TR(10,15)+TR(20,23)); + + BOOST_CHECK_EQUAL(r-TR(-3, 3), TRSet(3,5)+TR(10,15)+TR(20,25)); + BOOST_CHECK_EQUAL(r-TR(3, 7), TRSet(0,2)+TR(10,15)+TR(20,25)); + BOOST_CHECK_EQUAL(r-TR(3, 12), TRSet(0,3)+TR(12,15)+TR(20,25)); + BOOST_CHECK_EQUAL(r-TR(3, 22), TRSet(12,15)+TR(22,25)); + BOOST_CHECK_EQUAL(r-TR(12, 22), TRSet(0,5)+TR(10,11)+TR(22,25)); + + // Sets + BOOST_CHECK_EQUAL(r-(TRSet(-1,6)+TR(11,14)+TR(23,25)), + TRSet(10,11)+TR(14,15)+TR(20,23)); + // Split the ranges + BOOST_CHECK_EQUAL(r-(TRSet(2,3)+TR(11,13)+TR(21,23)), + TRSet(0,2)+TR(4,5)+ + TR(10,11)+TR(14,15)+ + TR(20,21)+TR(23,25)); + // Truncate the ranges + BOOST_CHECK_EQUAL(r-(TRSet(0,3)+TR(13,15)+TR(19,23)), + TRSet(3,5)+TR(10,13)+TR(20,23)); + // Remove multiple ranges with truncation + BOOST_CHECK_EQUAL(r-(TRSet(3,23)), TRSet(0,3)+TR(23,25)); + // Remove multiple ranges in middle + TRSet r2 = TRSet(0,5)+TR(10,15)+TR(20,25)+TR(30,35); + BOOST_CHECK_EQUAL(r2-TRSet(11,24), + TRSet(0,5)+TR(10,11)+TR(24,25)+TR(30,35)); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/RefCounted.cpp b/qpid/cpp/src/tests/RefCounted.cpp new file mode 100644 index 0000000000..3ac3895322 --- /dev/null +++ b/qpid/cpp/src/tests/RefCounted.cpp @@ -0,0 +1,55 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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. + * + */ + +#include "qpid/RefCounted.h" +#include <boost/intrusive_ptr.hpp> + +#include "unit_test.h" + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(RefCountedTestSuiteTestSuite) + +using boost::intrusive_ptr; +using namespace std; +using namespace qpid; + +struct CountMe : public RefCounted { + static int instances; + CountMe() { ++instances; } + ~CountMe() { --instances; } +}; + +int CountMe::instances=0; + +QPID_AUTO_TEST_CASE(testRefCounted) { + BOOST_CHECK_EQUAL(0, CountMe::instances); + intrusive_ptr<CountMe> p(new CountMe()); + BOOST_CHECK_EQUAL(1, CountMe::instances); + intrusive_ptr<CountMe> q(p); + BOOST_CHECK_EQUAL(1, CountMe::instances); + q=0; + BOOST_CHECK_EQUAL(1, CountMe::instances); + p=0; + BOOST_CHECK_EQUAL(0, CountMe::instances); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/RetryList.cpp b/qpid/cpp/src/tests/RetryList.cpp new file mode 100644 index 0000000000..50cd5edfe8 --- /dev/null +++ b/qpid/cpp/src/tests/RetryList.cpp @@ -0,0 +1,111 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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. + * + */ + + +#include "unit_test.h" +#include "test_tools.h" +#include "qpid/broker/RetryList.h" + +using namespace qpid; +using namespace qpid::broker; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(RetryListTestSuite) + +struct RetryListFixture +{ + RetryList list; + std::vector<Url> urls; + std::vector<Address> expected; + + void addUrl(const std::string& s) + { + urls.push_back(Url(s)); + } + + void addExpectation(const std::string& host, uint16_t port) + { + expected.push_back(Address("tcp", host, port)); + } + + void check() + { + list.reset(urls); + for (int t = 0; t < 2; t++) { + Address next; + for (std::vector<Address>::const_iterator i = expected.begin(); i != expected.end(); ++i) { + BOOST_CHECK(list.next(next)); + BOOST_CHECK_EQUAL(i->host, next.host); + BOOST_CHECK_EQUAL(i->port, next.port); + } + BOOST_CHECK(!list.next(next)); + } + } +}; + +QPID_AUTO_TEST_CASE(testWithSingleAddress) +{ + RetryListFixture test; + test.addUrl("amqp:host:5673"); + test.addExpectation("host", 5673); + test.check(); +} + +QPID_AUTO_TEST_CASE(testWithSingleUrlOfMultipleAddresses) +{ + RetryListFixture test; + test.addUrl("amqp:host1,host2:2222,tcp:host3:5673,host4:1"); + + test.addExpectation("host1", 5672); + test.addExpectation("host2", 2222); + test.addExpectation("host3", 5673); + test.addExpectation("host4", 1); + + test.check(); +} + +QPID_AUTO_TEST_CASE(testWithMultipleUrlsOfMultipleAddresses) +{ + RetryListFixture test; + test.addUrl("amqp:my-host"); + test.addUrl("amqp:host1:6666,host2:2222,tcp:host3:5673,host4:1"); + test.addUrl("amqp:host5,host6:2222,tcp:host7:5673"); + + test.addExpectation("my-host", 5672); + test.addExpectation("host1", 6666); + test.addExpectation("host2", 2222); + test.addExpectation("host3", 5673); + test.addExpectation("host4", 1); + test.addExpectation("host5", 5672); + test.addExpectation("host6", 2222); + test.addExpectation("host7", 5673); + + test.check(); +} + +QPID_AUTO_TEST_CASE(testEmptyList) +{ + RetryListFixture test; + test.check(); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/Selector.cpp b/qpid/cpp/src/tests/Selector.cpp new file mode 100644 index 0000000000..73af1a9623 --- /dev/null +++ b/qpid/cpp/src/tests/Selector.cpp @@ -0,0 +1,442 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/broker/SelectorToken.h" +#include "qpid/broker/Selector.h" +#include "qpid/broker/SelectorValue.h" + +#include "unit_test.h" + +#include <string> +#include <map> +#include <boost/ptr_container/ptr_vector.hpp> + +using std::string; +using std::map; +using std::vector; + +namespace qb = qpid::broker; + +using qpid::broker::Token; +using qpid::broker::TokenType; +using qpid::broker::Tokeniser; +using qpid::broker::tokenise; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(SelectorSuite) + +typedef bool (*TokeniseF)(string::const_iterator&,string::const_iterator&,Token&); + +bool tokeniseEos(std::string::const_iterator& s, std::string::const_iterator& e, Token& tok) +{ + Token t1; + std::string::const_iterator t = s; + bool r = tokenise(t, e, t1); + if (r && (t1.type==qb::T_EOS)) {tok = t1; s = t; return true;} + return false; +} + +bool tokeniseParens(std::string::const_iterator& s, std::string::const_iterator& e, Token& tok) +{ + Token t1; + std::string::const_iterator t = s; + bool r = tokenise(t, e, t1); + if (r && (t1.type==qb::T_LPAREN || t1.type==qb::T_RPAREN)) {tok = t1; s = t; return true;} + return false; +} + +bool tokeniseOperator(std::string::const_iterator& s, std::string::const_iterator& e, Token& tok) +{ + Token t1; + std::string::const_iterator t = s; + bool r = tokenise(t, e, t1); + if (r && (t1.type>=qb::T_PLUS && t1.type<=qb::T_GREQ)) {tok = t1; s = t; return true;} + return false; +} + +bool tokeniseString(std::string::const_iterator& s, std::string::const_iterator& e, Token& tok) +{ + Token t1; + std::string::const_iterator t = s; + bool r = tokenise(t, e, t1); + if (r && (t1.type==qb::T_STRING)) {tok = t1; s = t; return true;} + return false; +} + +bool tokeniseIdentifier(std::string::const_iterator& s, std::string::const_iterator& e, Token& tok) +{ + Token t1; + std::string::const_iterator t = s; + bool r = tokenise(t, e, t1); + if (r && (t1.type==qb::T_IDENTIFIER)) {tok = t1; s = t; return true;} + return false; +} + +bool tokeniseReservedWord(std::string::const_iterator& s, std::string::const_iterator& e, Token& tok) +{ + std::string::const_iterator t = s; + Token t1; + if (tokenise(t, e, t1)) { + switch (t1.type) { + case qb::T_AND: + case qb::T_BETWEEN: + case qb::T_ESCAPE: + case qb::T_FALSE: + case qb::T_IN: + case qb::T_IS: + case qb::T_LIKE: + case qb::T_NOT: + case qb::T_NULL: + case qb::T_OR: + case qb::T_TRUE: + tok = t1; + s = t; + return true; + default: + break; + } + } + return false; +} + +bool tokeniseNumeric(std::string::const_iterator& s, std::string::const_iterator& e, Token& tok) +{ + Token t1; + std::string::const_iterator t = s; + bool r = tokenise(t, e, t1); + if (r && (t1.type==qb::T_NUMERIC_EXACT || t1.type==qb::T_NUMERIC_APPROX)) {tok = t1; s = t; return true;} + return false; +} + + +void verifyTokeniserSuccess(TokeniseF t, const char* ss, TokenType tt, const char* tv, const char* fs) { + Token tok; + string s(ss); + string::const_iterator sb = s.begin(); + string::const_iterator se = s.end(); + BOOST_CHECK(t(sb, se, tok)); + BOOST_CHECK_EQUAL(tok, Token(tt, tv)); + BOOST_CHECK_EQUAL(string(sb, se), fs); +} + +void verifyTokeniserFail(TokeniseF t, const char* c) { + Token tok; + string s(c); + string::const_iterator sb = s.begin(); + string::const_iterator se = s.end(); + BOOST_CHECK(!t(sb, se, tok)); + BOOST_CHECK_EQUAL(string(sb, se), c); +} + +QPID_AUTO_TEST_CASE(tokeniseSuccess) +{ + verifyTokeniserSuccess(&tokenise, "", qb::T_EOS, "", ""); + verifyTokeniserSuccess(&tokenise, " ", qb::T_EOS, "", ""); + verifyTokeniserSuccess(&tokenise, "null_123+blah", qb::T_IDENTIFIER, "null_123", "+blah"); + verifyTokeniserSuccess(&tokenise, "\"null-123\"+blah", qb::T_IDENTIFIER, "null-123", "+blah"); + verifyTokeniserSuccess(&tokenise, "\"This is an \"\"odd!\"\" identifier\"+blah", qb::T_IDENTIFIER, "This is an \"odd!\" identifier", "+blah"); + verifyTokeniserSuccess(&tokenise, "null+blah", qb::T_NULL, "null", "+blah"); + verifyTokeniserSuccess(&tokenise, "null+blah", qb::T_NULL, "null", "+blah"); + verifyTokeniserSuccess(&tokenise, "Is nOt null", qb::T_IS, "Is", " nOt null"); + verifyTokeniserSuccess(&tokenise, "nOt null", qb::T_NOT, "nOt", " null"); + verifyTokeniserSuccess(&tokenise, "Is nOt null", qb::T_IS, "Is", " nOt null"); + verifyTokeniserSuccess(&tokenise, "'Hello World'", qb::T_STRING, "Hello World", ""); + verifyTokeniserSuccess(&tokenise, "'Hello World''s end'a bit more", qb::T_STRING, "Hello World's end", "a bit more"); + verifyTokeniserSuccess(&tokenise, "=blah", qb::T_EQUAL, "=", "blah"); + verifyTokeniserSuccess(&tokenise, "<> Identifier", qb::T_NEQ, "<>", " Identifier"); + verifyTokeniserSuccess(&tokenise, "(a and b) not c", qb::T_LPAREN, "(", "a and b) not c"); + verifyTokeniserSuccess(&tokenise, ") not c", qb::T_RPAREN, ")", " not c"); + verifyTokeniserSuccess(&tokenise, "019kill", qb::T_NUMERIC_EXACT, "019", "kill"); + verifyTokeniserSuccess(&tokenise, "0kill", qb::T_NUMERIC_EXACT, "0", "kill"); + verifyTokeniserSuccess(&tokenise, "0.kill", qb::T_NUMERIC_APPROX, "0.", "kill"); + verifyTokeniserSuccess(&tokenise, "3.1415=pi", qb::T_NUMERIC_APPROX, "3.1415", "=pi"); + verifyTokeniserSuccess(&tokenise, ".25.kill", qb::T_NUMERIC_APPROX, ".25", ".kill"); + verifyTokeniserSuccess(&tokenise, "2e5.kill", qb::T_NUMERIC_APPROX, "2e5", ".kill"); + verifyTokeniserSuccess(&tokenise, "3.e50easy to kill", qb::T_NUMERIC_APPROX, "3.e50", "easy to kill"); + verifyTokeniserSuccess(&tokenise, "34.25e+50easy to kill", qb::T_NUMERIC_APPROX, "34.25e+50", "easy to kill"); + verifyTokeniserSuccess(&tokenise, "34.e-50easy to kill", qb::T_NUMERIC_APPROX, "34.e-50", "easy to kill"); +} + +QPID_AUTO_TEST_CASE(tokeniseFailure) +{ + verifyTokeniserFail(&tokeniseEos, "hb23"); + verifyTokeniserFail(&tokeniseIdentifier, "123"); + verifyTokeniserFail(&tokeniseIdentifier, "'Embedded 123'"); + verifyTokeniserFail(&tokeniseReservedWord, "1.2e5"); + verifyTokeniserFail(&tokeniseReservedWord, "'Stringy thing'"); + verifyTokeniserFail(&tokeniseReservedWord, "oR_andsomething"); + verifyTokeniserFail(&tokeniseString, "'Embedded 123"); + verifyTokeniserFail(&tokeniseString, "'This isn''t fair"); + verifyTokeniserFail(&tokeniseOperator, "123"); + verifyTokeniserFail(&tokeniseOperator, "'Stringy thing'"); + verifyTokeniserFail(&tokeniseOperator, "NoT"); + verifyTokeniserFail(&tokeniseOperator, "(a and b)"); + verifyTokeniserFail(&tokeniseOperator, ")"); + verifyTokeniserFail(&tokeniseParens, "="); + verifyTokeniserFail(&tokeniseParens, "what ho!"); + verifyTokeniserFail(&tokeniseNumeric, "kill"); + verifyTokeniserFail(&tokeniseNumeric, "e3"); + verifyTokeniserFail(&tokeniseNumeric, "1.e.5"); + verifyTokeniserFail(&tokeniseNumeric, ".e5"); + verifyTokeniserFail(&tokeniseNumeric, "34e"); + verifyTokeniserFail(&tokeniseNumeric, ".3e+"); + verifyTokeniserFail(&tokeniseNumeric, ".3e-."); +} + +QPID_AUTO_TEST_CASE(tokenString) +{ + + string exp(" a =b"); + string::const_iterator s = exp.begin(); + string::const_iterator e = exp.end(); + Tokeniser t(s, e); + + BOOST_CHECK_EQUAL(t.nextToken(), Token(qb::T_IDENTIFIER, "a")); + BOOST_CHECK_EQUAL(t.nextToken(), Token(qb::T_EQUAL, "=")); + BOOST_CHECK_EQUAL(t.nextToken(), Token(qb::T_IDENTIFIER, "b")); + BOOST_CHECK_EQUAL(t.nextToken(), Token(qb::T_EOS, "")); + + exp = " not 'hello kitty''s friend' = Is null "; + s = exp.begin(); + e = exp.end(); + Tokeniser u(s, e); + + BOOST_CHECK_EQUAL(u.nextToken(), Token(qb::T_NOT, "not")); + BOOST_CHECK_EQUAL(u.nextToken(), Token(qb::T_STRING, "hello kitty's friend")); + BOOST_CHECK_EQUAL(u.nextToken(), Token(qb::T_EQUAL, "=")); + BOOST_CHECK_EQUAL(u.nextToken(), Token(qb::T_IS, "Is")); + BOOST_CHECK_EQUAL(u.nextToken(), Token(qb::T_NULL, "null")); + BOOST_CHECK_EQUAL(u.nextToken(), Token(qb::T_EOS, "")); + BOOST_CHECK_EQUAL(u.nextToken(), Token(qb::T_EOS, "")); + + u.returnTokens(3); + BOOST_CHECK_EQUAL(u.nextToken(), Token(qb::T_IS, "Is")); + BOOST_CHECK_EQUAL(u.nextToken(), Token(qb::T_NULL, "null")); + BOOST_CHECK_EQUAL(u.nextToken(), Token(qb::T_EOS, "")); + BOOST_CHECK_EQUAL(u.nextToken(), Token(qb::T_EOS, "")); + + exp = "(a+6)*7.5/1e6"; + s = exp.begin(); + e = exp.end(); + Tokeniser v(s, e); + + BOOST_CHECK_EQUAL(v.nextToken(), Token(qb::T_LPAREN, "(")); + BOOST_CHECK_EQUAL(v.nextToken(), Token(qb::T_IDENTIFIER, "a")); + BOOST_CHECK_EQUAL(v.nextToken(), Token(qb::T_PLUS, "+")); + BOOST_CHECK_EQUAL(v.nextToken(), Token(qb::T_NUMERIC_EXACT, "6")); + BOOST_CHECK_EQUAL(v.nextToken(), Token(qb::T_RPAREN, ")")); + BOOST_CHECK_EQUAL(v.nextToken(), Token(qb::T_MULT, "*")); + BOOST_CHECK_EQUAL(v.nextToken(), Token(qb::T_NUMERIC_APPROX, "7.5")); + BOOST_CHECK_EQUAL(v.nextToken(), Token(qb::T_DIV, "/")); + BOOST_CHECK_EQUAL(v.nextToken(), Token(qb::T_NUMERIC_APPROX, "1e6")); +} + +QPID_AUTO_TEST_CASE(parseStringFail) +{ + BOOST_CHECK_THROW(qb::Selector e("hello world"), std::range_error); + BOOST_CHECK_THROW(qb::Selector e("hello ^ world"), std::range_error); + BOOST_CHECK_THROW(qb::Selector e("A is null not"), std::range_error); + BOOST_CHECK_THROW(qb::Selector e("A is null or not"), std::range_error); + BOOST_CHECK_THROW(qb::Selector e("A is null or and"), std::range_error); + BOOST_CHECK_THROW(qb::Selector e("A is null and (B='hello out there'"), std::range_error); + BOOST_CHECK_THROW(qb::Selector e("in='hello kitty'"), std::range_error); + BOOST_CHECK_THROW(qb::Selector e("A like 234"), std::range_error); + BOOST_CHECK_THROW(qb::Selector e("A not 234 escape"), std::range_error); + BOOST_CHECK_THROW(qb::Selector e("A not like 'eclecti_' escape 'happy'"), std::range_error); + BOOST_CHECK_THROW(qb::Selector e("A not like 'eclecti_' escape happy"), std::range_error); + BOOST_CHECK_THROW(qb::Selector e("A not like 'eclecti_' escape '%'"), std::range_error); + BOOST_CHECK_THROW(qb::Selector e("A BETWEEN AND 'true'"), std::range_error); + BOOST_CHECK_THROW(qb::Selector e("A NOT BETWEEN 34 OR 3.9"), std::range_error); + BOOST_CHECK_THROW(qb::Selector e("A IN ()"), std::range_error); + BOOST_CHECK_THROW(qb::Selector e("A NOT IN ()"), std::range_error); + BOOST_CHECK_THROW(qb::Selector e("A IN 'hello', 'there', 1, true, (1-17))"), std::range_error); + BOOST_CHECK_THROW(qb::Selector e("A IN ('hello', 'there' 1, true, (1-17))"), std::range_error); +} + +QPID_AUTO_TEST_CASE(parseString) +{ + BOOST_CHECK_NO_THROW(qb::Selector e("'Daft' is not null")); + BOOST_CHECK_NO_THROW(qb::Selector e("42 is null")); + BOOST_CHECK_NO_THROW(qb::Selector e("A is not null")); + BOOST_CHECK_NO_THROW(qb::Selector e("A is null")); + BOOST_CHECK_NO_THROW(qb::Selector e("A = C")); + BOOST_CHECK_NO_THROW(qb::Selector e("A <> C")); + BOOST_CHECK_NO_THROW(qb::Selector e("A='hello kitty'")); + BOOST_CHECK_NO_THROW(qb::Selector e("A<>'hello kitty'")); + BOOST_CHECK_NO_THROW(qb::Selector e("A=B")); + BOOST_CHECK_NO_THROW(qb::Selector e("A<>B")); + BOOST_CHECK_NO_THROW(qb::Selector e("A='hello kitty' OR B='Bye, bye cruel world'")); + BOOST_CHECK_NO_THROW(qb::Selector e("B='hello kitty' AnD A='Bye, bye cruel world'")); + BOOST_CHECK_NO_THROW(qb::Selector e("A is null or A='Bye, bye cruel world'")); + BOOST_CHECK_NO_THROW(qb::Selector e("Z is null OR A is not null and A<>'Bye, bye cruel world'")); + BOOST_CHECK_NO_THROW(qb::Selector e("(Z is null OR A is not null) and A<>'Bye, bye cruel world'")); + BOOST_CHECK_NO_THROW(qb::Selector e("NOT C is not null OR C is null")); + BOOST_CHECK_NO_THROW(qb::Selector e("Not A='' or B=z")); + BOOST_CHECK_NO_THROW(qb::Selector e("Not A=17 or B=5.6")); + BOOST_CHECK_NO_THROW(qb::Selector e("A<>17 and B=5.6e17")); + BOOST_CHECK_NO_THROW(qb::Selector e("A LIKE 'excep%ional'")); + BOOST_CHECK_NO_THROW(qb::Selector e("B NOT LIKE 'excep%ional'")); + BOOST_CHECK_NO_THROW(qb::Selector e("A LIKE 'excep%ional' EScape '\'")); + BOOST_CHECK_NO_THROW(qb::Selector e("A BETWEEN 13 AND 'true'")); + BOOST_CHECK_NO_THROW(qb::Selector e("A NOT BETWEEN 100 AND 3.9")); + BOOST_CHECK_NO_THROW(qb::Selector e("true")); + BOOST_CHECK_NO_THROW(qb::Selector e("-354")); + BOOST_CHECK_NO_THROW(qb::Selector e("-(X or Y)")); + BOOST_CHECK_NO_THROW(qb::Selector e("-687 or 567")); + BOOST_CHECK_NO_THROW(qb::Selector e("(354.6)")); + BOOST_CHECK_NO_THROW(qb::Selector e("A is null and 'hello out there'")); + BOOST_CHECK_NO_THROW(qb::Selector e("17/4>4")); + BOOST_CHECK_NO_THROW(qb::Selector e("17/4>+4")); + BOOST_CHECK_NO_THROW(qb::Selector e("17/4>-4")); + BOOST_CHECK_NO_THROW(qb::Selector e("A IN ('hello', 'there', 1 , true, (1-17))")); +} + +class TestSelectorEnv : public qpid::broker::SelectorEnv { + mutable map<string, qb::Value> values; + boost::ptr_vector<string> strings; + static const qb::Value EMPTY; + + const qb::Value& value(const string& v) const { + const qb::Value& r = values.find(v)!=values.end() ? values[v] : EMPTY; + return r; + } + +public: + void set(const string& id, const char* value) { + strings.push_back(new string(value)); + values[id] = strings[strings.size()-1]; + } + + void set(const string& id, const qb::Value& value) { + if (value.type==qb::Value::T_STRING) { + strings.push_back(new string(*value.s)); + values[id] = strings[strings.size()-1]; + } else { + values[id] = value; + } + } +}; + +const qb::Value TestSelectorEnv::EMPTY; + +QPID_AUTO_TEST_CASE(simpleEval) +{ + TestSelectorEnv env; + env.set("A", "Bye, bye cruel world"); + env.set("B", "hello kitty"); + + BOOST_CHECK(qb::Selector("").eval(env)); + BOOST_CHECK(qb::Selector(" ").eval(env)); + BOOST_CHECK(qb::Selector("A is not null").eval(env)); + BOOST_CHECK(!qb::Selector("A is null").eval(env)); + BOOST_CHECK(!qb::Selector("A = C").eval(env)); + BOOST_CHECK(!qb::Selector("A <> C").eval(env)); + BOOST_CHECK(!qb::Selector("C is not null").eval(env)); + BOOST_CHECK(qb::Selector("C is null").eval(env)); + BOOST_CHECK(qb::Selector("A='Bye, bye cruel world'").eval(env)); + BOOST_CHECK(!qb::Selector("A<>'Bye, bye cruel world'").eval(env)); + BOOST_CHECK(!qb::Selector("A='hello kitty'").eval(env)); + BOOST_CHECK(qb::Selector("A<>'hello kitty'").eval(env)); + BOOST_CHECK(!qb::Selector("A=B").eval(env)); + BOOST_CHECK(qb::Selector("A<>B").eval(env)); + BOOST_CHECK(!qb::Selector("A='hello kitty' OR B='Bye, bye cruel world'").eval(env)); + BOOST_CHECK(qb::Selector("B='hello kitty' OR A='Bye, bye cruel world'").eval(env)); + BOOST_CHECK(qb::Selector("B='hello kitty' AnD A='Bye, bye cruel world'").eval(env)); + BOOST_CHECK(!qb::Selector("B='hello kitty' AnD B='Bye, bye cruel world'").eval(env)); + BOOST_CHECK(qb::Selector("A is null or A='Bye, bye cruel world'").eval(env)); + BOOST_CHECK(qb::Selector("Z is null OR A is not null and A<>'Bye, bye cruel world'").eval(env)); + BOOST_CHECK(!qb::Selector("(Z is null OR A is not null) and A<>'Bye, bye cruel world'").eval(env)); + BOOST_CHECK(qb::Selector("NOT C is not null OR C is null").eval(env)); + BOOST_CHECK(qb::Selector("Not A='' or B=z").eval(env)); + BOOST_CHECK(qb::Selector("Not A=17 or B=5.6").eval(env)); + BOOST_CHECK(!qb::Selector("A<>17 and B=5.6e17").eval(env)); + BOOST_CHECK(!qb::Selector("C=D").eval(env)); + BOOST_CHECK(qb::Selector("13 is not null").eval(env)); + BOOST_CHECK(!qb::Selector("'boo!' is null").eval(env)); + BOOST_CHECK(qb::Selector("A LIKE '%cru_l%'").eval(env)); + BOOST_CHECK(qb::Selector("'_%%_hello.th_re%' LIKE 'z_%.%z_%z%' escape 'z'").eval(env)); + BOOST_CHECK(qb::Selector("A NOT LIKE 'z_%.%z_%z%' escape 'z'").eval(env)); + BOOST_CHECK(qb::Selector("'{}[]<>,.!\"$%^&*()_-+=?/|\\' LIKE '{}[]<>,.!\"$z%^&*()z_-+=?/|\\' escape 'z'").eval(env)); +} + +QPID_AUTO_TEST_CASE(numericEval) +{ + TestSelectorEnv env; + env.set("A", 42.0); + env.set("B", 39); + + BOOST_CHECK(qb::Selector("A>B").eval(env)); + BOOST_CHECK(qb::Selector("A=42").eval(env)); + BOOST_CHECK(qb::Selector("B=39.0").eval(env)); + BOOST_CHECK(qb::Selector("Not A=17 or B=5.6").eval(env)); + BOOST_CHECK(!qb::Selector("A<>17 and B=5.6e17").eval(env)); + BOOST_CHECK(qb::Selector("3 BETWEEN -17 and 98.5").eval(env)); + BOOST_CHECK(qb::Selector("A BETWEEN B and 98.5").eval(env)); + BOOST_CHECK(!qb::Selector("B NOT BETWEEN 35 AND 100").eval(env)); + BOOST_CHECK(!qb::Selector("A BETWEEN B and 40").eval(env)); + BOOST_CHECK(!qb::Selector("A BETWEEN C and 40").eval(env)); + BOOST_CHECK(!qb::Selector("A BETWEEN 45 and C").eval(env)); + BOOST_CHECK(qb::Selector("(A BETWEEN 40 and C) IS NULL").eval(env)); + BOOST_CHECK(qb::Selector("(A BETWEEN C and 45) IS NULL").eval(env)); + BOOST_CHECK(qb::Selector("17/4=4").eval(env)); + BOOST_CHECK(!qb::Selector("A/0=0").eval(env)); + BOOST_CHECK(qb::Selector("A*B+19<A*(B+19)").eval(env)); + BOOST_CHECK(qb::Selector("-A=0-A").eval(env)); +} + +QPID_AUTO_TEST_CASE(comparisonEval) +{ + TestSelectorEnv env; + + BOOST_CHECK(!qb::Selector("17 > 19.0").eval(env)); + BOOST_CHECK(!qb::Selector("'hello' > 19.0").eval(env)); + BOOST_CHECK(!qb::Selector("'hello' < 19.0").eval(env)); + BOOST_CHECK(!qb::Selector("'hello' = 19.0").eval(env)); + BOOST_CHECK(!qb::Selector("'hello'>42 and 'hello'<42 and 'hello'=42 and 'hello'<>42").eval(env)); + BOOST_CHECK(qb::Selector("20 >= 19.0 and 20 > 19").eval(env)); + BOOST_CHECK(qb::Selector("42 <= 42.0 and 37.0 >= 37").eval(env)); + BOOST_CHECK(qb::Selector("(A IN ('hello', 'there', 1 , true, (1-17))) IS NULL").eval(env)); + BOOST_CHECK(qb::Selector("'hello' IN ('hello', 'there', 1 , true, (1-17))").eval(env)); + BOOST_CHECK(qb::Selector("TRUE IN ('hello', 'there', 1 , true, (1-17))").eval(env)); + BOOST_CHECK(qb::Selector("-16 IN ('hello', 'there', 1 , true, (1-17))").eval(env)); + BOOST_CHECK(!qb::Selector("'hell' IN ('hello', 'there', 1 , true, (1-17))").eval(env)); + BOOST_CHECK(qb::Selector("('hell' IN ('hello', 'there', 1 , true, (1-17), A)) IS NULL").eval(env)); +} + +QPID_AUTO_TEST_CASE(NullEval) +{ + TestSelectorEnv env; + + BOOST_CHECK(qb::Selector("P > 19.0 or (P is null)").eval(env)); + BOOST_CHECK(qb::Selector("P is null or P=''").eval(env)); + BOOST_CHECK(!qb::Selector("P=Q").eval(env)); + BOOST_CHECK(!qb::Selector("not P=Q").eval(env)); + BOOST_CHECK(!qb::Selector("not P=Q and not P=Q").eval(env)); + BOOST_CHECK(!qb::Selector("P=Q or not P=Q").eval(env)); + BOOST_CHECK(!qb::Selector("P > 19.0 or P <= 19.0").eval(env)); + BOOST_CHECK(qb::Selector("P > 19.0 or 17 <= 19.0").eval(env)); +} + +QPID_AUTO_TEST_SUITE_END() + +}} diff --git a/qpid/cpp/src/tests/SequenceNumberTest.cpp b/qpid/cpp/src/tests/SequenceNumberTest.cpp new file mode 100644 index 0000000000..f3c934e3ca --- /dev/null +++ b/qpid/cpp/src/tests/SequenceNumberTest.cpp @@ -0,0 +1,209 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "unit_test.h" +#include <iostream> +#include "qpid/framing/SequenceNumber.h" +#include "qpid/framing/SequenceNumberSet.h" + +using namespace qpid::framing; + +namespace qpid { +namespace tests { + +void checkDifference(SequenceNumber& a, SequenceNumber& b, int gap) +{ + BOOST_CHECK_EQUAL(gap, a - b); + BOOST_CHECK_EQUAL(-gap, b - a); + + //increment until b wraps around + for (int i = 0; i < (gap + 2); i++, ++a, ++b) { + BOOST_CHECK_EQUAL(gap, a - b); + } + //keep incrementing until a also wraps around + for (int i = 0; i < (gap + 2); i++, ++a, ++b) { + BOOST_CHECK_EQUAL(gap, a - b); + } + //let b catch up and overtake + for (int i = 0; i < (gap*2); i++, ++b) { + BOOST_CHECK_EQUAL(gap - i, a - b); + BOOST_CHECK_EQUAL(i - gap, b - a); + } +} + +void checkComparison(SequenceNumber& a, SequenceNumber& b, int gap) +{ + //increment until b wraps around + for (int i = 0; i < (gap + 2); i++) { + BOOST_CHECK(++a < ++b);//test prefix + } + //keep incrementing until a also wraps around + for (int i = 0; i < (gap + 2); i++) { + BOOST_CHECK(a++ < b++);//test postfix + } + //let a 'catch up' + for (int i = 0; i < gap; i++) { + a++; + } + BOOST_CHECK(a == b); + BOOST_CHECK(++a > b); +} + + +QPID_AUTO_TEST_SUITE(SequenceNumberTestSuite) + +QPID_AUTO_TEST_CASE(testIncrementPostfix) +{ + SequenceNumber a; + SequenceNumber b; + BOOST_CHECK(!(a > b)); + BOOST_CHECK(!(b < a)); + BOOST_CHECK(a == b); + + SequenceNumber c = a++; + BOOST_CHECK(a > b); + BOOST_CHECK(b < a); + BOOST_CHECK(a != b); + BOOST_CHECK(c < a); + BOOST_CHECK(a != c); + + b++; + BOOST_CHECK(!(a > b)); + BOOST_CHECK(!(b < a)); + BOOST_CHECK(a == b); + BOOST_CHECK(c < b); + BOOST_CHECK(b != c); +} + +QPID_AUTO_TEST_CASE(testIncrementPrefix) +{ + SequenceNumber a; + SequenceNumber b; + BOOST_CHECK(!(a > b)); + BOOST_CHECK(!(b < a)); + BOOST_CHECK(a == b); + + SequenceNumber c = ++a; + BOOST_CHECK(a > b); + BOOST_CHECK(b < a); + BOOST_CHECK(a != b); + BOOST_CHECK(a == c); + + ++b; + BOOST_CHECK(!(a > b)); + BOOST_CHECK(!(b < a)); + BOOST_CHECK(a == b); +} + +QPID_AUTO_TEST_CASE(testWrapAround) +{ + const uint32_t max = 0xFFFFFFFF; + SequenceNumber a(max - 10); + SequenceNumber b(max - 5); + checkComparison(a, b, 5); + + const uint32_t max_signed = 0x7FFFFFFF; + SequenceNumber c(max_signed - 10); + SequenceNumber d(max_signed - 5); + checkComparison(c, d, 5); +} + +QPID_AUTO_TEST_CASE(testCondense) +{ + SequenceNumberSet set; + for (uint i = 0; i < 6; i++) { + set.push_back(SequenceNumber(i)); + } + set.push_back(SequenceNumber(7)); + for (uint i = 9; i < 13; i++) { + set.push_back(SequenceNumber(i)); + } + set.push_back(SequenceNumber(13)); + SequenceNumberSet actual = set.condense(); + + SequenceNumberSet expected; + expected.addRange(SequenceNumber(0), SequenceNumber(5)); + expected.addRange(SequenceNumber(7), SequenceNumber(7)); + expected.addRange(SequenceNumber(9), SequenceNumber(13)); + BOOST_CHECK_EQUAL(expected, actual); +} + +QPID_AUTO_TEST_CASE(testCondenseSingleRange) +{ + SequenceNumberSet set; + for (uint i = 0; i < 6; i++) { + set.push_back(SequenceNumber(i)); + } + SequenceNumberSet actual = set.condense(); + + SequenceNumberSet expected; + expected.addRange(SequenceNumber(0), SequenceNumber(5)); + BOOST_CHECK_EQUAL(expected, actual); +} + +QPID_AUTO_TEST_CASE(testCondenseSingleItem) +{ + SequenceNumberSet set; + set.push_back(SequenceNumber(1)); + SequenceNumberSet actual = set.condense(); + + SequenceNumberSet expected; + expected.addRange(SequenceNumber(1), SequenceNumber(1)); + BOOST_CHECK_EQUAL(expected, actual); +} + +QPID_AUTO_TEST_CASE(testDifference) +{ + SequenceNumber a; + SequenceNumber b; + + for (int i = 0; i < 10; i++, ++a) { + BOOST_CHECK_EQUAL(i, a - b); + BOOST_CHECK_EQUAL(-i, b - a); + } + + b = a; + + for (int i = 0; i < 10; i++, ++b) { + BOOST_CHECK_EQUAL(-i, a - b); + BOOST_CHECK_EQUAL(i, b - a); + } +} + +QPID_AUTO_TEST_CASE(testDifferenceWithWrapAround1) +{ + const uint32_t max = 0xFFFFFFFF; + SequenceNumber a(max - 5); + SequenceNumber b(max - 10); + checkDifference(a, b, 5); +} + +QPID_AUTO_TEST_CASE(testDifferenceWithWrapAround2) +{ + const uint32_t max_signed = 0x7FFFFFFF; + SequenceNumber c(max_signed - 5); + SequenceNumber d(max_signed - 10); + checkDifference(c, d, 5); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/SequenceSet.cpp b/qpid/cpp/src/tests/SequenceSet.cpp new file mode 100644 index 0000000000..bc0a8ea509 --- /dev/null +++ b/qpid/cpp/src/tests/SequenceSet.cpp @@ -0,0 +1,187 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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. + * + */ + +#include "qpid/framing/SequenceSet.h" +#include "unit_test.h" +#include <list> + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(SequenceSetTestSuite) + +using namespace qpid::framing; + +struct RangeExpectations +{ + typedef std::pair<SequenceNumber, SequenceNumber> Range; + typedef std::list<Range> Ranges; + + Ranges ranges; + + RangeExpectations& expect(const SequenceNumber& start, const SequenceNumber& end) { + ranges.push_back(Range(start, end)); + return *this; + } + + void operator()(const SequenceNumber& start, const SequenceNumber& end) { + BOOST_CHECK(!ranges.empty()); + if (!ranges.empty()) { + BOOST_CHECK_EQUAL(start, ranges.front().first); + BOOST_CHECK_EQUAL(end, ranges.front().second); + ranges.pop_front(); + } + } + + void check(SequenceSet& set) { + set.for_each(*this); + BOOST_CHECK(ranges.empty()); + } +}; + +QPID_AUTO_TEST_CASE(testAdd) { + SequenceSet s; + s.add(2); + s.add(8,8); + s.add(3,5); + + for (uint32_t i = 0; i <= 1; i++) + BOOST_CHECK(!s.contains(i)); + + for (uint32_t i = 2; i <= 5; i++) + BOOST_CHECK(s.contains(i)); + + for (uint32_t i = 6; i <= 7; i++) + BOOST_CHECK(!s.contains(i)); + + BOOST_CHECK(s.contains(8)); + + for (uint32_t i = 9; i <= 10; i++) + BOOST_CHECK(!s.contains(i)); + + RangeExpectations().expect(2, 5).expect(8, 8).check(s); + + SequenceSet t; + t.add(6, 10); + t.add(s); + + for (uint32_t i = 0; i <= 1; i++) + BOOST_CHECK(!t.contains(i)); + + for (uint32_t i = 2; i <= 10; i++) + BOOST_CHECK_MESSAGE(t.contains(i), t << " contains " << i); + + RangeExpectations().expect(2, 10).check(t); +} + +QPID_AUTO_TEST_CASE(testAdd2) { + SequenceSet s; + s.add(7,6); + s.add(4,4); + s.add(3,10); + s.add(2); + RangeExpectations().expect(2, 10).check(s); +} + +QPID_AUTO_TEST_CASE(testRemove) { + SequenceSet s; + SequenceSet t; + s.add(0, 10); + t.add(0, 10); + + s.remove(7); + s.remove(3, 5); + s.remove(9, 10); + + t.remove(s); + + for (uint32_t i = 0; i <= 2; i++) { + BOOST_CHECK(s.contains(i)); + BOOST_CHECK(!t.contains(i)); + } + + for (uint32_t i = 3; i <= 5; i++) { + BOOST_CHECK(!s.contains(i)); + BOOST_CHECK(t.contains(i)); + } + + BOOST_CHECK(s.contains(6)); + BOOST_CHECK(!t.contains(6)); + + BOOST_CHECK(!s.contains(7)); + BOOST_CHECK(t.contains(7)); + + BOOST_CHECK(s.contains(8)); + BOOST_CHECK(!t.contains(8)); + + for (uint32_t i = 9; i <= 10; i++) { + BOOST_CHECK(!s.contains(i)); + BOOST_CHECK(t.contains(i)); + } + + RangeExpectations().expect(0, 2).expect(6, 6).expect(8, 8).check(s); + RangeExpectations().expect(3, 5).expect(7, 7).expect(9, 10).check(t); +} + + +QPID_AUTO_TEST_CASE(testOutOfOrderRemove) { + + SequenceSet s(2, 20); + + // test remove from middle: + s.remove(7); + RangeExpectations().expect(2, 6).expect(8, 20).check(s); + s.remove(14); + RangeExpectations().expect(2, 6).expect(8, 13).expect(15, 20).check(s); + + // remove from front of subrange: + s.remove(8, 8); + RangeExpectations().expect(2, 6).expect(9, 13).expect(15, 20).check(s); + + // remove from tail of subrange: + s.remove(6); + RangeExpectations().expect(2, 5).expect(9, 13).expect(15, 20).check(s); + + // remove across subrange: + s.remove(13, 15); + RangeExpectations().expect(2, 5).expect(9, 12).expect(16, 20).check(s); + + // remove within subrange: + s.remove(6, 8); + RangeExpectations().expect(2, 5).expect(9, 12).expect(16, 20).check(s); + + // remove overlap subrange tail: + s.remove(11, 15); + RangeExpectations().expect(2, 5).expect(9, 10).expect(16, 20).check(s); + + // remove overlap subrange head: + s.remove(14, 17); + RangeExpectations().expect(2, 5).expect(9, 10).expect(18, 20).check(s); + + // remove overlap sequence tail: + s.remove(20, 22); + RangeExpectations().expect(2, 5).expect(9, 10).expect(18, 19).check(s); + + // remove overlap sequence head: + s.remove(1, 3); + RangeExpectations().expect(4, 5).expect(9, 10).expect(18, 19).check(s); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/SessionState.cpp b/qpid/cpp/src/tests/SessionState.cpp new file mode 100644 index 0000000000..1cf3415484 --- /dev/null +++ b/qpid/cpp/src/tests/SessionState.cpp @@ -0,0 +1,303 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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. + * + */ + +#include "unit_test.h" + +#include "qpid/SessionState.h" +#include "qpid/Exception.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/framing/SessionFlushBody.h" + +#include <boost/bind.hpp> +#include <algorithm> +#include <functional> +#include <numeric> + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(SessionStateTestSuite) + +using namespace std; +using namespace qpid::framing; + +// ================================================================ +// Utility functions. + +// Apply f to [begin, end) and accumulate the result +template <class Iter, class T, class F> +T applyAccumulate(Iter begin, Iter end, T seed, const F& f) { + return std::accumulate(begin, end, seed, boost::bind(std::plus<T>(), _1, boost::bind(f, _2))); +} + +// Create a frame with a one-char string. +AMQFrame& frame(char s) { + static AMQFrame frame((AMQContentBody(string(&s, 1)))); + return frame; +} + +// Simple string representation of a frame. +string str(const AMQFrame& f) { + if (f.getMethod()) return "C"; // Command or Control + const AMQContentBody* c = dynamic_cast<const AMQContentBody*>(f.getBody()); + if (c) return c->getData(); // Return data for content frames. + return "H"; // Must be a header. +} +// Make a string from a range of frames. +string str(const boost::iterator_range<vector<AMQFrame>::const_iterator>& frames) { + string (*strFrame)(const AMQFrame&) = str; + return applyAccumulate(frames.begin(), frames.end(), string(), ptr_fun(strFrame)); +} +// Make a transfer command frame. +AMQFrame transferFrame(bool hasContent) { + AMQFrame t((MessageTransferBody())); + t.setFirstFrame(true); + t.setLastFrame(true); + t.setFirstSegment(true); + t.setLastSegment(!hasContent); + return t; +} +// Make a content frame +AMQFrame contentFrame(string content, bool isLast=true) { + AMQFrame f((AMQContentBody(content))); + f.setFirstFrame(true); + f.setLastFrame(true); + f.setFirstSegment(false); + f.setLastSegment(isLast); + return f; +} +AMQFrame contentFrameChar(char content, bool isLast=true) { + return contentFrame(string(1, content), isLast); +} + +// Send frame & return size of frame. +size_t send(qpid::SessionState& s, const AMQFrame& f) { s.senderRecord(f); return f.encodedSize(); } +// Send transfer command with no content. +size_t transfer0(qpid::SessionState& s) { return send(s, transferFrame(false)); } +// Send transfer frame with single content frame. +size_t transfer1(qpid::SessionState& s, string content) { + return send(s,transferFrame(true)) + send(s,contentFrame(content)); +} +size_t transfer1Char(qpid::SessionState& s, char content) { + return transfer1(s, string(1,content)); +} + +// Send transfer frame with multiple single-byte content frames. +size_t transferN(qpid::SessionState& s, string content) { + size_t size=send(s, transferFrame(!content.empty())); + if (!content.empty()) { + char last = content[content.size()-1]; + content.resize(content.size()-1); + size += applyAccumulate(content.begin(), content.end(), 0, + boost::bind(&send, boost::ref(s), + boost::bind(contentFrameChar, _1, false))); + size += send(s, contentFrameChar(last, true)); + } + return size; +} + +// Send multiple transfers with single-byte content. +size_t transfers(qpid::SessionState& s, string content) { + return applyAccumulate(content.begin(), content.end(), 0, + boost::bind(transfer1Char, boost::ref(s), _1)); +} + +size_t contentFrameSize(size_t n=1) { return AMQFrame(( AMQContentBody())).encodedSize() + n; } +size_t transferFrameSize() { return AMQFrame((MessageTransferBody())).encodedSize(); } + +// ==== qpid::SessionState test classes + +using qpid::SessionId; +using qpid::SessionPoint; + + +QPID_AUTO_TEST_CASE(testSendGetReplyList) { + qpid::SessionState s; + s.setTimeout(1); + s.senderGetCommandPoint(); + transfer1(s, "abc"); + transfers(s, "def"); + transferN(s, "xyz"); + BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(0,0))),"CabcCdCeCfCxyz"); + // Ignore controls. + s.senderRecord(AMQFrame(new SessionFlushBody())); + BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(2,0))),"CeCfCxyz"); +} + +QPID_AUTO_TEST_CASE(testNeedFlush) { + qpid::SessionState::Configuration c; + // sync after 2 1-byte transfers or equivalent bytes. + c.replayFlushLimit = 2*(transferFrameSize()+contentFrameSize()); + qpid::SessionState s(SessionId(), c); + s.setTimeout(1); + s.senderGetCommandPoint(); + transfers(s, "a"); + BOOST_CHECK(!s.senderNeedFlush()); + transfers(s, "b"); + BOOST_CHECK(s.senderNeedFlush()); + s.senderRecordFlush(); + BOOST_CHECK(!s.senderNeedFlush()); + transfers(s, "c"); + BOOST_CHECK(!s.senderNeedFlush()); + transfers(s, "d"); + BOOST_CHECK(s.senderNeedFlush()); + BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint())), "CaCbCcCd"); +} + +QPID_AUTO_TEST_CASE(testPeerConfirmed) { + qpid::SessionState::Configuration c; + // sync after 2 1-byte transfers or equivalent bytes. + c.replayFlushLimit = 2*(transferFrameSize()+contentFrameSize()); + qpid::SessionState s(SessionId(), c); + s.setTimeout(1); + s.senderGetCommandPoint(); + transfers(s, "ab"); + BOOST_CHECK(s.senderNeedFlush()); + transfers(s, "cd"); + BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(0,0))), "CaCbCcCd"); + s.senderConfirmed(SessionPoint(3)); + BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(3,0))), "Cd"); + BOOST_CHECK(!s.senderNeedFlush()); + + // Multi-frame transfer. + transfer1(s, "efg"); + transfers(s, "xy"); + BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(3,0))), "CdCefgCxCy"); + BOOST_CHECK(s.senderNeedFlush()); + + s.senderConfirmed(SessionPoint(4)); + BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(4,0))), "CefgCxCy"); + BOOST_CHECK(s.senderNeedFlush()); + + s.senderConfirmed(SessionPoint(5)); + BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(5,0))), "CxCy"); + BOOST_CHECK(s.senderNeedFlush()); + + s.senderConfirmed(SessionPoint(6)); + BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(6,0))), "Cy"); + BOOST_CHECK(!s.senderNeedFlush()); +} + +QPID_AUTO_TEST_CASE(testPeerCompleted) { + qpid::SessionState s; + s.setTimeout(1); + s.senderGetCommandPoint(); + // Completion implies confirmation + transfers(s, "abc"); + BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(0,0))), "CaCbCc"); + SequenceSet set(SequenceSet() + 0 + 1); + s.senderCompleted(set); + BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(2,0))), "Cc"); + + transfers(s, "def"); + // We dont do out-of-order confirmation, so this will only confirm up to 3: + set = SequenceSet(SequenceSet() + 2 + 3 + 5); + s.senderCompleted(set); + BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(4,0))), "CeCf"); +} + +QPID_AUTO_TEST_CASE(testReceive) { + // Advance expected/received correctly + qpid::SessionState s; + s.receiverSetCommandPoint(SessionPoint()); + BOOST_CHECK_EQUAL(s.receiverGetExpected(), SessionPoint(0)); + BOOST_CHECK_EQUAL(s.receiverGetReceived(), SessionPoint(0)); + + BOOST_CHECK(s.receiverRecord(transferFrame(false))); + BOOST_CHECK_EQUAL(s.receiverGetExpected(), SessionPoint(1)); + BOOST_CHECK_EQUAL(s.receiverGetReceived(), SessionPoint(1)); + + BOOST_CHECK(s.receiverRecord(transferFrame(true))); + SessionPoint point = SessionPoint(1, transferFrameSize()); + BOOST_CHECK_EQUAL(s.receiverGetExpected(), point); + BOOST_CHECK_EQUAL(s.receiverGetReceived(), point); + BOOST_CHECK(s.receiverRecord(contentFrame("", false))); + point.offset += contentFrameSize(0); + BOOST_CHECK_EQUAL(s.receiverGetExpected(), point); + BOOST_CHECK_EQUAL(s.receiverGetReceived(), point); + BOOST_CHECK(s.receiverRecord(contentFrame("", true))); + BOOST_CHECK_EQUAL(s.receiverGetExpected(), SessionPoint(2)); + BOOST_CHECK_EQUAL(s.receiverGetReceived(), SessionPoint(2)); + + // Idempotence barrier, rewind expected & receive some duplicates. + s.receiverSetCommandPoint(SessionPoint(1)); + BOOST_CHECK(!s.receiverRecord(transferFrame(false))); + BOOST_CHECK_EQUAL(s.receiverGetExpected(), SessionPoint(2)); + BOOST_CHECK_EQUAL(s.receiverGetReceived(), SessionPoint(2)); + BOOST_CHECK(s.receiverRecord(transferFrame(false))); + BOOST_CHECK_EQUAL(s.receiverGetExpected(), SessionPoint(3)); + BOOST_CHECK_EQUAL(s.receiverGetReceived(), SessionPoint(3)); +} + +QPID_AUTO_TEST_CASE(testCompleted) { + // completed & unknownCompleted + qpid::SessionState s; + s.receiverSetCommandPoint(SessionPoint()); + s.receiverRecord(transferFrame(false)); + s.receiverRecord(transferFrame(false)); + s.receiverRecord(transferFrame(false)); + s.receiverCompleted(1); + BOOST_CHECK_EQUAL(s.receiverGetUnknownComplete(), SequenceSet(SequenceSet()+1)); + s.receiverCompleted(0); + BOOST_CHECK_EQUAL(s.receiverGetUnknownComplete(), + SequenceSet(SequenceSet() + qpid::Range<SequenceNumber>(0,2))); + s.receiverKnownCompleted(SequenceSet(SequenceSet()+1)); + BOOST_CHECK_EQUAL(s.receiverGetUnknownComplete(), SequenceSet(SequenceSet()+2)); + // TODO aconway 2008-04-30: missing tests for known-completed. +} + +QPID_AUTO_TEST_CASE(testNeedKnownCompleted) { + size_t flushInterval= 2*(transferFrameSize()+contentFrameSize())+1; + qpid::SessionState::Configuration c(flushInterval); + qpid::SessionState s(qpid::SessionId(), c); + s.senderGetCommandPoint(); + transfers(s, "a"); + SequenceSet set(SequenceSet() + 0); + s.senderCompleted(set); + BOOST_CHECK(!s.senderNeedKnownCompleted()); + + transfers(s, "b"); + set += 1; + s.senderCompleted(set); + BOOST_CHECK(!s.senderNeedKnownCompleted()); + + transfers(s, "c"); + set += 2; + s.senderCompleted(set); + BOOST_CHECK(s.senderNeedKnownCompleted()); + s.senderRecordKnownCompleted(); + BOOST_CHECK(!s.senderNeedKnownCompleted()); + + transfers(s, "de"); + set += 3; + set += 4; + s.senderCompleted(set); + BOOST_CHECK(!s.senderNeedKnownCompleted()); + + transfers(s, "f"); + set += 2; + s.senderCompleted(set); + BOOST_CHECK(s.senderNeedKnownCompleted()); + s.senderRecordKnownCompleted(); + BOOST_CHECK(!s.senderNeedKnownCompleted()); +} + + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/Shlib.cpp b/qpid/cpp/src/tests/Shlib.cpp new file mode 100644 index 0000000000..7f01323e3c --- /dev/null +++ b/qpid/cpp/src/tests/Shlib.cpp @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "test_tools.h" +#include "config.h" +#include "qpid/sys/Shlib.h" +#include "qpid/Exception.h" + +#include "unit_test.h" + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(ShlibTestSuite) + +using namespace qpid::sys; +typedef void (*CallMe)(int*); + + +QPID_AUTO_TEST_CASE(testShlib) { + Shlib sh("./" QPID_SHLIB_PREFIX "shlibtest" QPID_SHLIB_POSTFIX QPID_SHLIB_SUFFIX); + // Double cast to avoid ISO warning. + CallMe callMe=sh.getSymbol<CallMe>("callMe"); + BOOST_REQUIRE(callMe != 0); + int unloaded=0; + callMe(&unloaded); + sh.unload(); + BOOST_CHECK_EQUAL(42, unloaded); + try { + sh.getSymbol("callMe"); + BOOST_FAIL("Expected exception"); + } + catch (const qpid::Exception&) {} +} + +QPID_AUTO_TEST_CASE(testAutoShlib) { + int unloaded = 0; + { + AutoShlib sh("./" QPID_SHLIB_PREFIX "shlibtest" QPID_SHLIB_POSTFIX QPID_SHLIB_SUFFIX); + CallMe callMe=sh.getSymbol<CallMe>("callMe"); + BOOST_REQUIRE(callMe != 0); + callMe(&unloaded); + } + BOOST_CHECK_EQUAL(42, unloaded); +} + + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/Statistics.cpp b/qpid/cpp/src/tests/Statistics.cpp new file mode 100644 index 0000000000..7cacde8b74 --- /dev/null +++ b/qpid/cpp/src/tests/Statistics.cpp @@ -0,0 +1,131 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "Statistics.h" +#include <qpid/messaging/Message.h> +#include <ostream> +#include <iomanip> + +namespace qpid { +namespace tests { + +using namespace std; + +Statistic::~Statistic() {} + +Throughput::Throughput() : messages(0), started(false) {} + +void Throughput::message(const messaging::Message&) { + ++messages; + if (!started) { + start = sys::now(); + started = true; + } +} + +void Throughput::header(ostream& o) const { + o << "tp(m/s)"; +} + +void Throughput::report(ostream& o) const { + double elapsed(int64_t(sys::Duration(start, sys::now()))/double(sys::TIME_SEC)); + o << fixed << setprecision(0) << messages/elapsed; +} + +ThroughputAndLatency::ThroughputAndLatency() : + total(0), + min(numeric_limits<double>::max()), + max(numeric_limits<double>::min()), + samples(0) +{} + +const std::string TS = "ts"; + +void ThroughputAndLatency::message(const messaging::Message& m) { + Throughput::message(m); + types::Variant::Map::const_iterator i = m.getProperties().find(TS); + if (i != m.getProperties().end()) { + ++samples; + int64_t start(i->second.asInt64()); + int64_t end(sys::Duration::FromEpoch()); + double latency = double(end - start)/sys::TIME_MSEC; + if (latency > 0) { + total += latency; + if (latency < min) min = latency; + if (latency > max) max = latency; + } + } +} + +void ThroughputAndLatency::header(ostream& o) const { + Throughput::header(o); + o << '\t' << "l-min" << '\t' << "l-max" << '\t' << "l-avg"; +} + +void ThroughputAndLatency::report(ostream& o) const { + Throughput::report(o); + if (samples) { + o << fixed << setprecision(2) + << '\t' << min << '\t' << max << '\t' << total/samples; + } +} + +ReporterBase::ReporterBase(ostream& o, int batch, bool wantHeader) + : batchSize(batch), batchCount(0), headerPrinted(!wantHeader), out(o) +{} + +ReporterBase::~ReporterBase() {} + +/** Count message in the statistics */ +void ReporterBase::message(const messaging::Message& m) { + if (!overall.get()) overall = create(); + overall->message(m); + if (batchSize) { + if (!batch.get()) batch = create(); + batch->message(m); + if (++batchCount == batchSize) { + header(); + batch->report(out); + out << endl; + batch = create(); + batchCount = 0; + } + } +} + +/** Print overall report. */ +void ReporterBase::report() { + if (!overall.get()) overall = create(); + header(); + overall->report(out); + out << endl; +} + +void ReporterBase::header() { + if (!headerPrinted) { + if (!overall.get()) overall = create(); + overall->header(out); + out << endl; + headerPrinted = true; + } +} + + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/Statistics.h b/qpid/cpp/src/tests/Statistics.h new file mode 100644 index 0000000000..091046a17f --- /dev/null +++ b/qpid/cpp/src/tests/Statistics.h @@ -0,0 +1,111 @@ +#ifndef TESTS_STATISTICS_H +#define TESTS_STATISTICS_H + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + + +#include <qpid/sys/Time.h> +#include <limits> +#include <iosfwd> +#include <memory> + +namespace qpid { + +namespace messaging { +class Message; +} + +namespace tests { + +class Statistic { + public: + virtual ~Statistic(); + virtual void message(const messaging::Message&) = 0; + virtual void report(std::ostream&) const = 0; + virtual void header(std::ostream&) const = 0; +}; + +class Throughput : public Statistic { + public: + Throughput(); + virtual void message(const messaging::Message&); + virtual void report(std::ostream&) const; + virtual void header(std::ostream&) const; + + protected: + int messages; + + private: + bool started; + sys::AbsTime start; +}; + +class ThroughputAndLatency : public Throughput { + public: + ThroughputAndLatency(); + virtual void message(const messaging::Message&); + virtual void report(std::ostream&) const; + virtual void header(std::ostream&) const; + + private: + double total, min, max; // Milliseconds + int samples; +}; + +/** Report batch and overall statistics */ +class ReporterBase { + public: + virtual ~ReporterBase(); + + /** Count message in the statistics */ + void message(const messaging::Message& m); + + /** Print overall report. */ + void report(); + + protected: + ReporterBase(std::ostream& o, int batchSize, bool wantHeader); + virtual std::auto_ptr<Statistic> create() = 0; + + private: + void header(); + void report(const Statistic& s); + std::auto_ptr<Statistic> overall; + std::auto_ptr<Statistic> batch; + int batchSize, batchCount; + bool stopped, headerPrinted; + std::ostream& out; +}; + +template <class Stats> class Reporter : public ReporterBase { + public: + Reporter(std::ostream& o, int batchSize, bool wantHeader) + : ReporterBase(o, batchSize, wantHeader) {} + + virtual std::auto_ptr<Statistic> create() { + return std::auto_ptr<Statistic>(new Stats); + } +}; + +}} // namespace qpid::tests + +#endif /*!TESTS_STATISTICS_H*/ diff --git a/qpid/cpp/src/tests/StringUtils.cpp b/qpid/cpp/src/tests/StringUtils.cpp new file mode 100644 index 0000000000..c50287a4f4 --- /dev/null +++ b/qpid/cpp/src/tests/StringUtils.cpp @@ -0,0 +1,81 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include <iostream> +#include "qpid/StringUtils.h" + +#include "unit_test.h" + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(StringUtilsTestSuite) + +using std::string; + +QPID_AUTO_TEST_CASE(testSplit_general) +{ + std::vector<std::string> results = split("a bbbb, car,d123,,,e", ", "); + BOOST_CHECK_EQUAL(5u, results.size()); + BOOST_CHECK_EQUAL(string("a"), results[0]); + BOOST_CHECK_EQUAL(string("bbbb"), results[1]); + BOOST_CHECK_EQUAL(string("car"), results[2]); + BOOST_CHECK_EQUAL(string("d123"), results[3]); + BOOST_CHECK_EQUAL(string("e"), results[4]); +} + +QPID_AUTO_TEST_CASE(testSplit_noDelims) +{ + std::vector<std::string> results = split("abc", ", "); + BOOST_CHECK_EQUAL(1u, results.size()); + BOOST_CHECK_EQUAL(string("abc"), results[0]); +} + +QPID_AUTO_TEST_CASE(testSplit_delimAtEnd) +{ + std::vector<std::string> results = split("abc def,,", ", "); + BOOST_CHECK_EQUAL(2u, results.size()); + BOOST_CHECK_EQUAL(string("abc"), results[0]); + BOOST_CHECK_EQUAL(string("def"), results[1]); +} + +QPID_AUTO_TEST_CASE(testSplit_delimAtStart) +{ + std::vector<std::string> results = split(",,abc def", ", "); + BOOST_CHECK_EQUAL(2u, results.size()); + BOOST_CHECK_EQUAL(string("abc"), results[0]); + BOOST_CHECK_EQUAL(string("def"), results[1]); +} + +QPID_AUTO_TEST_CASE(testSplit_onlyDelims) +{ + std::vector<std::string> results = split(",, , ", ", "); + BOOST_CHECK_EQUAL(0u, results.size()); +} + +QPID_AUTO_TEST_CASE(testSplit_empty) +{ + std::vector<std::string> results = split("", ", "); + BOOST_CHECK_EQUAL(0u, results.size()); +} + +QPID_AUTO_TEST_SUITE_END() + +}} diff --git a/qpid/cpp/src/tests/SystemInfo.cpp b/qpid/cpp/src/tests/SystemInfo.cpp new file mode 100644 index 0000000000..34f1ac408e --- /dev/null +++ b/qpid/cpp/src/tests/SystemInfo.cpp @@ -0,0 +1,36 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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. + * + */ + + +#include "unit_test.h" +#include "test_tools.h" +#include "qpid/sys/SystemInfo.h" +#include <boost/assign.hpp> + +using namespace std; +using namespace qpid::sys; +using namespace boost::assign; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(SystemInfoTestSuite) + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/TestMessageStore.h b/qpid/cpp/src/tests/TestMessageStore.h new file mode 100644 index 0000000000..0b63bc9c15 --- /dev/null +++ b/qpid/cpp/src/tests/TestMessageStore.h @@ -0,0 +1,63 @@ +#ifndef _tests_TestMessageStore_h +#define _tests_TestMessageStore_h + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/broker/NullMessageStore.h" +#include <vector> + +using namespace qpid; +using namespace qpid::broker; +using namespace qpid::framing; + +namespace qpid { +namespace tests { + +typedef std::pair<std::string, boost::intrusive_ptr<PersistableMessage> > msg_queue_pair; + +class TestMessageStore : public NullMessageStore +{ + public: + std::vector<boost::intrusive_ptr<PersistableMessage> > dequeued; + std::vector<msg_queue_pair> enqueued; + + void dequeue(TransactionContext*, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& /*queue*/) + { + dequeued.push_back(msg); + } + + void enqueue(TransactionContext*, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue) + { + msg->enqueueComplete(); + enqueued.push_back(msg_queue_pair(queue.getName(), msg)); + } + + TestMessageStore() : NullMessageStore() {} + ~TestMessageStore(){} +}; + +}} // namespace qpid::tests + +#endif diff --git a/qpid/cpp/src/tests/TestOptions.h b/qpid/cpp/src/tests/TestOptions.h new file mode 100644 index 0000000000..f8da0f59cf --- /dev/null +++ b/qpid/cpp/src/tests/TestOptions.h @@ -0,0 +1,79 @@ +#ifndef _TestOptions_ +#define _TestOptions_ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "qpid/Options.h" +#include "qpid/log/Options.h" +#include "qpid/Url.h" +#include "qpid/log/Logger.h" +#include "qpid/client/Connection.h" +#include "ConnectionOptions.h" + +#include <iostream> +#include <exception> + +namespace qpid { + +struct TestOptions : public qpid::Options +{ + TestOptions(const std::string& helpText_=std::string(), + const std::string& argv0=std::string()) + : Options("Test Options"), help(false), log(argv0), helpText(helpText_) + { + addOptions() + ("help", optValue(help), "print this usage statement"); + add(con); + add(log); + } + + /** As well as parsing, throw help message if requested. */ + void parse(int argc, char** argv) { + try { + qpid::Options::parse(argc, argv); + } catch (const std::exception& e) { + std::ostringstream msg; + msg << *this << std::endl << std::endl << e.what() << std::endl; + throw qpid::Options::Exception(msg.str()); + } + qpid::log::Logger::instance().configure(log); + if (help) { + std::ostringstream msg; + msg << *this << std::endl << std::endl << helpText << std::endl; + throw qpid::Options::Exception(msg.str()); + } + } + + /** Open a connection using option values */ + void open(qpid::client::Connection& connection) { + connection.open(con); + } + + + bool help; + ConnectionOptions con; + qpid::log::Options log; + std::string helpText; +}; + +} + +#endif diff --git a/qpid/cpp/src/tests/TimerTest.cpp b/qpid/cpp/src/tests/TimerTest.cpp new file mode 100644 index 0000000000..d28eeeffc1 --- /dev/null +++ b/qpid/cpp/src/tests/TimerTest.cpp @@ -0,0 +1,176 @@ + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/sys/Timer.h" +#include "qpid/sys/Monitor.h" +#include "qpid/Options.h" +#include "unit_test.h" +#include <math.h> +#include <iostream> +#include <memory> +#include <boost/format.hpp> +#include <boost/lexical_cast.hpp> + +using namespace qpid::sys; +using boost::intrusive_ptr; +using boost::dynamic_pointer_cast; + +namespace qpid { +namespace tests { + +class Counter +{ + Mutex lock; + uint counter; + public: + Counter() : counter(0) {} + uint next() + { + Mutex::ScopedLock l(lock); + return ++counter; + } +}; + +class TestTask : public TimerTask +{ + const AbsTime start; + const Duration expected; + AbsTime end; + bool fired; + uint position; + Monitor monitor; + Counter& counter; + + public: + TestTask(Duration timeout, Counter& _counter) + : TimerTask(timeout, "Test"), start(now()), expected(timeout), end(start), fired(false), counter(_counter) {} + + void fire() + { + Monitor::ScopedLock l(monitor); + fired = true; + position = counter.next(); + end = now(); + monitor.notify(); + } + + void check(uint expected_position, uint64_t tolerance = 500 * TIME_MSEC) + { + Monitor::ScopedLock l(monitor); + BOOST_CHECK(fired); + BOOST_CHECK_EQUAL(expected_position, position); + Duration actual(start, end); +#ifdef _MSC_VER + uint64_t difference = _abs64(expected - actual); +#elif defined(_WIN32) + uint64_t difference = labs(expected - actual); +#elif defined(__SUNPRO_CC) || defined (__IBMCPP__) + uint64_t difference = llabs(expected - actual); +#else + uint64_t difference = abs(expected - actual); +#endif + std::string msg(boost::lexical_cast<std::string>(boost::format("tolerance = %1%, difference = %2%") % tolerance % difference)); + BOOST_CHECK_MESSAGE(difference < tolerance, msg); + } + + void wait(Duration d) + { + Monitor::ScopedLock l(monitor); + monitor.wait(AbsTime(now(), d)); + } +}; + +class DummyRunner : public Runnable +{ + public: + void run() {} +}; + +QPID_AUTO_TEST_SUITE(TimerTestSuite) + +QPID_AUTO_TEST_CASE(testGeneral) +{ + Counter counter; + Timer timer; + intrusive_ptr<TestTask> task1(new TestTask(Duration(3 * TIME_SEC), counter)); + intrusive_ptr<TestTask> task2(new TestTask(Duration(1 * TIME_SEC), counter)); + intrusive_ptr<TestTask> task3(new TestTask(Duration(4 * TIME_SEC), counter)); + intrusive_ptr<TestTask> task4(new TestTask(Duration(2 * TIME_SEC), counter)); + + timer.add(task1); + timer.add(task2); + timer.add(task3); + timer.add(task4); + + dynamic_pointer_cast<TestTask>(task3)->wait(Duration(6 * TIME_SEC)); + + dynamic_pointer_cast<TestTask>(task1)->check(3); + dynamic_pointer_cast<TestTask>(task2)->check(1); + dynamic_pointer_cast<TestTask>(task3)->check(4); + dynamic_pointer_cast<TestTask>(task4)->check(2); +} + +std::string toString(Duration d) { return boost::lexical_cast<std::string>(d); } +Duration fromString(const std::string& str) { return boost::lexical_cast<Duration>(str); } + +QPID_AUTO_TEST_CASE(testOstreamInOut) { + std::string empty; + BOOST_CHECK_EQUAL(toString(Duration(int64_t(TIME_SEC))), "1s"); + BOOST_CHECK_EQUAL(toString(Duration(int64_t(TIME_SEC*123.4))), "123.4s"); + BOOST_CHECK_EQUAL(toString(Duration(int64_t(TIME_MSEC*123.4))), "123.4ms"); + BOOST_CHECK_EQUAL(toString(Duration(int64_t(TIME_USEC*123.4))), "123.4us"); + BOOST_CHECK_EQUAL(toString(Duration(int64_t(TIME_NSEC*123))), "123ns"); + + BOOST_CHECK_EQUAL(fromString("123.4"), Duration(int64_t(TIME_SEC*123.4))); + BOOST_CHECK_EQUAL(fromString("123.4s"), Duration(int64_t(TIME_SEC*123.4))); + BOOST_CHECK_EQUAL(fromString("123ms"), Duration(int64_t(TIME_MSEC*123))); + BOOST_CHECK_EQUAL(fromString("123us"), Duration(int64_t(TIME_USEC*123))); + BOOST_CHECK_EQUAL(fromString("123ns"), Duration(int64_t(TIME_NSEC*123))); + + Duration d = 0; + std::istringstream i; + std::string s; + + i.str("123x"); + i >> d; + BOOST_CHECK(i.fail()); + BOOST_CHECK_EQUAL(d, 0); + BOOST_CHECK_EQUAL(i.str(), "123x"); + + i.str("xxx"); + i >> d; + BOOST_CHECK(i.fail()); + BOOST_CHECK_EQUAL(d, 0); + BOOST_CHECK_EQUAL(i.str(), "xxx"); +} + +QPID_AUTO_TEST_CASE(testOptionParse) { + Options opts; + Duration interval; + opts.addOptions()("interval", optValue(interval, "I"), "blah"); + const char *args[] = { "fakeexe", "--interval", "123.4" }; + opts.parse(sizeof(args)/sizeof(args[0]), args); + BOOST_CHECK_EQUAL(interval, Duration(int64_t(TIME_SEC*123.4))); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/TopicExchangeTest.cpp b/qpid/cpp/src/tests/TopicExchangeTest.cpp new file mode 100644 index 0000000000..d57951ea3f --- /dev/null +++ b/qpid/cpp/src/tests/TopicExchangeTest.cpp @@ -0,0 +1,408 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +#include "qpid/broker/TopicKeyNode.h" +#include "qpid/broker/TopicExchange.h" +#include "unit_test.h" +#include "test_tools.h" + +using namespace qpid::broker; +using namespace std; + + +namespace qpid { +namespace broker { + +// Class for exercising the pattern match code in the TopicExchange +class TopicExchange::TopicExchangeTester { + +public: + typedef std::vector<std::string> BindingVec; + typedef TopicKeyNode<TopicExchange::BindingKey> TestBindingNode; + +private: + // binding node iterator that collects all routes that are bound + class TestFinder : public TestBindingNode::TreeIterator { + public: + TestFinder(BindingVec& m) : bv(m) {}; + ~TestFinder() {}; + bool visit(TestBindingNode& node) { + if (!node.bindings.bindingVector.empty()) + bv.push_back(node.routePattern); + return true; + } + + BindingVec& bv; + }; + +public: + TopicExchangeTester() {}; + ~TopicExchangeTester() {}; + bool addBindingKey(const std::string& bKey) { + string routingPattern = normalize(bKey); + BindingKey *bk = bindingTree.add(routingPattern); + if (bk) { + // push a dummy binding to mark this node as "non-leaf" + bk->bindingVector.push_back(Binding::shared_ptr()); + return true; + } + return false; + } + + bool removeBindingKey(const std::string& bKey){ + string routingPattern = normalize(bKey); + BindingKey *bk = bindingTree.get(routingPattern); + if (bk) { + bk->bindingVector.pop_back(); + if (bk->bindingVector.empty()) { + // no more bindings - remove this node + bindingTree.remove(routingPattern); + } + return true; + } + return false; + } + + void findMatches(const std::string& rKey, BindingVec& matches) { + TestFinder testFinder(matches); + bindingTree.iterateMatch( rKey, testFinder ); + } + + void getAll(BindingVec& bindings) { + TestFinder testFinder(bindings); + bindingTree.iterateAll( testFinder ); + } + +private: + TestBindingNode bindingTree; +}; +} // namespace broker + + +namespace tests { + +QPID_AUTO_TEST_SUITE(TopicExchangeTestSuite) + +#define CHECK_NORMALIZED(expect, pattern) BOOST_CHECK_EQUAL(expect, TopicExchange::normalize(pattern)); + +namespace { + // return the count of bindings that match 'pattern' + int match(TopicExchange::TopicExchangeTester &tt, + const std::string& pattern) + { + TopicExchange::TopicExchangeTester::BindingVec bv; + tt.findMatches(pattern, bv); + return int(bv.size()); + } + + // return true if expected contains exactly all bindings that match + // against pattern. + bool compare(TopicExchange::TopicExchangeTester& tt, + const std::string& pattern, + const TopicExchange::TopicExchangeTester::BindingVec& expected) + { + TopicExchange::TopicExchangeTester::BindingVec bv; + tt.findMatches(pattern, bv); + if (expected.size() != bv.size()) { + // std::cout << "match failed 1 f=[" << bv << "]" << std::endl; + // std::cout << "match failed 1 e=[" << expected << "]" << std::endl; + return false; + } + TopicExchange::TopicExchangeTester::BindingVec::const_iterator i; + for (i = expected.begin(); i != expected.end(); i++) { + TopicExchange::TopicExchangeTester::BindingVec::iterator j; + for (j = bv.begin(); j != bv.end(); j++) { + // std::cout << "matched [" << *j << "]" << std::endl; + if (*i == *j) break; + } + if (j == bv.end()) { + // std::cout << "match failed 2 [" << bv << "]" << std::endl; + return false; + } + } + return true; + } +} + + +QPID_AUTO_TEST_CASE(testNormalize) +{ + CHECK_NORMALIZED("", ""); + CHECK_NORMALIZED("a.b.c", "a.b.c"); + CHECK_NORMALIZED("a.*.c", "a.*.c"); + CHECK_NORMALIZED("#", "#"); + CHECK_NORMALIZED("#", "#.#.#.#"); + CHECK_NORMALIZED("*.*.*.#", "#.*.#.*.#.#.*"); + CHECK_NORMALIZED("a.*.*.*.#", "a.*.#.*.#.*.#"); + CHECK_NORMALIZED("a.*.*.*.#", "a.*.#.*.#.*"); + CHECK_NORMALIZED("*.*.*.#", "*.#.#.*.*.#"); +} + +QPID_AUTO_TEST_CASE(testPlain) +{ + TopicExchange::TopicExchangeTester tt; + string pattern("ab.cd.e"); + + BOOST_CHECK(tt.addBindingKey(pattern)); + BOOST_CHECK_EQUAL(1, match(tt, "ab.cd.e")); + BOOST_CHECK_EQUAL(0, match(tt, "abx.cd.e")); + BOOST_CHECK_EQUAL(0, match(tt, "ab.cd")); + BOOST_CHECK_EQUAL(0, match(tt, "ab.cd..e.")); + BOOST_CHECK_EQUAL(0, match(tt, "ab.cd.e.")); + BOOST_CHECK_EQUAL(0, match(tt, ".ab.cd.e")); + BOOST_CHECK(tt.removeBindingKey(pattern)); + + pattern = ""; + BOOST_CHECK(tt.addBindingKey(pattern)); + BOOST_CHECK_EQUAL(1, match(tt, "")); + BOOST_CHECK(tt.removeBindingKey(pattern)); + + pattern = "."; + BOOST_CHECK(tt.addBindingKey(pattern)); + BOOST_CHECK_EQUAL(1, match(tt, ".")); + BOOST_CHECK(tt.removeBindingKey(pattern)); +} + + +QPID_AUTO_TEST_CASE(testStar) +{ + TopicExchange::TopicExchangeTester tt; + string pattern("a.*.b"); + BOOST_CHECK(tt.addBindingKey(pattern)); + BOOST_CHECK_EQUAL(1, match(tt, "a.xx.b")); + BOOST_CHECK_EQUAL(0, match(tt, "a.b")); + BOOST_CHECK(tt.removeBindingKey(pattern)); + + pattern = "*.x"; + BOOST_CHECK(tt.addBindingKey(pattern)); + BOOST_CHECK_EQUAL(1, match(tt, "y.x")); + BOOST_CHECK_EQUAL(1, match(tt, ".x")); + BOOST_CHECK_EQUAL(0, match(tt, "x")); + BOOST_CHECK(tt.removeBindingKey(pattern)); + + pattern = "x.x.*"; + BOOST_CHECK(tt.addBindingKey(pattern)); + BOOST_CHECK_EQUAL(1, match(tt, "x.x.y")); + BOOST_CHECK_EQUAL(1, match(tt, "x.x.")); + BOOST_CHECK_EQUAL(0, match(tt, "x.x")); + BOOST_CHECK_EQUAL(0, match(tt, "q.x.y")); + BOOST_CHECK(tt.removeBindingKey(pattern)); +} + +QPID_AUTO_TEST_CASE(testHash) +{ + TopicExchange::TopicExchangeTester tt; + string pattern("a.#.b"); + BOOST_CHECK(tt.addBindingKey(pattern)); + BOOST_CHECK_EQUAL(1, match(tt, "a.b")); + BOOST_CHECK_EQUAL(1, match(tt, "a.x.b")); + BOOST_CHECK_EQUAL(1, match(tt, "a..x.y.zz.b")); + BOOST_CHECK_EQUAL(0, match(tt, "a.b.")); + BOOST_CHECK_EQUAL(0, match(tt, "q.x.b")); + BOOST_CHECK(tt.removeBindingKey(pattern)); + + pattern = "a.#"; + BOOST_CHECK(tt.addBindingKey(pattern)); + BOOST_CHECK_EQUAL(1, match(tt, "a")); + BOOST_CHECK_EQUAL(1, match(tt, "a.b")); + BOOST_CHECK_EQUAL(1, match(tt, "a.b.c")); + BOOST_CHECK(tt.removeBindingKey(pattern)); + + pattern = "#.a"; + BOOST_CHECK(tt.addBindingKey(pattern)); + BOOST_CHECK_EQUAL(1, match(tt, "a")); + BOOST_CHECK_EQUAL(1, match(tt, "x.y.a")); + BOOST_CHECK(tt.removeBindingKey(pattern)); + + pattern = "a.#.b.#.c"; + BOOST_CHECK(tt.addBindingKey(pattern)); + BOOST_CHECK_EQUAL(1, match(tt, "a.b.c")); + BOOST_CHECK_EQUAL(1, match(tt, "a.x.b.y.c")); + BOOST_CHECK_EQUAL(1, match(tt, "a.x.x.b.y.y.c")); + BOOST_CHECK(tt.removeBindingKey(pattern)); +} + +QPID_AUTO_TEST_CASE(testMixed) +{ + TopicExchange::TopicExchangeTester tt; + string pattern("*.x.#.y"); + BOOST_CHECK(tt.addBindingKey(pattern)); + BOOST_CHECK_EQUAL(1, match(tt, "a.x.y")); + BOOST_CHECK_EQUAL(1, match(tt, "a.x.p.qq.y")); + BOOST_CHECK_EQUAL(0, match(tt, "a.a.x.y")); + BOOST_CHECK_EQUAL(0, match(tt, "aa.x.b.c")); + BOOST_CHECK(tt.removeBindingKey(pattern)); + + pattern = "a.#.b.*"; + BOOST_CHECK(tt.addBindingKey(pattern)); + BOOST_CHECK_EQUAL(1, match(tt, "a.b.x")); + BOOST_CHECK_EQUAL(1, match(tt, "a.x.x.x.b.x")); + BOOST_CHECK(tt.removeBindingKey(pattern)); + + pattern = "*.*.*.#"; + BOOST_CHECK(tt.addBindingKey(pattern)); + BOOST_CHECK_EQUAL(1, match(tt, "x.y.z")); + BOOST_CHECK_EQUAL(1, match(tt, "x.y.z.a.b.c")); + BOOST_CHECK_EQUAL(0, match(tt, "x.y")); + BOOST_CHECK_EQUAL(0, match(tt, "x")); + BOOST_CHECK(tt.removeBindingKey(pattern)); +} + + +QPID_AUTO_TEST_CASE(testMultiple) +{ + TopicExchange::TopicExchangeTester tt; + const std::string bindings[] = + { "a", "b", + "a.b", "b.c", + "a.b.c.d", "b.c.d.e", + "a.*", "a.#", "a.*.#", + "#.b", "*.b", "*.#.b", + "a.*.b", "a.#.b", "a.*.#.b", + "*.b.*", "#.b.#", + }; + const size_t nBindings = sizeof(bindings)/sizeof(bindings[0]); + + // setup bindings + for (size_t idx = 0; idx < nBindings; idx++) { + BOOST_CHECK(tt.addBindingKey(bindings[idx])); + } + + { + // read all bindings, and verify all are present + TopicExchange::TopicExchangeTester::BindingVec b; + tt.getAll(b); + BOOST_CHECK_EQUAL(b.size(), nBindings); + for (size_t idx = 0; idx < nBindings; idx++) { + bool found = false; + for (TopicExchange::TopicExchangeTester::BindingVec::iterator i = b.begin(); + i != b.end(); i++) { + if (*i == bindings[idx]) { + found = true; + break; + } + } + BOOST_CHECK(found); + } + } + + { // test match on pattern "a" + const std::string matches[] = { "a", "a.#" }; + const size_t nMatches = 2; + TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); + BOOST_CHECK(compare(tt, "a", expected)); + } + + { // test match on pattern "a.z" + const std::string matches[] = { "a.*", "a.#", "a.*.#" }; + const size_t nMatches = 3; + TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); + BOOST_CHECK(compare(tt, "a.z", expected)); + } + + { // test match on pattern "a.b" + const std::string matches[] = { + "a.b", "a.*", "a.#", "a.*.#", + "#.b", "#.b.#", "*.#.b", "*.b", + "a.#.b" + }; + const size_t nMatches = 9; + TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); + BOOST_CHECK(compare(tt, "a.b", expected)); + } + + { // test match on pattern "a.c.c.b" + + const std::string matches[] = { + "#.b", "#.b.#", "*.#.b", "a.#.b", + "a.#", "a.*.#.b", "a.*.#" + }; + const size_t nMatches = 7; + TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); + BOOST_CHECK(compare(tt, "a.c.c.b", expected)); + } + + { // test match on pattern "a.b.c" + + const std::string matches[] = { + "#.b.#", "*.b.*", "a.#", "a.*.#" + }; + const size_t nMatches = 4; + TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); + BOOST_CHECK(compare(tt, "a.b.c", expected)); + } + + { // test match on pattern "b" + + const std::string matches[] = { + "#.b", "#.b.#", "b" + }; + const size_t nMatches = 3; + TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); + BOOST_CHECK(compare(tt, "b", expected)); + } + + { // test match on pattern "x.b" + + const std::string matches[] = { + "#.b", "#.b.#", "*.#.b", "*.b" + }; + const size_t nMatches = 4; + TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); + BOOST_CHECK(compare(tt, "x.b", expected)); + } + + { // test match on pattern "x.y.z.b" + + const std::string matches[] = { + "#.b", "#.b.#", "*.#.b" + }; + const size_t nMatches = 3; + TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); + BOOST_CHECK(compare(tt, "x.y.z.b", expected)); + } + + { // test match on pattern "x.y.z.b.a.b.c" + + const std::string matches[] = { + "#.b.#", "#.b.#" + }; + const size_t nMatches = 2; + TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); + BOOST_CHECK(compare(tt, "x.y.z.b.a.b.c", expected)); + } + + { // test match on pattern "a.b.c.d" + + const std::string matches[] = { + "#.b.#", "a.#", "a.*.#", "a.b.c.d", + }; + const size_t nMatches = 4; + TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); + BOOST_CHECK(compare(tt, "a.b.c.d", expected)); + } + + // cleanup bindings + for (size_t idx = 0; idx < nBindings; idx++) { + BOOST_CHECK(tt.removeBindingKey(bindings[idx])); + } +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/TransactionObserverTest.cpp b/qpid/cpp/src/tests/TransactionObserverTest.cpp new file mode 100644 index 0000000000..80ef494c21 --- /dev/null +++ b/qpid/cpp/src/tests/TransactionObserverTest.cpp @@ -0,0 +1,147 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "unit_test.h" +#include "test_tools.h" +#include "MessagingFixture.h" +#include "qpid/broker/BrokerObserver.h" +#include "qpid/broker/TransactionObserver.h" +#include "qpid/broker/TxBuffer.h" +#include "qpid/broker/Queue.h" +#include "qpid/ha/types.h" + +#include <boost/bind.hpp> +#include <boost/function.hpp> +#include <boost/lexical_cast.hpp> +#include <iostream> +#include <vector> + +namespace qpid { +namespace tests { + +using framing::SequenceSet; +using messaging::Message; +using boost::shared_ptr; + +using namespace boost::assign; +using namespace boost; +using namespace broker; +using namespace std; +using namespace messaging; +using namespace types; + +QPID_AUTO_TEST_SUITE(TransactionalObserverTest) + +Message msg(string content) { return Message(content); } + +struct MockTransactionObserver : public TransactionObserver { + bool prep; + vector<string> events; + + MockTransactionObserver(bool prep_=true) : prep(prep_) {} + + void record(const string& e) { events.push_back(e); } + + void enqueue(const shared_ptr<Queue>& q, const broker::Message& m) { + record("enqueue "+q->getName()+" "+m.getContent()); + } + void dequeue(const Queue::shared_ptr& q, SequenceNumber p, SequenceNumber r) { + record("dequeue "+q->getName()+" "+ + lexical_cast<string>(p)+" "+lexical_cast<string>(r)); + } + bool prepare() { record("prepare"); return prep; } + void commit() { record("commit"); } + void rollback() {record("rollback"); } +}; + +struct MockBrokerObserver : public BrokerObserver { + bool prep; + shared_ptr<MockTransactionObserver> tx; + + MockBrokerObserver(bool prep_=true) : prep(prep_) {} + + void startTx(const intrusive_ptr<TxBuffer>& buffer) { + if (!tx) { // Don't overwrite first tx with automatically started second tx. + tx.reset(new MockTransactionObserver(prep)); + buffer->setObserver(tx); + } + } +}; + +Session simpleTxTransaction(MessagingFixture& fix) { + fix.session.createSender("q1;{create:always}").send(msg("foo")); // Not in TX + // Transaction with 1 enqueue and 1 dequeue. + Session txSession = fix.connection.createTransactionalSession(); + BOOST_CHECK_EQUAL("foo", txSession.createReceiver("q1").fetch().getContent()); + txSession.acknowledge(); + txSession.createSender("q2;{create:always}").send(msg("bar")); + return txSession; +} + +QPID_AUTO_TEST_CASE(testTxCommit) { + MessagingFixture fix; + shared_ptr<MockBrokerObserver> brokerObserver(new MockBrokerObserver); + fix.broker->getBrokerObservers().add(brokerObserver); + Session txSession = simpleTxTransaction(fix); + txSession.commit(); + // Note on ordering: observers see enqueues as they happen, but dequeues just + // before prepare. + BOOST_CHECK_EQUAL( + list_of<string>("enqueue q2 bar")("dequeue q1 1 0")("prepare")("commit"), + brokerObserver->tx->events + ); +} + +QPID_AUTO_TEST_CASE(testTxFail) { + MessagingFixture fix; + shared_ptr<MockBrokerObserver> brokerObserver(new MockBrokerObserver(false)); + fix.broker->getBrokerObservers().add(brokerObserver); + Session txSession = simpleTxTransaction(fix); + try { + ScopedSuppressLogging sl; // Suppress messages for expected error. + txSession.commit(); + BOOST_FAIL("Expected exception"); + } catch(...) {} + + BOOST_CHECK_EQUAL( + list_of<string>("enqueue q2 bar")("dequeue q1 1 0")("prepare")("rollback"), + brokerObserver->tx->events + ); +} + +QPID_AUTO_TEST_CASE(testTxRollback) { + MessagingFixture fix; + shared_ptr<MockBrokerObserver> brokerObserver(new MockBrokerObserver(false)); + fix.broker->getBrokerObservers().add(brokerObserver); + Session txSession = simpleTxTransaction(fix); + txSession.rollback(); + // Note: The dequeue does not appear here. This is because TxAccepts + // (i.e. dequeues) are not enlisted until SemanticState::commit and are + // never enlisted if the transaction is rolled back. + BOOST_CHECK_EQUAL( + list_of<string>("enqueue q2 bar")("rollback"), + brokerObserver->tx->events + ); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/TxBufferTest.cpp b/qpid/cpp/src/tests/TxBufferTest.cpp new file mode 100644 index 0000000000..3f052d213e --- /dev/null +++ b/qpid/cpp/src/tests/TxBufferTest.cpp @@ -0,0 +1,188 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +#include "qpid/broker/TxBuffer.h" +#include "unit_test.h" +#include "test_tools.h" +#include <iostream> +#include <vector> +#include "TxMocks.h" + +using namespace qpid::broker; +using boost::static_pointer_cast; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(TxBufferTestSuite) + +QPID_AUTO_TEST_CASE(testCommitLocal) +{ + MockTransactionalStore store; + store.expectBegin().expectCommit(); + + MockTxOp::shared_ptr opA(new MockTxOp()); + opA->expectPrepare().expectCommit(); + MockTxOp::shared_ptr opB(new MockTxOp()); + opB->expectPrepare().expectPrepare().expectCommit().expectCommit();//opB enlisted twice to test relative order + MockTxOp::shared_ptr opC(new MockTxOp()); + opC->expectPrepare().expectCommit(); + + TxBuffer buffer; + buffer.enlist(static_pointer_cast<TxOp>(opA)); + buffer.enlist(static_pointer_cast<TxOp>(opB)); + buffer.enlist(static_pointer_cast<TxOp>(opB));//opB enlisted twice + buffer.enlist(static_pointer_cast<TxOp>(opC)); + + buffer.startCommit(&store); + buffer.endCommit(&store); + store.check(); + BOOST_CHECK(store.isCommitted()); + opA->check(); + opB->check(); + opC->check(); +} + +QPID_AUTO_TEST_CASE(testFailOnCommitLocal) +{ + MockTransactionalStore store; + store.expectBegin().expectAbort(); + + MockTxOp::shared_ptr opA(new MockTxOp()); + opA->expectPrepare().expectRollback(); + MockTxOp::shared_ptr opB(new MockTxOp(true)); + opB->expectPrepare().expectRollback(); + MockTxOp::shared_ptr opC(new MockTxOp());//will never get prepare as b will fail + opC->expectRollback(); + + TxBuffer buffer; + buffer.enlist(static_pointer_cast<TxOp>(opA)); + buffer.enlist(static_pointer_cast<TxOp>(opB)); + buffer.enlist(static_pointer_cast<TxOp>(opC)); + + try { + ScopedSuppressLogging sl; // Suppress messages for expected error. + buffer.startCommit(&store); + buffer.endCommit(&store); + BOOST_FAIL("Expected exception"); + } catch (...) {} + BOOST_CHECK(store.isAborted()); + store.check(); + opA->check(); + opB->check(); + opC->check(); +} + +QPID_AUTO_TEST_CASE(testPrepare) +{ + MockTxOp::shared_ptr opA(new MockTxOp()); + opA->expectPrepare(); + MockTxOp::shared_ptr opB(new MockTxOp()); + opB->expectPrepare(); + MockTxOp::shared_ptr opC(new MockTxOp()); + opC->expectPrepare(); + + TxBuffer buffer; + buffer.enlist(static_pointer_cast<TxOp>(opA)); + buffer.enlist(static_pointer_cast<TxOp>(opB)); + buffer.enlist(static_pointer_cast<TxOp>(opC)); + + BOOST_CHECK(buffer.prepare(0)); + opA->check(); + opB->check(); + opC->check(); +} + +QPID_AUTO_TEST_CASE(testFailOnPrepare) +{ + MockTxOp::shared_ptr opA(new MockTxOp()); + opA->expectPrepare(); + MockTxOp::shared_ptr opB(new MockTxOp(true)); + opB->expectPrepare(); + MockTxOp::shared_ptr opC(new MockTxOp());//will never get prepare as b will fail + + TxBuffer buffer; + buffer.enlist(static_pointer_cast<TxOp>(opA)); + buffer.enlist(static_pointer_cast<TxOp>(opB)); + buffer.enlist(static_pointer_cast<TxOp>(opC)); + + BOOST_CHECK(!buffer.prepare(0)); + opA->check(); + opB->check(); + opC->check(); +} + +QPID_AUTO_TEST_CASE(testRollback) +{ + MockTxOp::shared_ptr opA(new MockTxOp()); + opA->expectRollback(); + MockTxOp::shared_ptr opB(new MockTxOp(true)); + opB->expectRollback(); + MockTxOp::shared_ptr opC(new MockTxOp()); + opC->expectRollback(); + + TxBuffer buffer; + buffer.enlist(static_pointer_cast<TxOp>(opA)); + buffer.enlist(static_pointer_cast<TxOp>(opB)); + buffer.enlist(static_pointer_cast<TxOp>(opC)); + + buffer.rollback(); + opA->check(); + opB->check(); + opC->check(); +} + +QPID_AUTO_TEST_CASE(testBufferIsClearedAfterRollback) +{ + MockTxOp::shared_ptr opA(new MockTxOp()); + opA->expectRollback(); + MockTxOp::shared_ptr opB(new MockTxOp()); + opB->expectRollback(); + + TxBuffer buffer; + buffer.enlist(static_pointer_cast<TxOp>(opA)); + buffer.enlist(static_pointer_cast<TxOp>(opB)); + + buffer.rollback(); + buffer.commit();//second call should not reach ops + opA->check(); + opB->check(); +} + +QPID_AUTO_TEST_CASE(testBufferIsClearedAfterCommit) +{ + MockTxOp::shared_ptr opA(new MockTxOp()); + opA->expectCommit(); + MockTxOp::shared_ptr opB(new MockTxOp()); + opB->expectCommit(); + + TxBuffer buffer; + buffer.enlist(static_pointer_cast<TxOp>(opA)); + buffer.enlist(static_pointer_cast<TxOp>(opB)); + + buffer.commit(); + buffer.rollback();//second call should not reach ops + opA->check(); + opB->check(); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/TxMocks.h b/qpid/cpp/src/tests/TxMocks.h new file mode 100644 index 0000000000..8b54e7484b --- /dev/null +++ b/qpid/cpp/src/tests/TxMocks.h @@ -0,0 +1,236 @@ +/* + * + * 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. + * + */ +#ifndef _tests_TxMocks_h +#define _tests_TxMocks_h + + +#include "qpid/Exception.h" +#include "qpid/Msg.h" +#include "qpid/broker/TransactionalStore.h" +#include "qpid/broker/TxOp.h" +#include <iostream> +#include <vector> + +using namespace qpid::broker; +using boost::static_pointer_cast; +using std::string; + +namespace qpid { +namespace tests { + +template <class T> void assertEqualVector(std::vector<T>& expected, std::vector<T>& actual){ + unsigned int i = 0; + while(i < expected.size() && i < actual.size()){ + BOOST_CHECK_EQUAL(expected[i], actual[i]); + i++; + } + if (i < expected.size()) { + throw qpid::Exception(QPID_MSG("Missing " << expected[i])); + } else if (i < actual.size()) { + throw qpid::Exception(QPID_MSG("Extra " << actual[i])); + } + BOOST_CHECK_EQUAL(expected.size(), actual.size()); +} + +class TxOpConstants{ +protected: + const string PREPARE; + const string COMMIT; + const string ROLLBACK; + + TxOpConstants() : PREPARE("PREPARE"), COMMIT("COMMIT"), ROLLBACK("ROLLBACK") {} +}; + +class MockTxOp : public TxOp, public TxOpConstants{ + std::vector<string> expected; + std::vector<string> actual; + bool failOnPrepare; + string debugName; +public: + typedef boost::shared_ptr<MockTxOp> shared_ptr; + + MockTxOp() : failOnPrepare(false) {} + MockTxOp(bool _failOnPrepare) : failOnPrepare(_failOnPrepare) {} + + void setDebugName(string name){ + debugName = name; + } + + void printExpected(){ + std::cout << std::endl << "MockTxOp[" << debugName << "] expects: "; + for (std::vector<string>::iterator i = expected.begin(); i < expected.end(); i++) { + if(i != expected.begin()) std::cout << ", "; + std::cout << *i; + } + std::cout << std::endl; + } + + void printActual(){ + std::cout << std::endl << "MockTxOp[" << debugName << "] actual: "; + for (std::vector<string>::iterator i = actual.begin(); i < actual.end(); i++) { + if(i != actual.begin()) std::cout << ", "; + std::cout << *i; + } + std::cout << std::endl; + } + + bool prepare(TransactionContext*) throw(){ + actual.push_back(PREPARE); + return !failOnPrepare; + } + void commit() throw(){ + actual.push_back(COMMIT); + } + void rollback() throw(){ + if(!debugName.empty()) std::cout << std::endl << "MockTxOp[" << debugName << "]::rollback()" << std::endl; + actual.push_back(ROLLBACK); + } + + void callObserver(const boost::shared_ptr<TransactionObserver>&) {} + + MockTxOp& expectPrepare(){ + expected.push_back(PREPARE); + return *this; + } + MockTxOp& expectCommit(){ + expected.push_back(COMMIT); + return *this; + } + MockTxOp& expectRollback(){ + expected.push_back(ROLLBACK); + return *this; + } + void check(){ + assertEqualVector(expected, actual); + } + + ~MockTxOp(){} +}; + +class MockTransactionalStore : public TransactionalStore{ + const string BEGIN; + const string BEGIN2PC; + const string PREPARE; + const string COMMIT; + const string ABORT; + std::vector<string> expected; + std::vector<string> actual; + + enum states {OPEN = 1, PREPARED = 2, COMMITTED = 3, ABORTED = 4}; + int state; + + class TestTransactionContext : public TPCTransactionContext{ + MockTransactionalStore* store; + public: + TestTransactionContext(MockTransactionalStore* _store) : store(_store) {} + void prepare(){ + if(!store->isOpen()) throw "txn already completed"; + store->state = PREPARED; + } + + void commit(){ + if(!store->isOpen() && !store->isPrepared()) throw "txn already completed"; + store->state = COMMITTED; + } + + void abort(){ + if(!store->isOpen() && !store->isPrepared()) throw "txn already completed"; + store->state = ABORTED; + } + ~TestTransactionContext(){} + }; + +public: + MockTransactionalStore() : + BEGIN("BEGIN"), BEGIN2PC("BEGIN2PC"), PREPARE("PREPARE"), COMMIT("COMMIT"), ABORT("ABORT"), state(OPEN){} + + void collectPreparedXids(std::set<std::string>&) + { + throw "Operation not supported"; + } + + std::auto_ptr<TPCTransactionContext> begin(const std::string&){ + actual.push_back(BEGIN2PC); + std::auto_ptr<TPCTransactionContext> txn(new TestTransactionContext(this)); + return txn; + } + std::auto_ptr<TransactionContext> begin(){ + actual.push_back(BEGIN); + std::auto_ptr<TransactionContext> txn(new TestTransactionContext(this)); + return txn; + } + void prepare(TPCTransactionContext& ctxt){ + actual.push_back(PREPARE); + dynamic_cast<TestTransactionContext&>(ctxt).prepare(); + } + void commit(TransactionContext& ctxt){ + actual.push_back(COMMIT); + dynamic_cast<TestTransactionContext&>(ctxt).commit(); + } + void abort(TransactionContext& ctxt){ + actual.push_back(ABORT); + dynamic_cast<TestTransactionContext&>(ctxt).abort(); + } + MockTransactionalStore& expectBegin(){ + expected.push_back(BEGIN); + return *this; + } + MockTransactionalStore& expectBegin2PC(){ + expected.push_back(BEGIN2PC); + return *this; + } + MockTransactionalStore& expectPrepare(){ + expected.push_back(PREPARE); + return *this; + } + MockTransactionalStore& expectCommit(){ + expected.push_back(COMMIT); + return *this; + } + MockTransactionalStore& expectAbort(){ + expected.push_back(ABORT); + return *this; + } + void check(){ + assertEqualVector(expected, actual); + } + + bool isPrepared(){ + return state == PREPARED; + } + + bool isCommitted(){ + return state == COMMITTED; + } + + bool isAborted(){ + return state == ABORTED; + } + + bool isOpen() const{ + return state == OPEN; + } + ~MockTransactionalStore(){} +}; + +}} // namespace qpid::tests + +#endif diff --git a/qpid/cpp/src/tests/Url.cpp b/qpid/cpp/src/tests/Url.cpp new file mode 100644 index 0000000000..b30de682bc --- /dev/null +++ b/qpid/cpp/src/tests/Url.cpp @@ -0,0 +1,116 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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. + * + */ + + +#include "unit_test.h" +#include "test_tools.h" +#include "qpid/Url.h" +#include <boost/assign.hpp> + +using namespace std; +using namespace qpid; +using namespace boost::assign; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(UrlTestSuite) + +#define URL_CHECK_STR(STR) BOOST_CHECK_EQUAL(Url(STR).str(), STR) +#define URL_CHECK_INVALID(STR) BOOST_CHECK_THROW(Url(STR), Url::Invalid) + +QPID_AUTO_TEST_CASE(TestParseTcp) { + URL_CHECK_STR("amqp:tcp:host:42"); + URL_CHECK_STR("amqp:tcp:host-._~%ff%23:42"); // unreserved chars and pct encoded hex. + // Check defaults + BOOST_CHECK_EQUAL(Url("amqp:host:42").str(), "amqp:tcp:host:42"); + BOOST_CHECK_EQUAL(Url("amqp:tcp:host").str(), "amqp:tcp:host:5672"); + BOOST_CHECK_EQUAL(Url("host").str(), "amqp:tcp:host:5672"); +} + +QPID_AUTO_TEST_CASE(TestParseInvalid) { + //host is required: + URL_CHECK_INVALID("amqp:tcp:"); + URL_CHECK_INVALID("amqp:"); + URL_CHECK_INVALID("amqp::42"); + URL_CHECK_INVALID(""); + + // Port must be numeric + URL_CHECK_INVALID("host:badPort"); +} + +QPID_AUTO_TEST_CASE(TestParseXyz) { + Url::addProtocol("xyz"); + URL_CHECK_STR("amqp:xyz:host:123"); + BOOST_CHECK_EQUAL(Url("xyz:host").str(), "amqp:xyz:host:5672"); +} + +QPID_AUTO_TEST_CASE(TestParseTricky) { + BOOST_CHECK_EQUAL(Url("amqp").str(), "amqp:tcp:amqp:5672"); + BOOST_CHECK_EQUAL(Url("amqp:tcp").str(), "amqp:tcp:tcp:5672"); + // These are ambiguous parses and arguably not the best result + BOOST_CHECK_EQUAL(Url("amqp:876").str(), "amqp:tcp:876:5672"); + BOOST_CHECK_EQUAL(Url("tcp:567").str(), "amqp:tcp:567:5672"); +} + +QPID_AUTO_TEST_CASE(TestParseIPv6) { + Url u1("[::]"); + BOOST_CHECK_EQUAL(u1[0].host, "::"); + BOOST_CHECK_EQUAL(u1[0].port, 5672); + Url u2("[::1]"); + BOOST_CHECK_EQUAL(u2[0].host, "::1"); + BOOST_CHECK_EQUAL(u2[0].port, 5672); + Url u3("[::127.0.0.1]"); + BOOST_CHECK_EQUAL(u3[0].host, "::127.0.0.1"); + BOOST_CHECK_EQUAL(u3[0].port, 5672); + Url u4("[2002::222:68ff:fe0b:e61a]"); + BOOST_CHECK_EQUAL(u4[0].host, "2002::222:68ff:fe0b:e61a"); + BOOST_CHECK_EQUAL(u4[0].port, 5672); + Url u5("[2002::222:68ff:fe0b:e61a]:123"); + BOOST_CHECK_EQUAL(u5[0].host, "2002::222:68ff:fe0b:e61a"); + BOOST_CHECK_EQUAL(u5[0].port, 123); +} + +QPID_AUTO_TEST_CASE(TestParseMultiAddress) { + Url::addProtocol("xyz"); + URL_CHECK_STR("amqp:tcp:host:0,xyz:foo:123,tcp:foo:0,xyz:bar:1"); + URL_CHECK_STR("amqp:xyz:foo:222,tcp:foo:0"); + URL_CHECK_INVALID("amqp:tcp:h:0,"); + URL_CHECK_INVALID(",amqp:tcp:h"); +} + +QPID_AUTO_TEST_CASE(TestParseUserPass) { + URL_CHECK_STR("amqp:user/pass@tcp:host:123"); + URL_CHECK_STR("amqp:user@tcp:host:123"); + BOOST_CHECK_EQUAL(Url("user/pass@host").str(), "amqp:user/pass@tcp:host:5672"); + BOOST_CHECK_EQUAL(Url("user@host").str(), "amqp:user@tcp:host:5672"); + + Url u("user/pass@host"); + BOOST_CHECK_EQUAL(u.getUser(), "user"); + BOOST_CHECK_EQUAL(u.getPass(), "pass"); + Url v("foo@host"); + BOOST_CHECK_EQUAL(v.getUser(), "foo"); + BOOST_CHECK_EQUAL(v.getPass(), ""); + u = v; + BOOST_CHECK_EQUAL(u.getUser(), "foo"); + BOOST_CHECK_EQUAL(u.getPass(), ""); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/Uuid.cpp b/qpid/cpp/src/tests/Uuid.cpp new file mode 100644 index 0000000000..f9a67d9db0 --- /dev/null +++ b/qpid/cpp/src/tests/Uuid.cpp @@ -0,0 +1,150 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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. + * + */ + +#include "qpid/framing/Uuid.h" +#include "qpid/framing/Buffer.h" +#include "qpid/types/Uuid.h" + +#include "unit_test.h" + +#include <set> + +#include <boost/array.hpp> + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(UuidTestSuite) + +using namespace std; +using namespace qpid::framing; + +struct UniqueSet : public std::set<Uuid> { + void operator()(const Uuid& uuid) { + BOOST_REQUIRE(find(uuid) == end()); + insert(uuid); + } +}; + +QPID_AUTO_TEST_CASE(testUuidCtor) { + // Uniqueness + boost::array<Uuid,1000> uuids; + for_each(uuids.begin(), uuids.end(), mem_fun_ref(&Uuid::generate)); + UniqueSet unique; + for_each(uuids.begin(), uuids.end(), unique); +} + +boost::array<uint8_t, 16> sample = {{0x1b, 0x4e, 0x28, 0xba, 0x2f, 0xa1, 0x11, 0x02, 0x88, 0x3f, 0xb9, 0xa7, 0x61, 0xbd, 0xe3, 0xfb}}; +const string sampleStr("1b4e28ba-2fa1-1102-883f-b9a761bde3fb"); +const string zeroStr("00000000-0000-0000-0000-000000000000"); +const string badUuid1("1b4e28ba-2fa1-11d2-883f-b9761bde3fb"); +const string badUuid2("1b4e28ba-2fa1-11d23883f-b9761dbde3fb"); + +QPID_AUTO_TEST_CASE(testUuidIstream) { + Uuid uuid; + istringstream in(sampleStr); + in >> uuid; + BOOST_CHECK(!in.fail()); + BOOST_CHECK(::memcmp(uuid.data(), sample.data(), uuid.size())==0); + + istringstream is(zeroStr); + Uuid zero; + is >> zero; + BOOST_CHECK(!is.fail()); + BOOST_CHECK_EQUAL(zero, Uuid()); +} + +QPID_AUTO_TEST_CASE(testUuidOstream) { + Uuid uuid(sample.c_array()); + ostringstream out; + out << uuid; + BOOST_CHECK(out.good()); + BOOST_CHECK_EQUAL(out.str(), sampleStr); + + ostringstream os; + os << Uuid(); + BOOST_CHECK(out.good()); + BOOST_CHECK_EQUAL(os.str(), zeroStr); +} + +QPID_AUTO_TEST_CASE(testBadUuidIstream) { + Uuid a; + istringstream is(badUuid1); + is >> a; + BOOST_CHECK(!is.good()); + istringstream is2(badUuid2); + is2 >> a; + BOOST_CHECK(!is2.good()); +} + +QPID_AUTO_TEST_CASE(testUuidIOstream) { + Uuid a(true), b(true); + ostringstream os; + os << a << endl << b; + Uuid aa, bb; + istringstream is(os.str()); + is >> aa >> ws >> bb; + BOOST_CHECK(os.good()); + BOOST_CHECK_EQUAL(a, aa); + BOOST_CHECK_EQUAL(b, bb); +} + +QPID_AUTO_TEST_CASE(testUuidEncodeDecode) { + std::vector<char> buff(Uuid::size()); + Buffer wbuf(&buff[0], Uuid::size()); + Uuid uuid(sample.c_array()); + uuid.encode(wbuf); + + Buffer rbuf(&buff[0], Uuid::size()); + Uuid decoded; + decoded.decode(rbuf); + BOOST_CHECK_EQUAL(string(sample.begin(), sample.end()), + string(decoded.data(), decoded.data()+decoded.size())); +} + +QPID_AUTO_TEST_CASE(testTypesUuid) +{ + //tests for the Uuid class in the types namespace (introduced + //to avoid pulling in dependencies from framing) + types::Uuid a; + types::Uuid b(true); + types::Uuid c(true); + types::Uuid d(b); + types::Uuid e; + e = c; + + BOOST_CHECK(!a); + BOOST_CHECK(b); + + BOOST_CHECK(a != b); + BOOST_CHECK(b != c); + + BOOST_CHECK_EQUAL(b, d); + BOOST_CHECK_EQUAL(c, e); + + ostringstream out; + out << b; + istringstream in(out.str()); + in >> a; + BOOST_CHECK(!in.fail()); + BOOST_CHECK_EQUAL(a, b); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/Variant.cpp b/qpid/cpp/src/tests/Variant.cpp new file mode 100644 index 0000000000..5ae7fc89eb --- /dev/null +++ b/qpid/cpp/src/tests/Variant.cpp @@ -0,0 +1,834 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "unit_test.h" +#include "qpid/types/Variant.h" +#include "qpid/amqp_0_10/Codecs.h" +#include <boost/assign.hpp> +#include <iostream> + +using namespace qpid::types; +using namespace qpid::amqp_0_10; +using boost::assign::list_of; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(VariantSuite) + +QPID_AUTO_TEST_CASE(testConversions) +{ + Variant value; + + //string to float/double + value = "1.5"; + BOOST_CHECK_EQUAL((float) 1.5, value.asFloat()); + BOOST_CHECK_EQUAL((double) 1.5, value.asDouble()); + + //float to string or double + value = 1.5f; + BOOST_CHECK_EQUAL((float) 1.5, value.asFloat()); + BOOST_CHECK_EQUAL((double) 1.5, value.asDouble()); + BOOST_CHECK_EQUAL(std::string("1.5"), value.asString()); + + //double to string (conversion to float not valid) + value = 1.5; + BOOST_CHECK_THROW(value.asFloat(), InvalidConversion); + BOOST_CHECK_EQUAL((double) 1.5, value.asDouble()); + BOOST_CHECK_EQUAL(std::string("1.5"), value.asString()); + + //uint8 to larger unsigned ints and string + value = (uint8_t) 7; + BOOST_CHECK_EQUAL((uint8_t) 7, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 7, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 7, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 7, value.asUint64()); + BOOST_CHECK_EQUAL(std::string("7"), value.asString()); + + value = (uint16_t) 8; + BOOST_CHECK_EQUAL(std::string("8"), value.asString()); + value = (uint32_t) 9; + BOOST_CHECK_EQUAL(std::string("9"), value.asString()); + + //uint32 to larger unsigned ints and string + value = (uint32_t) 9999999; + BOOST_CHECK_EQUAL((uint32_t) 9999999, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 9999999, value.asUint64()); + BOOST_CHECK_EQUAL(std::string("9999999"), value.asString()); + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + + value = "true"; + BOOST_CHECK(value.asBool()); + value = "false"; + BOOST_CHECK(!value.asBool()); + value = "1"; + BOOST_CHECK(value.asBool()); + value = "0"; + BOOST_CHECK(!value.asBool()); + value = "other"; + BOOST_CHECK_THROW(value.asBool(), InvalidConversion); +} + +QPID_AUTO_TEST_CASE(testConversionsFromString) +{ + Variant value; + value = "5"; + BOOST_CHECK_EQUAL(5, value.asInt16()); + BOOST_CHECK_EQUAL(5u, value.asUint16()); + + value = "-5"; + BOOST_CHECK_EQUAL(-5, value.asInt16()); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + + value = "18446744073709551615"; + BOOST_CHECK_EQUAL(18446744073709551615ull, value.asUint64()); + BOOST_CHECK_THROW(value.asInt64(), InvalidConversion); + + value = "9223372036854775808"; + BOOST_CHECK_EQUAL(9223372036854775808ull, value.asUint64()); + BOOST_CHECK_THROW(value.asInt64(), InvalidConversion); + + value = "-9223372036854775809"; + BOOST_CHECK_THROW(value.asUint64(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt64(), InvalidConversion); + + value = "2147483648"; + BOOST_CHECK_EQUAL(2147483648ul, value.asUint32()); + BOOST_CHECK_THROW(value.asInt32(), InvalidConversion); + + value = "-2147483649"; + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt32(), InvalidConversion); + + value = "32768"; + BOOST_CHECK_EQUAL(32768u, value.asUint16()); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + + value = "-32769"; + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + + value = "-2.5"; + BOOST_CHECK_EQUAL(-2.5, value.asFloat()); + + value = "-0.875432e10"; + BOOST_CHECK_EQUAL(-0.875432e10, value.asDouble()); + + value = "-0"; + BOOST_CHECK_EQUAL(0, value.asInt16()); + BOOST_CHECK_EQUAL(0u, value.asUint16()); + + value = "-Blah"; + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt32(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint64(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt64(), InvalidConversion); + BOOST_CHECK_THROW(value.asFloat(), InvalidConversion); + BOOST_CHECK_THROW(value.asDouble(), InvalidConversion); + + value = "-000"; + BOOST_CHECK_EQUAL(0, value.asInt16()); + BOOST_CHECK_EQUAL(0u, value.asUint16()); + + value = "-0010"; + BOOST_CHECK_EQUAL(-10, value.asInt16()); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); +} + +QPID_AUTO_TEST_CASE(testSizeConversionsUint) +{ + Variant value; + + //uint8 (in 7 bits) to other uints, ints + value = (uint8_t) 7; + BOOST_CHECK_EQUAL((uint8_t) 7, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 7, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 7, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 7, value.asUint64()); + BOOST_CHECK_EQUAL((int8_t) 7, value.asInt8()); + BOOST_CHECK_EQUAL((int16_t) 7, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 7, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 7, value.asInt64()); + + //uint8 (in 8 bits) to other uints, ints + value = (uint8_t) 200; + BOOST_CHECK_EQUAL((uint8_t) 200, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 200, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 200, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 200, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) 200, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 200, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 200, value.asInt64()); + + + + //uint16 (in 7 bits) to other uints, ints + value = (uint16_t) 120; + BOOST_CHECK_EQUAL((uint8_t) 120, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 120, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 120, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 120, value.asUint64()); + BOOST_CHECK_EQUAL((int8_t) 120, value.asInt8()); + BOOST_CHECK_EQUAL((int16_t) 120, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 120, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 120, value.asInt64()); + + //uint16 (more than 7 bits) to other uints, ints + value = (uint16_t) 240; + BOOST_CHECK_EQUAL((uint8_t) 240, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 240, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 240, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 240, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) 240, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 240, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 240, value.asInt64()); + + //uint16 (more than 8 bits) to other uints, ints + value = (uint16_t) 1000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_EQUAL((uint16_t) 1000, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 1000, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 1000, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) 1000, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 1000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 1000, value.asInt64()); + + //uint16 (more than 15 bits) to other uints, ints + value = (uint16_t) 32770; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_EQUAL((uint16_t) 32770, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 32770, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 32770, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_EQUAL((int32_t) 32770, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 32770, value.asInt64()); + + + + //uint32 (in 7 bits) to other uints, ints + value = (uint32_t) 120; + BOOST_CHECK_EQUAL((uint8_t) 120, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 120, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 120, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 120, value.asUint64()); + BOOST_CHECK_EQUAL((int8_t) 120, value.asInt8()); + BOOST_CHECK_EQUAL((int16_t) 120, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 120, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 120, value.asInt64()); + + //uint32 (more than 7 bits) to other uints, ints + value = (uint32_t) 240; + BOOST_CHECK_EQUAL((uint8_t) 240, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 240, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 240, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 240, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) 240, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 240, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 240, value.asInt64()); + + //uint32 (more than 8 bits) to other uints, ints + value = (uint32_t) 1000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_EQUAL((uint16_t) 1000, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 1000, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 1000, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) 1000, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 1000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 1000, value.asInt64()); + + //uint32 (more than 15 bits) to other uints, ints + value = (uint32_t) 32770; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_EQUAL((uint16_t) 32770, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 32770, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 32770, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_EQUAL((int32_t) 32770, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 32770, value.asInt64()); + + //uint32 (more than 16 bits) to other uints, ints + value = (uint32_t) 66000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_EQUAL((uint32_t) 66000, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 66000, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_EQUAL((int32_t) 66000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 66000, value.asInt64()); + + + + //uint64 (in 7 bits) to other uints, ints + value = (uint64_t) 120; + BOOST_CHECK_EQUAL((uint8_t) 120, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 120, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 120, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 120, value.asUint64()); + BOOST_CHECK_EQUAL((int8_t) 120, value.asInt8()); + BOOST_CHECK_EQUAL((int16_t) 120, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 120, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 120, value.asInt64()); + + //uint64 (more than 7 bits) to other uints, ints + value = (uint64_t) 240; + BOOST_CHECK_EQUAL((uint8_t) 240, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 240, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 240, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 240, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) 240, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 240, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 240, value.asInt64()); + + //uint64 (more than 8 bits) to other uints, ints + value = (uint64_t) 1000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_EQUAL((uint16_t) 1000, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 1000, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 1000, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) 1000, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 1000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 1000, value.asInt64()); + + //uint64 (more than 15 bits) to other uints, ints + value = (uint64_t) 32770; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_EQUAL((uint16_t) 32770, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 32770, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 32770, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_EQUAL((int32_t) 32770, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 32770, value.asInt64()); + + //uint64 (more than 16 bits) to other uints, ints + value = (uint64_t) 66000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_EQUAL((uint32_t) 66000, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 66000, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_EQUAL((int32_t) 66000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 66000, value.asInt64()); + + //uint64 (more than 31 bits) to other uints, ints + value = (uint64_t) 3000000000ul; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_EQUAL((uint32_t) 3000000000ul, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 3000000000ul, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt32(), InvalidConversion); + BOOST_CHECK_EQUAL((int64_t) 3000000000ul, value.asInt64()); + + //uint64 (more than 32 bits) to other uints, ints + value = (uint64_t) 7000000000ull; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_EQUAL((uint64_t) 7000000000ull, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt32(), InvalidConversion); + BOOST_CHECK_EQUAL((int64_t) 7000000000ull, value.asInt64()); + + //uint64 (more than 63 bits) to other uints, ints + value = (uint64_t) 0x8000000000000000ull; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_EQUAL((uint64_t) 0x8000000000000000ull, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt32(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt64(), InvalidConversion); +} + +QPID_AUTO_TEST_CASE(testSizeConversionsInt) +{ + Variant value; + + //int8 (positive in 7 bits) + value = (int8_t) 100; + BOOST_CHECK_EQUAL((uint8_t) 100, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 100, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 100, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 100, value.asUint64()); + BOOST_CHECK_EQUAL((int8_t) 100, value.asInt8()); + BOOST_CHECK_EQUAL((int16_t) 100, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 100, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 100, value.asInt64()); + + //int8 (negative) + value = (int8_t) -100; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint64(), InvalidConversion); + BOOST_CHECK_EQUAL((int8_t) -100, value.asInt8()); + BOOST_CHECK_EQUAL((int16_t) -100, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) -100, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) -100, value.asInt64()); + + + + //int16 (positive in 7 bits) + value = (int16_t) 100; + BOOST_CHECK_EQUAL((uint8_t) 100, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 100, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 100, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 100, value.asUint64()); + BOOST_CHECK_EQUAL((int8_t) 100, value.asInt8()); + BOOST_CHECK_EQUAL((int16_t) 100, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 100, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 100, value.asInt64()); + + //int16 (positive in 8 bits) + value = (int16_t) 200; + BOOST_CHECK_EQUAL((uint8_t) 200, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 200, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 200, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 200, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) 200, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 200, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 200, value.asInt64()); + + //int16 (positive in more than 8 bits) + value = (int16_t) 1000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_EQUAL((uint16_t) 1000, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 1000, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 1000, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) 1000, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 1000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 1000, value.asInt64()); + + //int16 (negative in 7 bits) + value = (int16_t) -100; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint64(), InvalidConversion); + BOOST_CHECK_EQUAL((int8_t) -100, value.asInt8()); + BOOST_CHECK_EQUAL((int16_t) -100, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) -100, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) -100, value.asInt64()); + + //int16 (negative in more than 7 bits) + value = (int16_t) -1000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint64(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) -1000, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) -1000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) -1000, value.asInt64()); + + + + //int32 (positive in 7 bits) + value = (int32_t) 100; + BOOST_CHECK_EQUAL((uint8_t) 100, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 100, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 100, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 100, value.asUint64()); + BOOST_CHECK_EQUAL((int8_t) 100, value.asInt8()); + BOOST_CHECK_EQUAL((int16_t) 100, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 100, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 100, value.asInt64()); + + //int32 (positive in 8 bits) + value = (int32_t) 200; + BOOST_CHECK_EQUAL((uint8_t) 200, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 200, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 200, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 200, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) 200, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 200, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 200, value.asInt64()); + + //int32 (positive in more than 8 bits) + value = (int32_t) 1000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_EQUAL((uint16_t) 1000, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 1000, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 1000, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) 1000, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 1000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 1000, value.asInt64()); + + //int32 (positive in more than 15 bits) + value = (int32_t) 40000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_EQUAL((uint16_t) 40000, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 40000, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 40000, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_EQUAL((int32_t) 40000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 40000, value.asInt64()); + + //int32 (negative in 7 bits) + value = (int32_t) -100; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint64(), InvalidConversion); + BOOST_CHECK_EQUAL((int8_t) -100, value.asInt8()); + BOOST_CHECK_EQUAL((int16_t) -100, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) -100, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) -100, value.asInt64()); + + //int32 (negative in more than 7 bits) + value = (int32_t) -1000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint64(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) -1000, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) -1000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) -1000, value.asInt64()); + + //int32 (negative in more than 15 bits) + value = (int32_t) -40000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint64(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_EQUAL((int32_t) -40000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) -40000, value.asInt64()); + + + + //int64 (positive in 7 bits) + value = (int64_t) 100; + BOOST_CHECK_EQUAL((uint8_t) 100, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 100, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 100, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 100, value.asUint64()); + BOOST_CHECK_EQUAL((int8_t) 100, value.asInt8()); + BOOST_CHECK_EQUAL((int16_t) 100, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 100, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 100, value.asInt64()); + + //int64 (positive in 8 bits) + value = (int64_t) 200; + BOOST_CHECK_EQUAL((uint8_t) 200, value.asUint8()); + BOOST_CHECK_EQUAL((uint16_t) 200, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 200, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 200, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) 200, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 200, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 200, value.asInt64()); + + //int64 (positive in more than 8 bits) + value = (int64_t) 1000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_EQUAL((uint16_t) 1000, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 1000, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 1000, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) 1000, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) 1000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 1000, value.asInt64()); + + //int64 (positive in more than 15 bits) + value = (int64_t) 40000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_EQUAL((uint16_t) 40000, value.asUint16()); + BOOST_CHECK_EQUAL((uint32_t) 40000, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 40000, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_EQUAL((int32_t) 40000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) 40000, value.asInt64()); + + //int64 (positive in more than 31 bits) + value = (int64_t) 3000000000ll; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_EQUAL((uint32_t) 3000000000ll, value.asUint32()); + BOOST_CHECK_EQUAL((uint64_t) 3000000000ll, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt32(), InvalidConversion); + BOOST_CHECK_EQUAL((int64_t) 3000000000ll, value.asInt64()); + + //int64 (positive in more than 32 bits) + value = (int64_t) 5000000000ll; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_EQUAL((uint64_t) 5000000000ll, value.asUint64()); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt32(), InvalidConversion); + BOOST_CHECK_EQUAL((int64_t) 5000000000ll, value.asInt64()); + + //int64 (negative in 7 bits) + value = (int64_t) -100; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint64(), InvalidConversion); + BOOST_CHECK_EQUAL((int8_t) -100, value.asInt8()); + BOOST_CHECK_EQUAL((int16_t) -100, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) -100, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) -100, value.asInt64()); + + //int64 (negative in more than 7 bits) + value = (int64_t) -1000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint64(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_EQUAL((int16_t) -1000, value.asInt16()); + BOOST_CHECK_EQUAL((int32_t) -1000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) -1000, value.asInt64()); + + //int64 (negative in more than 15 bits) + value = (int64_t) -40000; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint64(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_EQUAL((int32_t) -40000, value.asInt32()); + BOOST_CHECK_EQUAL((int64_t) -40000, value.asInt64()); + + //int64 (negative in more than 31 bits) + value = (int64_t) -3000000000ll; + BOOST_CHECK_THROW(value.asUint8(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint16(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint32(), InvalidConversion); + BOOST_CHECK_THROW(value.asUint64(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt8(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt16(), InvalidConversion); + BOOST_CHECK_THROW(value.asInt32(), InvalidConversion); + BOOST_CHECK_EQUAL((int64_t) -3000000000ll, value.asInt64()); +} + +QPID_AUTO_TEST_CASE(testAssignment) +{ + Variant value("abc"); + Variant other = value; + BOOST_CHECK_EQUAL(VAR_STRING, value.getType()); + BOOST_CHECK_EQUAL(other.getType(), value.getType()); + BOOST_CHECK_EQUAL(other.asString(), value.asString()); + + const uint32_t i(1000); + value = i; + BOOST_CHECK_EQUAL(VAR_UINT32, value.getType()); + BOOST_CHECK_EQUAL(VAR_STRING, other.getType()); +} + +QPID_AUTO_TEST_CASE(testList) +{ + const std::string s("abc"); + const float f(9.876f); + const int16_t x(1000); + + Variant value = Variant::List(); + value.asList().push_back(Variant(s)); + value.asList().push_back(Variant(f)); + value.asList().push_back(Variant(x)); + BOOST_CHECK_EQUAL(3u, value.asList().size()); + Variant::List::const_iterator i = value.asList().begin(); + + BOOST_CHECK(i != value.asList().end()); + BOOST_CHECK_EQUAL(VAR_STRING, i->getType()); + BOOST_CHECK_EQUAL(s, i->asString()); + i++; + + BOOST_CHECK(i != value.asList().end()); + BOOST_CHECK_EQUAL(VAR_FLOAT, i->getType()); + BOOST_CHECK_EQUAL(f, i->asFloat()); + i++; + + BOOST_CHECK(i != value.asList().end()); + BOOST_CHECK_EQUAL(VAR_INT16, i->getType()); + BOOST_CHECK_EQUAL(x, i->asInt16()); + i++; + + BOOST_CHECK(i == value.asList().end()); +} + +QPID_AUTO_TEST_CASE(testMap) +{ + const std::string red("red"); + const float pi(3.14f); + const int16_t x(1000); + const Uuid u(true); + + Variant value = Variant::Map(); + value.asMap()["colour"] = red; + value.asMap()["pi"] = pi; + value.asMap()["my-key"] = x; + value.asMap()["id"] = u; + BOOST_CHECK_EQUAL(4u, value.asMap().size()); + + BOOST_CHECK_EQUAL(VAR_STRING, value.asMap()["colour"].getType()); + BOOST_CHECK_EQUAL(red, value.asMap()["colour"].asString()); + + BOOST_CHECK_EQUAL(VAR_FLOAT, value.asMap()["pi"].getType()); + BOOST_CHECK_EQUAL(pi, value.asMap()["pi"].asFloat()); + + BOOST_CHECK_EQUAL(VAR_INT16, value.asMap()["my-key"].getType()); + BOOST_CHECK_EQUAL(x, value.asMap()["my-key"].asInt16()); + + BOOST_CHECK_EQUAL(VAR_UUID, value.asMap()["id"].getType()); + BOOST_CHECK_EQUAL(u, value.asMap()["id"].asUuid()); + + value.asMap()["my-key"] = "now it's a string"; + BOOST_CHECK_EQUAL(VAR_STRING, value.asMap()["my-key"].getType()); + BOOST_CHECK_EQUAL(std::string("now it's a string"), value.asMap()["my-key"].asString()); +} + +QPID_AUTO_TEST_CASE(testIsEqualTo) +{ + BOOST_CHECK_EQUAL(Variant("abc"), Variant("abc")); + BOOST_CHECK_EQUAL(Variant(1234), Variant(1234)); + + Variant a = Variant::Map(); + a.asMap()["colour"] = "red"; + a.asMap()["pi"] = 3.14f; + a.asMap()["my-key"] = 1234; + Variant b = Variant::Map(); + b.asMap()["colour"] = "red"; + b.asMap()["pi"] = 3.14f; + b.asMap()["my-key"] = 1234; + BOOST_CHECK_EQUAL(a, b); +} + +QPID_AUTO_TEST_CASE(testEncoding) +{ + Variant a("abc"); + a.setEncoding("utf8"); + Variant b = a; + Variant map = Variant::Map(); + map.asMap()["a"] = a; + map.asMap()["b"] = b; + BOOST_CHECK_EQUAL(a.getEncoding(), std::string("utf8")); + BOOST_CHECK_EQUAL(a.getEncoding(), b.getEncoding()); + BOOST_CHECK_EQUAL(a.getEncoding(), map.asMap()["a"].getEncoding()); + BOOST_CHECK_EQUAL(b.getEncoding(), map.asMap()["b"].getEncoding()); + BOOST_CHECK_EQUAL(map.asMap()["a"].getEncoding(), map.asMap()["b"].getEncoding()); +} + +QPID_AUTO_TEST_CASE(testBufferEncoding) +{ + Variant a("abc"); + a.setEncoding("utf8"); + std::string buffer; + + Variant::Map inMap, outMap; + inMap["a"] = a; + + MapCodec::encode(inMap, buffer); + MapCodec::decode(buffer, outMap); + BOOST_CHECK_EQUAL(inMap, outMap); + + inMap["b"] = Variant(std::string(65535, 'X')); + inMap["b"].setEncoding("utf16"); + MapCodec::encode(inMap, buffer); + MapCodec::decode(buffer, outMap); + BOOST_CHECK_EQUAL(inMap, outMap); + + inMap["fail"] = Variant(std::string(65536, 'X')); + inMap["fail"].setEncoding("utf16"); + BOOST_CHECK_THROW(MapCodec::encode(inMap, buffer), std::exception); +} + +QPID_AUTO_TEST_CASE(parse) +{ + Variant a; + a.parse("What a fine mess"); + BOOST_CHECK(a.getType()==types::VAR_STRING); + a.parse("true"); + BOOST_CHECK(a.getType()==types::VAR_BOOL); + a.parse("FalsE"); + BOOST_CHECK(a.getType()==types::VAR_BOOL); + a.parse("3.1415926"); + BOOST_CHECK(a.getType()==types::VAR_DOUBLE); + a.parse("-7.2e-15"); + BOOST_CHECK(a.getType()==types::VAR_DOUBLE); + a.parse("9223372036854775807"); + BOOST_CHECK(a.getType()==types::VAR_INT64); + a.parse("9223372036854775808"); + BOOST_CHECK(a.getType()==types::VAR_UINT64); + a.parse("-9223372036854775807"); + BOOST_CHECK(a.getType()==types::VAR_INT64); + a.parse("-9223372036854775808"); + BOOST_CHECK(a.getType()==types::VAR_DOUBLE); + a.parse("18446744073709551615"); + BOOST_CHECK(a.getType()==types::VAR_UINT64); + a.parse("18446744073709551616"); + BOOST_CHECK(a.getType()==types::VAR_DOUBLE); +} + +QPID_AUTO_TEST_CASE(described) +{ + Variant a; + BOOST_CHECK(!a.isDescribed()); + a.getDescriptors().push_back("foo"); + BOOST_CHECK(a.isDescribed()); + BOOST_CHECK_EQUAL(a.getDescriptors().size(), 1U); + BOOST_CHECK_EQUAL(a.getDescriptors().front(), Variant("foo")); + a = 42; + BOOST_CHECK(a.isDescribed()); + BOOST_CHECK_EQUAL(a.getDescriptors().size(), 1U); + BOOST_CHECK_EQUAL(a.getDescriptors().front(), Variant("foo")); + a.getDescriptors().push_back(33); + BOOST_CHECK_EQUAL(a.getDescriptors().size(), 2U); + BOOST_CHECK_EQUAL(a.getDescriptors().front(), Variant("foo")); + BOOST_CHECK_EQUAL(*(++a.getDescriptors().begin()), Variant(33)); + a.getDescriptors().clear(); + BOOST_CHECK(!a.isDescribed()); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/XmlClientSessionTest.cpp b/qpid/cpp/src/tests/XmlClientSessionTest.cpp new file mode 100644 index 0000000000..bfa6ed096b --- /dev/null +++ b/qpid/cpp/src/tests/XmlClientSessionTest.cpp @@ -0,0 +1,301 @@ +/* + * + * Licensed to the Apachef Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "unit_test.h" +#include "test_tools.h" +#include "BrokerFixture.h" +#include "qpid/sys/Shlib.h" +#include "qpid/sys/Monitor.h" +#include "qpid/sys/Thread.h" +#include "qpid/sys/Runnable.h" +#include "qpid/client/Message.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/client/Connection.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/client/LocalQueue.h" +#include "qpid/client/Session.h" +#include "qpid/client/SubscriptionManager.h" + +#include <boost/optional.hpp> +#include <boost/lexical_cast.hpp> + +#include <vector> + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(XmlClientSessionTest) + +struct XmlFixture { + XmlFixture() { + qpid::sys::Shlib shlib(getLibPath("XML_LIB")); + } + ~XmlFixture() {} +}; + +using namespace qpid::client; +using namespace qpid::client::arg; +using namespace qpid::framing; +using namespace qpid; + +using qpid::sys::Monitor; +using std::string; +using std::cout; +using std::endl; + + +class SubscribedLocalQueue : public LocalQueue { + private: + SubscriptionManager& subscriptions; + public: + SubscribedLocalQueue(SubscriptionManager& subs) : subscriptions(subs) {} + Message get () { return pop(); } + Message get (sys::Duration timeout) { return pop(timeout); } + virtual ~SubscribedLocalQueue() {} +}; + + +struct SimpleListener : public MessageListener +{ + Monitor lock; + std::vector<Message> messages; + + void received(Message& msg) + { + Monitor::ScopedLock l(lock); + messages.push_back(msg); + lock.notifyAll(); + } + + void waitFor(const uint n) + { + Monitor::ScopedLock l(lock); + while (messages.size() < n) { + lock.wait(); + } + } +}; + +struct ClientSessionFixture : public SessionFixture +{ + void declareSubscribe(const string& q="odd_blue", + const string& dest="xml") + { + session.queueDeclare(queue=q); + session.messageSubscribe(queue=q, destination=dest, acquireMode=1); + session.messageFlow(destination=dest, unit=0, value=0xFFFFFFFF);//messages + session.messageFlow(destination=dest, unit=1, value=0xFFFFFFFF);//bytes + } +}; + +// ########### START HERE #################################### + +QPID_FIXTURE_TEST_CASE(testXmlBinding, XmlFixture) { + ClientSessionFixture f; + + SubscriptionManager subscriptions(f.session); + SubscribedLocalQueue localQueue(subscriptions); + + f.session.exchangeDeclare(qpid::client::arg::exchange="xml", qpid::client::arg::type="xml"); + f.session.queueDeclare(qpid::client::arg::queue="odd_blue"); + subscriptions.subscribe(localQueue, "odd_blue"); + + FieldTable binding; + binding.setString("xquery", "declare variable $color external;" + "(./message/id mod 2 = 1) and ($color = 'blue')"); + f.session.exchangeBind(qpid::client::arg::exchange="xml", qpid::client::arg::queue="odd_blue", qpid::client::arg::bindingKey="query_name", qpid::client::arg::arguments=binding); + + Message message; + message.getDeliveryProperties().setRoutingKey("query_name"); + + message.getHeaders().setString("color", "blue"); + string m = "<message><id>1</id></message>"; + message.setData(m); + + f.session.messageTransfer(qpid::client::arg::content=message, qpid::client::arg::destination="xml"); + + Message m2 = localQueue.get(1*qpid::sys::TIME_SEC); + BOOST_CHECK_EQUAL(m, m2.getData()); +} + +/** + * Ensure that multiple queues can be bound using the same routing key + */ +QPID_FIXTURE_TEST_CASE(testXMLBindMultipleQueues, XmlFixture) { + ClientSessionFixture f; + + + f.session.exchangeDeclare(arg::exchange="xml", arg::type="xml"); + f.session.queueDeclare(arg::queue="blue", arg::exclusive=true, arg::autoDelete=true); + f.session.queueDeclare(arg::queue="red", arg::exclusive=true, arg::autoDelete=true); + + FieldTable blue; + blue.setString("xquery", "./colour = 'blue'"); + f.session.exchangeBind(arg::exchange="xml", arg::queue="blue", arg::bindingKey="by-colour", arg::arguments=blue); + FieldTable red; + red.setString("xquery", "./colour = 'red'"); + f.session.exchangeBind(arg::exchange="xml", arg::queue="red", arg::bindingKey="by-colour", arg::arguments=red); + + Message sent1("<colour>blue</colour>", "by-colour"); + f.session.messageTransfer(arg::content=sent1, arg::destination="xml"); + + Message sent2("<colour>red</colour>", "by-colour"); + f.session.messageTransfer(arg::content=sent2, arg::destination="xml"); + + Message received; + BOOST_CHECK(f.subs.get(received, "blue")); + BOOST_CHECK_EQUAL(sent1.getData(), received.getData()); + BOOST_CHECK(f.subs.get(received, "red")); + BOOST_CHECK_EQUAL(sent2.getData(), received.getData()); +} + +//### Test: Bad XML does not kill the server - and does not even +// raise an exception, the content is not required to be XML. + +QPID_FIXTURE_TEST_CASE(testXMLSendBadXML, XmlFixture) { + ClientSessionFixture f; + + f.session.exchangeDeclare(arg::exchange="xml", arg::type="xml"); + f.session.queueDeclare(arg::queue="blue", arg::exclusive=true, arg::autoDelete=true)\ + ; + f.session.queueDeclare(arg::queue="red", arg::exclusive=true, arg::autoDelete=true); + + FieldTable blue; + blue.setString("xquery", "./colour = 'blue'"); + f.session.exchangeBind(arg::exchange="xml", arg::queue="blue", arg::bindingKey="by-c\ +olour", arg::arguments=blue); + FieldTable red; + red.setString("xquery", "./colour = 'red'"); + f.session.exchangeBind(arg::exchange="xml", arg::queue="red", arg::bindingKey="by-co\ +lour", arg::arguments=red); + + Message sent1("<>colour>blue</colour>", "by-colour"); + f.session.messageTransfer(arg::content=sent1, arg::destination="xml"); + + BOOST_CHECK_EQUAL(1, 1); +} + + +//### Test: Bad XQuery does not kill the server, but does raise an exception + +QPID_FIXTURE_TEST_CASE(testXMLBadXQuery, XmlFixture) { + ClientSessionFixture f; + + f.session.exchangeDeclare(arg::exchange="xml", arg::type="xml"); + f.session.queueDeclare(arg::queue="blue", arg::exclusive=true, arg::autoDelete=true)\ + ; + + try { + ScopedSuppressLogging sl; // Supress logging of error messages for expected error. + FieldTable blue; + blue.setString("xquery", "./colour $=! 'blue'"); + f.session.exchangeBind(arg::exchange="xml", arg::queue="blue", arg::bindingKey="by-c\ +olour", arg::arguments=blue); + } + catch (const InternalErrorException& e) { + return; + } + BOOST_ERROR("A bad XQuery must raise an exception when used in an XML Binding."); + +} + + +//### Test: double, string, and integer field values can all be bound to queries + +QPID_FIXTURE_TEST_CASE(testXmlBindingUntyped, XmlFixture) { + ClientSessionFixture f; + + SubscriptionManager subscriptions(f.session); + SubscribedLocalQueue localQueue(subscriptions); + + f.session.exchangeDeclare(qpid::client::arg::exchange="xml", qpid::client::arg::type="xml"); + f.session.queueDeclare(qpid::client::arg::queue="odd_blue"); + subscriptions.subscribe(localQueue, "odd_blue"); + + FieldTable binding; + binding.setString("xquery", + "declare variable $s external;" + "declare variable $i external;" + "declare variable $d external;" + "$s = 'string' and $i = 1 and $d < 1"); + f.session.exchangeBind(qpid::client::arg::exchange="xml", qpid::client::arg::queue="odd_blue", qpid::client::arg::bindingKey="query_name", qpid::client::arg::arguments=binding); + + Message message; + message.getDeliveryProperties().setRoutingKey("query_name"); + + message.getHeaders().setString("s", "string"); + message.getHeaders().setInt("i", 1); + message.getHeaders().setDouble("d", 0.5); + string m = "<message>Hi, Mom!</message>"; + message.setData(m); + + f.session.messageTransfer(qpid::client::arg::content=message, qpid::client::arg::destination="xml"); + + Message m2 = localQueue.get(1*qpid::sys::TIME_SEC); + BOOST_CHECK_EQUAL(m, m2.getData()); +} + + +//### Test: double, string, and integer field values can all be bound to queries + +QPID_FIXTURE_TEST_CASE(testXmlBindingTyped, XmlFixture) { + ClientSessionFixture f; + + SubscriptionManager subscriptions(f.session); + SubscribedLocalQueue localQueue(subscriptions); + + f.session.exchangeDeclare(qpid::client::arg::exchange="xml", qpid::client::arg::type="xml"); + f.session.queueDeclare(qpid::client::arg::queue="odd_blue"); + subscriptions.subscribe(localQueue, "odd_blue"); + + FieldTable binding; + binding.setString("xquery", + "declare variable $s as xs:string external;" + "declare variable $i as xs:integer external;" + "declare variable $d external;" // XQilla bug when declaring xs:float, xs:double types? Fine if untyped, acts as float. + "$s = 'string' and $i = 1 and $d < 1"); + f.session.exchangeBind(qpid::client::arg::exchange="xml", qpid::client::arg::queue="odd_blue", qpid::client::arg::bindingKey="query_name", qpid::client::arg::arguments=binding); + + Message message; + message.getDeliveryProperties().setRoutingKey("query_name"); + + message.getHeaders().setString("s", "string"); + message.getHeaders().setInt("i", 1); + message.getHeaders().setDouble("d", 0.5); + string m = "<message>Hi, Mom!</message>"; + message.setData(m); + + f.session.messageTransfer(qpid::client::arg::content=message, qpid::client::arg::destination="xml"); + + Message m2 = localQueue.get(1*qpid::sys::TIME_SEC); + BOOST_CHECK_EQUAL(m, m2.getData()); +} + + +//### Test: Each session can provide its own definition for a query name + + + +//### Test: Bindings persist, surviving broker restart + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/acl.py b/qpid/cpp/src/tests/acl.py new file mode 100755 index 0000000000..2838bd3f83 --- /dev/null +++ b/qpid/cpp/src/tests/acl.py @@ -0,0 +1,3959 @@ +#!/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. +# + +import sys +import qpid +from qpid.util import connect +from qpid.connection import Connection +from qpid.datatypes import uuid4 +from qpid.testlib import TestBase010 +from qmf.console import Session +from qpid.datatypes import Message +import qpid.messaging +from qpidtoollibs import BrokerAgent + +class ACLFile: + def __init__(self, policy='data_dir/policy.acl'): + self.f = open(policy,'w') + + def write(self,line): + self.f.write(line) + + def close(self): + self.f.close() + +class ACLTests(TestBase010): + + # required for testing QMF methods + def get_messaging_connection(self, user, passwd): + parms = {'username':user, 'password':passwd, 'sasl_mechanisms':'PLAIN'} + brokerurl="%s:%s" %(self.broker.host, self.broker.port) + connection = qpid.messaging.Connection(brokerurl, **parms) + connection.open() + return connection + + # For connection limit tests this function + # throws if the connection won't start + # returns a connection that the caller can close if he likes. + def get_connection(self, user, passwd): + socket = connect(self.broker.host, self.broker.port) + connection = Connection (sock=socket, username=user, password=passwd, + mechanism="PLAIN") + connection.start() + return connection + + def get_session(self, user, passwd): + socket = connect(self.broker.host, self.broker.port) + connection = Connection (sock=socket, username=user, password=passwd, + mechanism="PLAIN") + connection.start() + return connection.session(str(uuid4())) + + def port_i(self): + return int(self.defines["port-i"]) + + def port_u(self): + return int(self.defines["port-u"]) + + def port_q(self): + return int(self.defines["port-q"]) + + def get_session_by_port(self, user, passwd, byPort): + socket = connect(self.broker.host, byPort) + connection = Connection (sock=socket, username=user, password=passwd, + mechanism="PLAIN") + connection.start() + return connection.session(str(uuid4())) + + def reload_acl(self): + result = None + try: + self.broker_access.reloadAclFile() + except Exception, e: + result = str(e) + return result + + def acl_lookup(self, userName, action, aclObj, aclObjName, propMap): + result = {} + try: + result = self.broker_access.acl_lookup(userName, action, aclObj, aclObjName, propMap) + except Exception, e: + result['text'] = str(e) + result['result'] = str(e) + return result + + def acl_lookupPublish(self, userName, exchange, key): + result = {} + try: + result = self.broker_access.acl_lookupPublish(userName, exchange, key) + except Exception, e: + result['text'] = str(e) + result['result'] = str(e) + return result + + def get_acl_file(self): + return ACLFile(self.config.defines.get("policy-file", "data_dir/policy.acl")) + + def setUp(self): + aclf = self.get_acl_file() + aclf.write('acl allow all all\n') + aclf.close() + TestBase010.setUp(self) + self.startBrokerAccess() + self.reload_acl() + + def tearDown(self): + aclf = self.get_acl_file() + aclf.write('acl allow all all\n') + aclf.close() + self.reload_acl() + TestBase010.tearDown(self) + + + def Lookup(self, userName, action, aclObj, aclObjName, propMap, expectedResult): + result = self.acl_lookup(userName, action, aclObj, aclObjName, propMap) + if (result['result'] != expectedResult): + suffix = ', [ERROR: Expected= ' + expectedResult + if (result['result'] is None): + suffix = suffix + ', Exception= ' + result['text'] + ']' + else: + suffix = suffix + ', Actual= ' + result['result'] + ']' + self.fail('Lookup: name=' + userName + ', action=' + action + ', aclObj=' + aclObj + ', aclObjName=' + aclObjName + ', propertyMap=' + str(propMap) + suffix) + + + def LookupPublish(self, userName, exchName, keyName, expectedResult): + result = self.acl_lookupPublish(userName, exchName, keyName) + if (result['result'] != expectedResult): + suffix = ', [ERROR: Expected= ' + expectedResult + if (result['result'] is None): + suffix = suffix + ', Exception= ' + result['text'] + ']' + else: + suffix = suffix + ', Actual= ' + result['result'] + ']' + self.fail('LookupPublish: name=' + userName + ', exchange=' + exchName + ', key=' + keyName + suffix) + + def AllBut(self, allList, removeList): + tmpList = allList[:] + for item in removeList: + try: + tmpList.remove(item) + except Exception, e: + self.fail("ERROR in AllBut() \nallList = %s \nremoveList = %s \nerror = %s " \ + % (allList, removeList, e)) + return tmpList + + #===================================== + # ACL general tests + #===================================== + + def test_deny_mode(self): + """ + Test the deny all mode + """ + aclf = self.get_acl_file() + aclf.write('acl allow anonymous all all\n') + aclf.write('acl allow bob@QPID create queue\n') + aclf.write('acl deny all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + session = self.get_session('bob','bob') + try: + session.queue_declare(queue="deny_queue") + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request"); + self.fail("Error during queue create request"); + + try: + session.exchange_bind(exchange="amq.direct", queue="deny_queue", binding_key="routing_key") + self.fail("ACL should deny queue bind request"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + + def test_allow_mode(self): + """ + Test the allow all mode + """ + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID bind exchange\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + session = self.get_session('bob','bob') + try: + session.queue_declare(queue="allow_queue") + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request"); + self.fail("Error during queue create request"); + + try: + session.exchange_bind(exchange="amq.direct", queue="allow_queue", binding_key="routing_key") + self.fail("ACL should deny queue bind request"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + + + def test_allow_mode_with_specfic_allow_override(self): + """ + Specific allow overrides a general deny + """ + aclf = self.get_acl_file() + aclf.write('group admins bob@QPID joe@QPID \n') + aclf.write('acl allow bob@QPID create queue \n') + aclf.write('acl deny admins create queue \n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + session = self.get_session('bob','bob') + + try: + session.queue_declare(queue='zed') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow create queue request"); + + + #===================================== + # ACL file format tests + #===================================== + + def test_empty_groups(self): + """ + Test empty groups + """ + aclf = self.get_acl_file() + aclf.write('acl group\n') + aclf.write('acl group admins bob@QPID joe@QPID\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result.find("Insufficient tokens for acl definition",0,len(result)) == -1): + self.fail("ACL Reader should reject the acl file due to empty group name") + + def test_illegal_acl_formats(self): + """ + Test illegal acl formats + """ + aclf = self.get_acl_file() + aclf.write('acl group admins bob@QPID joe@QPID\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result.find("Unknown ACL permission",0,len(result)) == -1): + self.fail(result) + + def test_illegal_extension_lines(self): + """ + Test illegal extension lines + """ + + aclf = self.get_acl_file() + aclf.write('group admins bob@QPID \n') + aclf.write(' \ \n') + aclf.write('joe@QPID \n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result.find("contains an illegal extension",0,len(result)) == -1): + self.fail(result) + + if (result.find("Non-continuation line must start with \"group\" or \"acl\"",0,len(result)) == -1): + self.fail(result) + + def test_illegal_extension_lines(self): + """ + Test proper extention lines + """ + aclf = self.get_acl_file() + aclf.write('group test1 joe@EXAMPLE.com \\ \n') # should be allowed + aclf.write(' jack@EXAMPLE.com \\ \n') # should be allowed + aclf.write('jill@TEST.COM \\ \n') # should be allowed + aclf.write('host/123.example.com@TEST.COM\n') # should be allowed + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + def test_nested_groups(self): + """ + Test nested groups + """ + + aclf = self.get_acl_file() + aclf.write('group user-consume martin@QPID ted@QPID\n') + aclf.write('group group2 kim@QPID user-consume rob@QPID \n') + aclf.write('acl allow anonymous all all \n') + aclf.write('acl allow group2 create queue \n') + aclf.write('acl deny all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + session = self.get_session('rob','rob') + try: + session.queue_declare(queue="rob_queue") + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request"); + self.fail("Error during queue create request"); + + + + def test_user_realm(self): + """ + Test a user defined without a realm + Ex. group admin rajith + Note: a user name without a realm is interpreted as a group name + """ + aclf = self.get_acl_file() + aclf.write('group admin bob\n') # shouldn't be allowed + aclf.write('acl deny admin bind exchange\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result.find("not defined yet.",0,len(result)) == -1): + self.fail(result) + + def test_allowed_chars_for_username(self): + """ + Test a user defined without a realm + Ex. group admin rajith + """ + aclf = self.get_acl_file() + aclf.write('group test1 joe@EXAMPLE.com\n') # should be allowed + aclf.write('group test2 jack_123-jill@EXAMPLE.com\n') # should be allowed + aclf.write('group test4 host/somemachine.example.com@EXAMPLE.COM\n') # should be allowed + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + aclf = self.get_acl_file() + aclf.write('group test1 joe$H@EXAMPLE.com\n') # shouldn't be allowed + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result.find("Username \"joe$H@EXAMPLE.com\" contains illegal characters",0,len(result)) == -1): + self.fail(result) + + #===================================== + # ACL validation tests + #===================================== + + def test_illegal_queue_policy(self): + """ + Test illegal queue policy + """ + + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 exclusive=true policytype=ding\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "ding is not a valid value for 'policytype', possible values are one of" + if (result.find(expected) == -1): + self.fail(result) + + def test_illegal_queuemaxsize_upper_limit_spec(self): + """ + Test illegal queue policy + """ + # + # Use maxqueuesize + # + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 maxqueuesize=-1\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "-1 is not a valid value for 'queuemaxsizeupperlimit', " \ + "values should be between 0 and 9223372036854775807"; + if (result.find(expected) == -1): + self.fail(result) + + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 maxqueuesize=9223372036854775808\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "9223372036854775808 is not a valid value for 'queuemaxsizeupperlimit', " \ + "values should be between 0 and 9223372036854775807"; + if (result.find(expected) == -1): + self.fail(result) + + # + # Use queuemaxsizeupperlimit + # + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 queuemaxsizeupperlimit=-1\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "-1 is not a valid value for 'queuemaxsizeupperlimit', " \ + "values should be between 0 and 9223372036854775807"; + if (result.find(expected) == -1): + self.fail(result) + + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 queuemaxsizeupperlimit=9223372036854775808\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "9223372036854775808 is not a valid value for 'queuemaxsizeupperlimit', " \ + "values should be between 0 and 9223372036854775807"; + if (result.find(expected) == -1): + self.fail(result) + + + + def test_illegal_queuemaxcount_upper_limit_spec(self): + """ + Test illegal queue policy + """ + # + # Use maxqueuecount + # + + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 maxqueuecount=-1\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "-1 is not a valid value for 'queuemaxcountupperlimit', " \ + "values should be between 0 and 9223372036854775807"; + if (result.find(expected) == -1): + self.fail(result) + + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 maxqueuecount=9223372036854775808\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "9223372036854775808 is not a valid value for 'queuemaxcountupperlimit', " \ + "values should be between 0 and 9223372036854775807"; + if (result.find(expected) == -1): + self.fail(result) + + # + # use maxqueuecountupperlimit + # + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 queuemaxcountupperlimit=-1\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "-1 is not a valid value for 'queuemaxcountupperlimit', " \ + "values should be between 0 and 9223372036854775807"; + if (result.find(expected) == -1): + self.fail(result) + + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 queuemaxcountupperlimit=9223372036854775808\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "9223372036854775808 is not a valid value for 'queuemaxcountupperlimit', " \ + "values should be between 0 and 9223372036854775807"; + if (result.find(expected) == -1): + self.fail(result) + + + def test_illegal_queuemaxsize_lower_limit_spec(self): + """ + Test illegal queue policy + """ + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 queuemaxsizelowerlimit=-1\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "-1 is not a valid value for 'queuemaxsizelowerlimit', " \ + "values should be between 0 and 9223372036854775807"; + if (result.find(expected) == -1): + self.fail(result) + + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 queuemaxsizelowerlimit=9223372036854775808\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "9223372036854775808 is not a valid value for 'queuemaxsizelowerlimit', " \ + "values should be between 0 and 9223372036854775807"; + if (result.find(expected) == -1): + self.fail(result) + + + + def test_illegal_queuemaxcount_lower_limit_spec(self): + """ + Test illegal queue policy + """ + + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 queuemaxcountlowerlimit=-1\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "-1 is not a valid value for 'queuemaxcountlowerlimit', " \ + "values should be between 0 and 9223372036854775807"; + if (result.find(expected) == -1): + self.fail(result) + + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 queuemaxcountlowerlimit=9223372036854775808\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "9223372036854775808 is not a valid value for 'queuemaxcountlowerlimit', " \ + "values should be between 0 and 9223372036854775807"; + if (result.find(expected) == -1): + self.fail(result) + + + def test_illegal_filemaxsize_upper_limit_spec(self): + """ + Test illegal file policy + """ + # + # Use filemaxsizeupperlimit + # + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 filemaxsizeupperlimit=-1\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "-1 is not a valid value for 'filemaxsizeupperlimit', " \ + "values should be between 0 and 9223372036854775807"; + if (result.find(expected) == -1): + self.fail(result) + + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 filemaxsizeupperlimit=9223372036854775808\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "9223372036854775808 is not a valid value for 'filemaxsizeupperlimit', " \ + "values should be between 0 and 9223372036854775807"; + if (result.find(expected) == -1): + self.fail(result) + + + + def test_illegal_filemaxcount_upper_limit_spec(self): + """ + Test illegal file policy + """ + # + # use maxfilecountupperlimit + # + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 filemaxcountupperlimit=-1\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "-1 is not a valid value for 'filemaxcountupperlimit', " \ + "values should be between 0 and 9223372036854775807"; + if (result.find(expected) == -1): + self.fail(result) + + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 filemaxcountupperlimit=9223372036854775808\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "9223372036854775808 is not a valid value for 'filemaxcountupperlimit', " \ + "values should be between 0 and 9223372036854775807"; + if (result.find(expected) == -1): + self.fail(result) + + + def test_illegal_filemaxsize_lower_limit_spec(self): + """ + Test illegal file policy + """ + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 filemaxsizelowerlimit=-1\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "-1 is not a valid value for 'filemaxsizelowerlimit', " \ + "values should be between 0 and 9223372036854775807"; + if (result.find(expected) == -1): + self.fail(result) + + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 filemaxsizelowerlimit=9223372036854775808\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "9223372036854775808 is not a valid value for 'filemaxsizelowerlimit', " \ + "values should be between 0 and 9223372036854775807"; + if (result.find(expected) == -1): + self.fail(result) + + + + def test_illegal_filemaxcount_lower_limit_spec(self): + """ + Test illegal file policy + """ + + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 filemaxcountlowerlimit=-1\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "-1 is not a valid value for 'filemaxcountlowerlimit', " \ + "values should be between 0 and 9223372036854775807"; + if (result.find(expected) == -1): + self.fail(result) + + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 filemaxcountlowerlimit=9223372036854775808\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "9223372036854775808 is not a valid value for 'filemaxcountlowerlimit', " \ + "values should be between 0 and 9223372036854775807"; + if (result.find(expected) == -1): + self.fail(result) + + + def test_illegal_pages_lower_limit_spec(self): + """ + Test illegal paged queue policy + """ + + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 pageslowerlimit=-1\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "-1 is not a valid value for 'pageslowerlimit', " \ + "values should be between 0 and 9223372036854775807"; + if (result.find(expected) == -1): + self.fail(result) + + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 pageslowerlimit=9223372036854775808\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "9223372036854775808 is not a valid value for 'pageslowerlimit', " \ + "values should be between 0 and 9223372036854775807"; + if (result.find(expected) == -1): + self.fail(result) + + + def test_illegal_pages_upper_limit_spec(self): + """ + Test illegal paged queue policy + """ + + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 pagesupperlimit=-1\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "-1 is not a valid value for 'pagesupperlimit', " \ + "values should be between 0 and 9223372036854775807"; + if (result.find(expected) == -1): + self.fail(result) + + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 pagesupperlimit=9223372036854775808\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "9223372036854775808 is not a valid value for 'pagesupperlimit', " \ + "values should be between 0 and 9223372036854775807"; + if (result.find(expected) == -1): + self.fail(result) + + + def test_illegal_pagefactor_lower_limit_spec(self): + """ + Test illegal paged queue policy + """ + + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 pagefactorlowerlimit=-1\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "-1 is not a valid value for 'pagefactorlowerlimit', " \ + "values should be between 0 and 9223372036854775807"; + if (result.find(expected) == -1): + self.fail(result) + + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 pagefactorlowerlimit=9223372036854775808\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "9223372036854775808 is not a valid value for 'pagefactorlowerlimit', " \ + "values should be between 0 and 9223372036854775807"; + if (result.find(expected) == -1): + self.fail(result) + + + def test_illegal_pagefactor_upper_limit_spec(self): + """ + Test illegal paged queue policy + """ + + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 pagefactorupperlimit=-1\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "-1 is not a valid value for 'pagefactorupperlimit', " \ + "values should be between 0 and 9223372036854775807"; + if (result.find(expected) == -1): + self.fail(result) + + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=q2 pagefactorupperlimit=9223372036854775808\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + expected = "9223372036854775808 is not a valid value for 'pagefactorupperlimit', " \ + "values should be between 0 and 9223372036854775807"; + if (result.find(expected) == -1): + self.fail(result) + + + #===================================== + # ACL queue tests + #===================================== + + def test_queue_allow_mode(self): + """ + Test cases for queue acl in allow mode + """ + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID access queue name=q1\n') + aclf.write('acl deny bob@QPID create queue name=q1 durable=true\n') + aclf.write('acl deny bob@QPID create queue name=q2 exclusive=true policytype=ring\n') + aclf.write('acl deny bob@QPID access queue name=q3\n') + aclf.write('acl deny bob@QPID delete queue name=q4\n') + aclf.write('acl deny bob@QPID create queue name=q5 maxqueuesize=1000 maxqueuecount=100\n') + aclf.write('acl deny bob@QPID create queue name=q6 paging=true\n') + aclf.write('acl deny bob@QPID purge queue name=q7\n') + aclf.write('acl deny bob@QPID move queue name=q7\n') + aclf.write('acl deny bob@QPID move queue name=q8 queuename=q7\n') + aclf.write('acl deny bob@QPID redirect queue name=q7\n') + aclf.write('acl deny bob@QPID redirect queue name=q8 queuename=q7\n') + aclf.write('acl deny bob@QPID reroute queue name=q7\n') + aclf.write('acl deny bob@QPID reroute queue name=q8 exchangename=amq.fanout\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + session = self.get_session('bob','bob') + + try: + session.queue_declare(queue="q1", durable=True) + self.fail("ACL should deny queue create request with name=q1 durable=true"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.queue_declare(queue="q1", durable=True, passive=True) + self.fail("ACL should deny queue passive declare request with name=q1 durable=true"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.policy_type"] = "ring" + session.queue_declare(queue="q2", exclusive=True, arguments=queue_options) + self.fail("ACL should deny queue create request with name=q2 exclusive=true qpid.policy_type=ring"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.policy_type"] = "ring_strict" + session.queue_declare(queue="q2", exclusive=True, arguments=queue_options) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request with name=q2 exclusive=true qpid.policy_type=ring_strict"); + + try: + queue_options = {} + queue_options["qpid.max_count"] = 200 + queue_options["qpid.max_size"] = 500 + session.queue_declare(queue="q5", exclusive=True, arguments=queue_options) + self.fail("ACL should deny queue create request with name=q2, qpid.max_size=500 and qpid.max_count=200"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.paging"] = True + session.queue_declare(queue="q6", arguments=queue_options) + self.fail("ACL should deny queue create request with name=q6, qpid.paging=True"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.max_count"] = 200 + queue_options["qpid.max_size"] = 100 + session.queue_declare(queue="q2", exclusive=True, arguments=queue_options) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request with name=q2, qpid.max_size=100 and qpid.max_count=200 "); + try: + session.queue_declare(queue="q3", exclusive=True) + session.queue_declare(queue="q4", durable=True) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request for q3 and q4 with any parameter"); + + try: + session.queue_query(queue="q3") + self.fail("ACL should deny queue query request for q3"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + # some queues needs to be created for testing purge / move / reroute / redirect + session.queue_declare(queue="q7") + session.queue_declare(queue="q8") + session.queue_declare(queue="q9") + try: + session.queue_purge(queue="q7") + self.fail("ACL should deny queue purge request for q7"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.queue_purge(queue="q8") + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue purge request for q8"); + + # as we use QMF methods, it is easier to use BrokerAgent from messaging.connection and not use session object as above + broker_agent = BrokerAgent(self.get_messaging_connection('bob','bob')) + + try: + broker_agent.queueMoveMessages("q7", "q8", 0) + self.fail("ACL should deny queue move request from q7 to q8"); + except Exception, e: + self.assertTrue("'error_code': 7" in e.args[0]) + broker_agent = BrokerAgent(self.get_messaging_connection('bob','bob')) + + try: + broker_agent.queueMoveMessages("q8", "q9", 0) + except Exception, e: + if ("'error_code': 7" in e.args[0]): + self.fail("ACL should allow queue move request from q8 to q9"); + + try: + broker_agent.queueMoveMessages("q9", "q8", 0) + except Exception, e: + if ("'error_code': 7" in e.args[0]): + self.fail("ACL should allow queue move request from q9 to q8"); + + try: + broker_agent.Redirect("q7", "q8") + self.fail("ACL should deny queue redirect request from q7 to q8"); + except Exception, e: + self.assertTrue("'error_code': 7" in e.args[0]) + broker_agent = BrokerAgent(self.get_messaging_connection('bob','bob')) + + try: + broker_agent.Redirect("q8", "q9") + except Exception, e: + if ("'error_code': 7" in e.args[0]): + self.fail("ACL should allow queue redirect request from q8 to q9"); + + try: + broker_agent.Redirect("q9", "q8") + except Exception, e: + if ("'error_code': 7" in e.args[0]): + self.fail("ACL should allow queue redirect request from q9 to q8"); + + try: + broker_agent.getQueue('q7').reroute(0, False, "amq.fanout") + self.fail("ACL should deny queue reroute request from q7 to amq.fanout"); + except Exception, e: + self.assertTrue("'error_code': 7" in e.args[0]) + broker_agent = BrokerAgent(self.get_messaging_connection('bob','bob')) + + try: + broker_agent.getQueue('q8').reroute(0, False, "amq.fanout") + self.fail("ACL should deny queue reroute request from q8 to amq.fanout"); + except Exception, e: + self.assertTrue("'error_code': 7" in e.args[0]) + broker_agent = BrokerAgent(self.get_messaging_connection('bob','bob')) + + try: + broker_agent.getQueue('q8').reroute(0, False, "amq.direct") + except Exception, e: + if ("'error_code': 7" in e.args[0]): + self.fail("ACL should allow queue reroute request from q8 to amq.direct"); + + try: + broker_agent.getQueue('q9').reroute(0, False, "amq.fanout") + except Exception, e: + if ("'error_code': 7" in e.args[0]): + self.fail("ACL should allow queue reroute request from q9 to amq.fanout"); + + try: + session.queue_delete(queue="q4") + self.fail("ACL should deny queue delete request for q4"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.queue_delete(queue="q3") + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue delete request for q3"); + + + def test_queue_deny_mode(self): + """ + Test cases for queue acl in deny mode + """ + aclf = self.get_acl_file() + aclf.write('acl allow bob@QPID access queue name=q1\n') + aclf.write('acl allow bob@QPID create queue name=q1 durable=true\n') + aclf.write('acl allow bob@QPID create queue name=q2 exclusive=true policytype=ring\n') + aclf.write('acl allow bob@QPID access queue name=q3\n') + aclf.write('acl allow bob@QPID purge queue name=q3\n') + aclf.write('acl allow bob@QPID create queue name=q3\n') + aclf.write('acl allow bob@QPID create queue name=q4\n') + aclf.write('acl allow bob@QPID delete queue name=q4\n') + aclf.write('acl allow bob@QPID create queue name=q5 maxqueuesize=1000 maxqueuecount=100\n') + aclf.write('acl allow bob@QPID create queue name=q6 queuemaxsizelowerlimit=50 queuemaxsizeupperlimit=100 queuemaxcountlowerlimit=50 queuemaxcountupperlimit=100\n') + aclf.write('acl allow bob@QPID create queue name=q7 policytype=self-destruct\n') + aclf.write('acl allow anonymous all all\n') + aclf.write('acl deny all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + session = self.get_session('bob','bob') + + try: + session.queue_declare(queue="q1", durable=True) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request with name=q1 durable=true"); + + try: + session.queue_declare(queue="q1", durable=True, passive=True) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue passive declare request with name=q1 durable=true passive=true"); + + try: + session.queue_declare(queue="q1", durable=False, passive=False) + self.fail("ACL should deny queue create request with name=q1 durable=true passive=false"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.queue_declare(queue="q2", exclusive=False) + self.fail("ACL should deny queue create request with name=q2 exclusive=false"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.max_count"] = 200 + queue_options["qpid.max_size"] = 500 + session.queue_declare(queue="q5", arguments=queue_options) + self.fail("ACL should deny queue create request with name=q5 maxqueuesize=500 maxqueuecount=200"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.max_count"] = 100 + queue_options["qpid.max_size"] = 500 + session.queue_declare(queue="q5", arguments=queue_options) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request with name=q5 maxqueuesize=500 maxqueuecount=200"); + + try: + queue_options = {} + queue_options["qpid.max_count"] = 49 + queue_options["qpid.max_size"] = 100 + session.queue_declare(queue="q6", arguments=queue_options) + self.fail("ACL should deny queue create request with name=q6 maxqueuesize=100 maxqueuecount=49"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.max_count"] = 101 + queue_options["qpid.max_size"] = 100 + session.queue_declare(queue="q6", arguments=queue_options) + self.fail("ACL should allow queue create request with name=q6 maxqueuesize=100 maxqueuecount=101"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.max_count"] = 100 + queue_options["qpid.max_size"] = 49 + session.queue_declare(queue="q6", arguments=queue_options) + self.fail("ACL should deny queue create request with name=q6 maxqueuesize=49 maxqueuecount=100"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.max_count"] = 100 + queue_options["qpid.max_size"] =101 + session.queue_declare(queue="q6", arguments=queue_options) + self.fail("ACL should deny queue create request with name=q6 maxqueuesize=101 maxqueuecount=100"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.max_count"] = 50 + queue_options["qpid.max_size"] = 50 + session.queue_declare(queue="q6", arguments=queue_options) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request with name=q6 maxqueuesize=50 maxqueuecount=50"); + + try: + queue_options = {} + queue_options["qpid.policy_type"] = "ring" + session.queue_declare(queue="q2", exclusive=True, arguments=queue_options) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request for q2 with exclusive=true policytype=ring"); + + try: + session.queue_declare(queue="q7", arguments={"qpid.policy_type": "ring"}) + self.fail("ACL should not allow queue create request for q7 with policytype=ring"); + except qpid.session.SessionException, e: + session = self.get_session('bob','bob') + + try: + session.queue_declare(queue="q7", arguments={"qpid.policy_type": "self-destruct"}) + except qpid.session.SessionException, e: + self.fail("ACL should allow queue create request for q7 with policytype=self-destruct"); + + try: + session.queue_declare(queue="q3") + session.queue_declare(queue="q4") + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request for q3 and q4"); + + try: + session.queue_query(queue="q4") + self.fail("ACL should deny queue query request for q4"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.queue_purge(queue="q4") + self.fail("ACL should deny queue purge request for q4"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.queue_purge(queue="q3") + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue purge request for q3"); + + try: + session.queue_query(queue="q3") + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue query request for q3"); + + try: + session.queue_delete(queue="q3") + self.fail("ACL should deny queue delete request for q3"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.queue_delete(queue="q4") + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue delete request for q4"); + + #===================================== + # ACL paged tests + #===================================== + + def test_paged_allow_mode(self): + """ + Test cases for paged acl in allow mode + """ + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID create queue name=qf1 pageslowerlimit=1000\n') + aclf.write('acl deny bob@QPID create queue name=qf2 pagesupperlimit=100\n') + aclf.write('acl deny bob@QPID create queue name=qf3 pagefactorlowerlimit=10\n') + aclf.write('acl deny bob@QPID create queue name=qf4 pagefactorupperlimit=1\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.paging"] = True + queue_options["qpid.max_pages_loaded"] = 500 + session.queue_declare(queue="qf1", arguments=queue_options) + self.fail("ACL should deny queue create request with name=qf1, qpid.paging=True, qpid.max_pages_loaded=500"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.paging"] = True + queue_options["qpid.max_pages_loaded"] = 500 + session.queue_declare(queue="qf2", arguments=queue_options) + self.fail("ACL should deny queue create request with name=qf2, qpid.paging=True, qpid.max_pages_loaded=500"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.paging"] = True + queue_options["qpid.page_factor"] = 5 + session.queue_declare(queue="qf3", arguments=queue_options) + self.fail("ACL should deny queue create request with name=qf3, qpid.paging=True, qpid.page_factor=5"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.paging"] = True + queue_options["qpid.page_factor"] = 5 + session.queue_declare(queue="qf4", arguments=queue_options) + self.fail("ACL should deny queue create request with name=qf4, qpid.paging=True, qpid.page_factor=5"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + + def test_paged_deny_mode(self): + """ + Test cases for paged acl in deny mode + """ + aclf = self.get_acl_file() + aclf.write('acl allow bob@QPID create queue name=qf1 pageslowerlimit=100 pagesupperlimit=1000\n') + aclf.write('acl allow bob@QPID create queue name=qf2 pagefactorlowerlimit=1 pagefactorupperlimit=10\n') + aclf.write('acl allow anonymous all all\n') + aclf.write('acl deny all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.paging"] = True + queue_options["qpid.max_pages_loaded"] = 500 + session.queue_declare(queue="qf1", arguments=queue_options) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request with name=qf1, qpid.paging=True, qpid.max_pages_loaded=500"); + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.paging"] = True + queue_options["qpid.page_factor"] = 5 + session.queue_declare(queue="qf2", arguments=queue_options) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request with name=qf2, qpid.paging=True, qpid.page_factor=5"); + session = self.get_session('bob','bob') + + + #===================================== + # ACL file tests + #===================================== + + def test_file_allow_mode(self): + """ + Test cases for file acl in allow mode + """ + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID access queue name=qf1\n') + aclf.write('acl deny bob@QPID create queue name=qf1 durable=true\n') + aclf.write('acl deny bob@QPID create queue name=qf2 exclusive=true policytype=ring\n') + aclf.write('acl deny bob@QPID access queue name=qf3\n') + aclf.write('acl deny bob@QPID purge queue name=qf3\n') + aclf.write('acl deny bob@QPID delete queue name=qf4\n') + aclf.write('acl deny bob@QPID create queue name=qf5 filemaxsizeupperlimit=1000 filemaxcountupperlimit=100\n') + aclf.write('acl deny bob@QPID create queue name=ABCDE queuemaxsizelowerlimit=900000 queuemaxsizeupperlimit=1024000 queuemaxcountlowerlimit=900 queuemaxcountupperlimit=2000 filemaxsizelowerlimit=0 filemaxsizeupperlimit=32 filemaxcountlowerlimit=0 filemaxcountupperlimit=4 policytype=ring durable=false autodelete=true\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + session = self.get_session('bob','bob') + + self.Lookup("bob@QPID", "create", "queue", "ABCDE", {"durable":"false", + "autodelete":"true", + "exclusive":"false", + "alternate":"", + "policytype":"ring", + "maxqueuesize":"1024000", + "maxqueuecount":"1000", + "maxfilesize":"0", + "maxfilecount":"0" }, "deny") + + try: + queue_options = {} + queue_options["qpid.file_count"] = 200 + queue_options["qpid.file_size"] = 500 + session.queue_declare(queue="qf5", exclusive=True, durable=True, arguments=queue_options) + self.fail("ACL should deny queue create request with name=qf5, qpid.file_size=500 and qpid.file_count=200"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.file_count"] = 200 + queue_options["qpid.file_size"] = 100 + session.queue_declare(queue="qf2", exclusive=True, arguments=queue_options) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request with name=qf2, qpid.file_size=100 and qpid.file_count=200 "); + + + def test_file_deny_mode(self): + """ + Test cases for queue acl in deny mode + """ + aclf = self.get_acl_file() + aclf.write('acl allow bob@QPID access queue name=qfd1\n') + aclf.write('acl allow bob@QPID create queue name=qfd1 durable=true\n') + aclf.write('acl allow bob@QPID create queue name=qfd2 exclusive=true policytype=ring\n') + aclf.write('acl allow bob@QPID access queue name=qfd3\n') + aclf.write('acl allow bob@QPID purge queue name=qfd3\n') + aclf.write('acl allow bob@QPID create queue name=qfd3\n') + aclf.write('acl allow bob@QPID create queue name=qfd4\n') + aclf.write('acl allow bob@QPID delete queue name=qfd4\n') + aclf.write('acl allow bob@QPID create queue name=qfd5 filemaxsizeupperlimit=1000 filemaxcountupperlimit=100\n') + aclf.write('acl allow bob@QPID create queue name=qfd6 filemaxsizelowerlimit=50 filemaxsizeupperlimit=100 filemaxcountlowerlimit=50 filemaxcountupperlimit=100\n') + aclf.write('acl allow bob@QPID create queue name=ABCDE queuemaxsizelowerlimit=900000 queuemaxsizeupperlimit=1024000 queuemaxcountlowerlimit=900 queuemaxcountupperlimit=2000 filemaxsizelowerlimit=0 filemaxsizeupperlimit=32 filemaxcountlowerlimit=0 filemaxcountupperlimit=4 policytype=ring durable=false autodelete=true\n') + aclf.write('acl allow bob@QPID create queue name=FGHIJ queuemaxsizelowerlimit=900000 queuemaxsizeupperlimit=1024000 queuemaxcountlowerlimit=900 queuemaxcountupperlimit=2000 filemaxsizelowerlimit=2 filemaxsizeupperlimit=32 filemaxcountlowerlimit=0 filemaxcountupperlimit=4 policytype=ring durable=false autodelete=true\n') + aclf.write('acl allow bob@QPID create queue name=KLMNO queuemaxsizelowerlimit=900000 queuemaxsizeupperlimit=1024000 queuemaxcountlowerlimit=900 queuemaxcountupperlimit=2000 filemaxsizelowerlimit=0 filemaxsizeupperlimit=0 filemaxcountlowerlimit=0 filemaxcountupperlimit=4 policytype=ring durable=false autodelete=true\n') + aclf.write('acl allow anonymous all all\n') + aclf.write('acl deny all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + session = self.get_session('bob','bob') + + self.Lookup("bob@QPID", "create", "queue", "ABCDE", {"durable":"false", + "autodelete":"true", + "exclusive":"false", + "alternate":"", + "policytype":"ring", + "maxqueuesize":"1024000", + "maxqueuecount":"1000", + "maxfilesize":"0", + "maxfilecount":"0" }, "allow") + + self.Lookup("bob@QPID", "create", "queue", "FGHIJ", {"durable":"false", + "autodelete":"true", + "exclusive":"false", + "alternate":"", + "policytype":"ring", + "maxqueuesize":"1024000", + "maxqueuecount":"1000", + "maxfilesize":"1", + "maxfilecount":"0" }, "deny") + + self.Lookup("bob@QPID", "create", "queue", "FGHIJ", {"durable":"false", + "autodelete":"true", + "exclusive":"false", + "alternate":"", + "policytype":"ring", + "maxqueuesize":"1024000", + "maxqueuecount":"1000", + "maxfilesize":"2", + "maxfilecount":"0" }, "allow") + + self.Lookup("bob@QPID", "create", "queue", "FGHIJ", {"durable":"false", + "autodelete":"true", + "exclusive":"false", + "alternate":"", + "policytype":"ring", + "maxqueuesize":"1024000", + "maxqueuecount":"1000", + "maxfilesize":"32", + "maxfilecount":"0" }, "allow") + + self.Lookup("bob@QPID", "create", "queue", "FGHIJ", {"durable":"false", + "autodelete":"true", + "exclusive":"false", + "alternate":"", + "policytype":"ring", + "maxqueuesize":"1024000", + "maxqueuecount":"1000", + "maxfilesize":"33", + "maxfilecount":"0" }, "deny") + + self.Lookup("bob@QPID", "create", "queue", "KLMNO", {"durable":"false", + "autodelete":"true", + "exclusive":"false", + "alternate":"", + "policytype":"ring", + "maxqueuesize":"1024000", + "maxqueuecount":"1000", + "maxfilesize":"0", + "maxfilecount":"0" }, "allow") + + self.Lookup("bob@QPID", "create", "queue", "KLMNO", {"durable":"false", + "autodelete":"true", + "exclusive":"false", + "alternate":"", + "policytype":"ring", + "maxqueuesize":"1024000", + "maxqueuecount":"1000", + "maxfilesize":"17", + "maxfilecount":"0" }, "allow") + + self.Lookup("bob@QPID", "create", "queue", "KLMNO", {"durable":"false", + "autodelete":"true", + "exclusive":"false", + "alternate":"", + "policytype":"ring", + "maxqueuesize":"1024000", + "maxqueuecount":"1000", + "maxfilesize":"33", + "maxfilecount":"0" }, "allow") + + try: + session.queue_declare(queue="qfd1", durable=True) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request with name=qfd1 durable=true"); + + try: + session.queue_declare(queue="qfd1", durable=True, passive=True) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue passive declare request with name=qfd1 durable=true passive=true"); + + try: + session.queue_declare(queue="qfd1", durable=False, passive=False) + self.fail("ACL should deny queue create request with name=qfd1 durable=true passive=false"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.queue_declare(queue="qfd2", exclusive=False) + self.fail("ACL should deny queue create request with name=qfd2 exclusive=false"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.file_count"] = 200 + queue_options["qpid.file_size"] = 500 + session.queue_declare(queue="qfd5", durable=True, arguments=queue_options) + self.fail("ACL should deny queue create request with name=qfd5 filemaxsizeupperlimit=500 filemaxcountupperlimit=200"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.file_count"] = 100 + queue_options["qpid.file_size"] = 500 + session.queue_declare(queue="qfd5", durable=True, arguments=queue_options) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request with name=qfd5 filemaxsizeupperlimit=500 filemaxcountupperlimit=200"); + + try: + queue_options = {} + queue_options["qpid.file_count"] = 49 + queue_options["qpid.file_size"] = 100 + session.queue_declare(queue="qfd6", durable=True, arguments=queue_options) + self.fail("ACL should deny queue create request with name=qfd6 filemaxsizeupperlimit=100 filemaxcountupperlimit=49"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.file_count"] = 101 + queue_options["qpid.file_size"] = 100 + session.queue_declare(queue="qfd6", durable=True, arguments=queue_options) + self.fail("ACL should allow queue create request with name=qfd6 filemaxsizeupperlimit=100 filemaxcountupperlimit=101"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.file_count"] = 100 + queue_options["qpid.file_size"] = 49 + session.queue_declare(queue="qfd6", durable=True, arguments=queue_options) + self.fail("ACL should deny queue create request with name=qfd6 filemaxsizeupperlimit=49 filemaxcountupperlimit=100"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.file_count"] = 100 + queue_options["qpid.file_size"] =101 + session.queue_declare(queue="qfd6", durable=True, arguments=queue_options) + self.fail("ACL should deny queue create request with name=qfd6 filemaxsizeupperlimit=101 filemaxcountupperlimit=100"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.file_count"] = 50 + queue_options["qpid.file_size"] = 50 + session.queue_declare(queue="qfd6", durable=True, arguments=queue_options) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request with name=qfd6 filemaxsizeupperlimit=50 filemaxcountupperlimit=50"); + + + #===================================== + # ACL exchange tests + #===================================== + + def test_exchange_acl_allow_mode(self): + session = self.get_session('bob','bob') + session.queue_declare(queue="baz") + + """ + Test cases for exchange acl in allow mode + """ + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID access exchange name=testEx\n') + aclf.write('acl deny bob@QPID create exchange name=testEx durable=true\n') + aclf.write('acl deny bob@QPID create exchange name=ex1 type=direct\n') + aclf.write('acl deny bob@QPID access exchange name=myEx queuename=q1 routingkey=rk1.*\n') + aclf.write('acl deny bob@QPID bind exchange name=myEx queuename=q1 routingkey=rk1\n') + aclf.write('acl deny bob@QPID unbind exchange name=myEx queuename=q1 routingkey=rk1\n') + aclf.write('acl deny bob@QPID delete exchange name=myEx\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + session = self.get_session('bob','bob') + session.queue_declare(queue='q1') + session.queue_declare(queue='q2') + session.exchange_declare(exchange='myEx', type='direct') + + try: + session.exchange_declare(exchange='testEx', durable=True) + self.fail("ACL should deny exchange create request with name=testEx durable=true"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_declare(exchange='testEx', durable=True, passive=True) + self.fail("ACL should deny passive exchange declare request with name=testEx durable=true passive=true"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_declare(exchange='testEx', type='direct', durable=False) + except qpid.session.SessionException, e: + print e + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange create request for testEx with any parameter other than durable=true"); + + try: + session.exchange_declare(exchange='ex1', type='direct') + self.fail("ACL should deny exchange create request with name=ex1 type=direct"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_declare(exchange='myXml', type='direct') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange create request for myXml with any parameter"); + + try: + session.exchange_query(name='myEx') + self.fail("ACL should deny exchange query request for myEx"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_bound(exchange='myEx', queue='q1', binding_key='rk1.*') + self.fail("ACL should deny exchange bound request for myEx with queuename=q1 and routing_key='rk1.*' "); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_query(name='amq.topic') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange query request for exchange='amq.topic'"); + + try: + session.exchange_bound(exchange='myEx', queue='q1', binding_key='rk2.*') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange bound request for myEx with queuename=q1 and binding_key='rk2.*'"); + + try: + session.exchange_bind(exchange='myEx', queue='q1', binding_key='rk1') + self.fail("ACL should deny exchange bind request with exchange='myEx' queuename='q1' bindingkey='rk1'"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_bind(exchange='myEx', queue='q1', binding_key='x') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange bind request for exchange='myEx', queue='q1', binding_key='x'"); + + try: + session.exchange_bind(exchange='myEx', queue='q2', binding_key='rk1') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange bind request for exchange='myEx', queue='q2', binding_key='rk1'"); + + try: + session.exchange_unbind(exchange='myEx', queue='q1', binding_key='rk1') + self.fail("ACL should deny exchange unbind request with exchange='myEx' queuename='q1' bindingkey='rk1'"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_unbind(exchange='myEx', queue='q1', binding_key='x') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange unbind request for exchange='myEx', queue='q1', binding_key='x'"); + + try: + session.exchange_unbind(exchange='myEx', queue='q2', binding_key='rk1') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange unbind request for exchange='myEx', queue='q2', binding_key='rk1'"); + + try: + session.exchange_delete(exchange='myEx') + self.fail("ACL should deny exchange delete request for myEx"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_delete(exchange='myXml') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange delete request for myXml"); + + + def test_exchange_acl_deny_mode(self): + session = self.get_session('bob','bob') + session.queue_declare(queue='bar') + + """ + Test cases for exchange acl in deny mode + """ + aclf = self.get_acl_file() + aclf.write('acl allow bob@QPID create exchange name=myEx durable=true\n') + aclf.write('acl allow bob@QPID bind exchange name=amq.topic queuename=bar routingkey=foo.*\n') + aclf.write('acl allow bob@QPID unbind exchange name=amq.topic queuename=bar routingkey=foo.*\n') + aclf.write('acl allow bob@QPID access exchange name=myEx queuename=q1 routingkey=rk1.*\n') + aclf.write('acl allow bob@QPID delete exchange name=myEx\n') + aclf.write('acl allow anonymous all all\n') + aclf.write('acl deny all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + session = self.get_session('bob','bob') + + try: + session.exchange_declare(exchange='myEx', type='direct', durable=True, passive=False) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange create request for myEx with durable=true and passive=false"); + + try: + session.exchange_declare(exchange='myEx', type='direct', durable=False) + self.fail("ACL should deny exchange create request with name=myEx durable=false"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_bind(exchange='amq.topic', queue='bar', binding_key='foo.bar') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange bind request for exchange='amq.topic', queue='bar', binding_key='foor.bar'"); + + try: + session.exchange_bind(exchange='amq.topic', queue='baz', binding_key='foo.bar') + self.fail("ACL should deny exchange bind request for exchange='amq.topic', queue='baz', binding_key='foo.bar'"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_bind(exchange='amq.topic', queue='bar', binding_key='fooz.bar') + self.fail("ACL should deny exchange bind request for exchange='amq.topic', queue='bar', binding_key='fooz.bar'"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_unbind(exchange='amq.topic', queue='bar', binding_key='foo.bar') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange unbind request for exchange='amq.topic', queue='bar', binding_key='foor.bar'"); + try: + session.exchange_unbind(exchange='amq.topic', queue='baz', binding_key='foo.bar') + self.fail("ACL should deny exchange unbind request for exchange='amq.topic', queue='baz', binding_key='foo.bar'"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_unbind(exchange='amq.topic', queue='bar', binding_key='fooz.bar') + self.fail("ACL should deny exchange unbind request for exchange='amq.topic', queue='bar', binding_key='fooz.bar'"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_query(name='amq.topic') + self.fail("ACL should deny exchange query request for amq.topic"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_bound(exchange='myEx', queue='q1', binding_key='rk2.*') + self.fail("ACL should deny exchange bound request for amq.topic with queuename=q1 and routing_key='rk2.*' "); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_query(name='myEx') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange query request for exchange='myEx'"); + + try: + session.exchange_bound(exchange='myEx', queue='q1', binding_key='rk1.*') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange bound request for myEx with queuename=q1 and binding_key='rk1.*'"); + + try: + session.exchange_delete(exchange='myEx') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow exchange delete request for myEx"); + + def test_create_and_delete_exchange_via_qmf(self): + """ + Test acl is enforced when creating/deleting via QMF + methods. Note that in order to be able to send the QMF methods + and receive the responses a significant amount of permissions + need to be enabled (TODO: can the set below be narrowed down + at all?) + """ + aclf = self.get_acl_file() + aclf.write('acl allow bob@QPID create exchange\n') + aclf.write('acl allow admin@QPID delete exchange\n') + aclf.write('acl allow all access exchange\n') + aclf.write('acl allow all bind exchange\n') + aclf.write('acl allow all create queue\n') + aclf.write('acl allow all access queue\n') + aclf.write('acl allow all delete queue\n') + aclf.write('acl allow all consume queue\n') + aclf.write('acl allow all access method\n') + aclf.write('acl deny all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + bob = BrokerAdmin(self.config.broker, "bob", "bob") + bob.create_exchange("my-exchange") #should pass + #cleanup by deleting exchange + try: + bob.delete_exchange("my-exchange") #should fail + self.fail("ACL should deny exchange delete request for my-exchange"); + except Exception, e: + self.assertEqual(7,e.args[0]["error_code"]) + assert e.args[0]["error_text"].find("unauthorized-access") == 0 + admin = BrokerAdmin(self.config.broker, "admin", "admin") + admin.delete_exchange("my-exchange") #should pass + + anonymous = BrokerAdmin(self.config.broker) + try: + anonymous.create_exchange("another-exchange") #should fail + self.fail("ACL should deny exchange create request for another-exchange"); + except Exception, e: + self.assertEqual(7,e.args[0]["error_code"]) + assert e.args[0]["error_text"].find("unauthorized-access") == 0 + + def test_qmf_query(self): + aclf = self.get_acl_file() + aclf.write('acl allow all access exchange\n') + aclf.write('acl allow all bind exchange\n') + aclf.write('acl allow all create queue\n') + aclf.write('acl allow all access queue\n') + aclf.write('acl allow all delete queue\n') + aclf.write('acl allow all consume queue\n') + aclf.write('acl allow all access method\n') + aclf.write('acl allow bob@QPID access query name=org.apache.qpid.broker:queue:q1\n') + aclf.write('acl allow bob@QPID access query schemaclass=exchange\n') + aclf.write('acl deny all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + bob = BrokerAdmin(self.config.broker, "bob", "bob") + + try: + bob.query(object_name="org.apache.qpid.broker:queue:q1") + except Exception, e: + if ("unauthorized-access:" in e.args[0]): + self.fail("ACL should allow queue QMF query for q1"); + + try: + bob.query(object_name="org.apache.qpid.broker:queue:q2") + self.fail("ACL should deny queue QMF query for q2"); + except Exception, e: + self.assertTrue("unauthorized-access:" in e.args[0]) + bob = BrokerAdmin(self.config.broker, "bob", "bob") + + try: + bob.query(class_name="binding") + self.fail("ACL should deny class binding QMF query"); + except Exception, e: + self.assertTrue("unauthorized-access:" in e.args[0]) + bob = BrokerAdmin(self.config.broker, "bob", "bob") + + try: + bob.query(class_name="exchange") + except Exception, e: + if ("unauthorized-access:" in e.args[0]): + self.fail("ACL should allow class exchange QMF query"); + + + #===================================== + # ACL consume tests + #===================================== + + def test_consume_allow_mode(self): + """ + Test cases for consume in allow mode + """ + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID consume queue name=q1\n') + aclf.write('acl deny bob@QPID consume queue name=q2\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + session = self.get_session('bob','bob') + + + try: + session.queue_declare(queue='q1') + session.queue_declare(queue='q2') + session.queue_declare(queue='q3') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow create queue request"); + + try: + session.message_subscribe(queue='q1', destination='myq1') + self.fail("ACL should deny subscription for queue='q1'"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.message_subscribe(queue='q2', destination='myq1') + self.fail("ACL should deny subscription for queue='q2'"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.message_subscribe(queue='q3', destination='myq3') + session.message_cancel(destination='myq3') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow subscription for q3"); + + + def test_consume_deny_mode(self): + """ + Test cases for consume in allow mode + """ + aclf = self.get_acl_file() + aclf.write('acl allow bob@QPID consume queue name=q1\n') + aclf.write('acl allow bob@QPID consume queue name=q2\n') + aclf.write('acl allow bob@QPID create queue\n') + aclf.write('acl allow anonymous all\n') + aclf.write('acl deny all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + session = self.get_session('bob','bob') + + + try: + session.queue_declare(queue='q1') + session.queue_declare(queue='q2') + session.queue_declare(queue='q3') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow create queue request"); + + try: + session.message_subscribe(queue='q1', destination='myq1') + session.message_subscribe(queue='q2', destination='myq2') + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow subscription for q1 and q2"); + + try: + session.message_subscribe(queue='q3', destination='myq3') + self.fail("ACL should deny subscription for queue='q3'"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + + #===================================== + # ACL publish tests + #===================================== + + def test_publish_acl_allow_mode(self): + """ + Test various publish acl + """ + aclf = self.get_acl_file() + aclf.write('acl deny bob@QPID publish exchange name=amq.direct routingkey=rk1\n') + aclf.write('acl deny bob@QPID publish exchange name=amq.topic\n') + aclf.write('acl deny bob@QPID publish exchange name=myEx routingkey=rk2\n') + aclf.write("acl deny bob@QPID publish exchange name=amq.default routingkey=restricted\n") + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + session = self.get_session('bob','bob') + + props = session.delivery_properties(routing_key="rk1") + + try: + session.message_transfer(destination="amq.direct", message=Message(props,"Test")) + self.fail("ACL should deny message transfer to name=amq.direct routingkey=rk1"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.message_transfer(destination="amq.topic", message=Message(props,"Test")) + self.fail("ACL should deny message transfer to name=amq.topic"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_declare(exchange='myEx', type='direct', durable=False) + session.message_transfer(destination="myEx", message=Message(props,"Test")) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow message transfer to exchange myEx with routing key rk1"); + + + props = session.delivery_properties(routing_key="rk2") + try: + session.message_transfer(destination="amq.direct", message=Message(props,"Test")) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow message transfer to exchange amq.direct with routing key rk2"); + + self.LookupPublish("bob@QPID", "", "restricted", "deny") + self.LookupPublish("bob@QPID", "", "another", "allow") + self.LookupPublish("joe@QPID", "", "restricted", "allow") + + + def test_publish_acl_deny_mode(self): + """ + Test various publish acl + """ + aclf = self.get_acl_file() + aclf.write('acl allow bob@QPID publish exchange name=amq.direct routingkey=rk1\n') + aclf.write('acl allow bob@QPID publish exchange name=amq.topic\n') + aclf.write('acl allow bob@QPID publish exchange name=myEx routingkey=rk2\n') + aclf.write('acl allow bob@QPID create exchange\n') + aclf.write("acl allow bob@QPID publish exchange name=amq.default routingkey=unrestricted\n") + aclf.write('acl allow anonymous all all \n') + aclf.write('acl deny all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + session = self.get_session('bob','bob') + + props = session.delivery_properties(routing_key="rk2") + + try: + session.message_transfer(destination="amq.direct", message=Message(props,"Test")) + self.fail("ACL should deny message transfer to name=amq.direct routingkey=rk2"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.message_transfer(destination="amq.topic", message=Message(props,"Test")) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow message transfer to exchange amq.topic with any routing key"); + + try: + session.exchange_declare(exchange='myEx', type='direct', durable=False) + session.message_transfer(destination="myEx", message=Message(props,"Test")) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow message transfer to exchange myEx with routing key=rk2"); + + props = session.delivery_properties(routing_key="rk1") + + try: + session.message_transfer(destination="myEx", message=Message(props,"Test")) + self.fail("ACL should deny message transfer to name=myEx routingkey=rk1"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.message_transfer(destination="amq.direct", message=Message(props,"Test")) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow message transfer to exchange amq.direct with routing key rk1"); + + self.LookupPublish("bob@QPID", "", "unrestricted", "allow") + self.LookupPublish("bob@QPID", "", "another", "deny") + self.LookupPublish("joe@QPID", "", "unrestricted", "deny") + + + #===================================== + # ACL broker configuration tests + #===================================== + + def test_broker_timestamp_config(self): + """ + Test ACL control of the broker timestamp configuration + """ + aclf = self.get_acl_file() + # enable lots of stuff to allow QMF to work + aclf.write('acl allow all create exchange\n') + aclf.write('acl allow all access exchange\n') + aclf.write('acl allow all bind exchange\n') + aclf.write('acl allow all publish exchange\n') + aclf.write('acl allow all create queue\n') + aclf.write('acl allow all access queue\n') + aclf.write('acl allow all delete queue\n') + aclf.write('acl allow all consume queue\n') + aclf.write('acl allow all access method\n') + # this should let bob access the timestamp configuration + aclf.write('acl allow bob@QPID access broker\n') + aclf.write('acl allow bob@QPID update broker\n') + aclf.write('acl allow admin@QPID all all\n') + aclf.write('acl deny all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + ts = None + bob = BrokerAdmin(self.config.broker, "bob", "bob") + ts = bob.get_timestamp_cfg() #should work + bob.set_timestamp_cfg(ts); #should work + + obo = BrokerAdmin(self.config.broker, "obo", "obo") + try: + ts = obo.get_timestamp_cfg() #should fail + failed = False + except Exception, e: + failed = True + self.assertEqual(7,e.args[0]["error_code"]) + assert e.args[0]["error_text"].find("unauthorized-access") == 0 + assert(failed) + + try: + obo.set_timestamp_cfg(ts) #should fail + failed = False + except Exception, e: + failed = True + self.assertEqual(7,e.args[0]["error_code"]) + assert e.args[0]["error_text"].find("unauthorized-access") == 0 + assert(failed) + + admin = BrokerAdmin(self.config.broker, "admin", "admin") + ts = admin.get_timestamp_cfg() #should pass + admin.set_timestamp_cfg(ts) #should pass + + + + #===================================== + # QMF Functional tests + #===================================== + + def test_qmf_functional_tests(self): + """ + Test using QMF method hooks into ACL logic + """ + aclf = self.get_acl_file() + aclf.write('group admins moe@COMPANY.COM \\\n') + aclf.write(' larry@COMPANY.COM \\\n') + aclf.write(' curly@COMPANY.COM \\\n') + aclf.write(' shemp@COMPANY.COM\n') + aclf.write('group auditors aaudit@COMPANY.COM baudit@COMPANY.COM caudit@COMPANY.COM \\\n') + aclf.write(' daudit@COMPANY.COM eaduit@COMPANY.COM eaudit@COMPANY.COM\n') + aclf.write('group tatunghosts tatung01@COMPANY.COM \\\n') + aclf.write(' tatung02/x86.build.company.com@COMPANY.COM \\\n') + aclf.write(' tatung03/x86.build.company.com@COMPANY.COM \\\n') + aclf.write(' tatung04/x86.build.company.com@COMPANY.COM \n') + aclf.write('group publishusers publish@COMPANY.COM x-pubs@COMPANY.COM\n') + aclf.write('acl allow-log admins all all\n') + aclf.write('# begin hack alert: allow anonymous to access the lookup debug functions\n') + aclf.write('acl allow-log anonymous create queue\n') + aclf.write('acl allow-log anonymous all exchange name=qmf.*\n') + aclf.write('acl allow-log anonymous all exchange name=amq.direct\n') + aclf.write('acl allow-log anonymous all exchange name=qpid.management\n') + aclf.write('acl allow-log anonymous access method name=*\n') + aclf.write('# end hack alert\n') + aclf.write('acl allow-log auditors all exchange name=company.topic routingkey=private.audit.*\n') + aclf.write('acl allow-log tatunghosts publish exchange name=company.topic routingkey=tatung.*\n') + aclf.write('acl allow-log tatunghosts publish exchange name=company.direct routingkey=tatung-service-queue\n') + aclf.write('acl allow-log publishusers create queue\n') + aclf.write('acl allow-log publishusers publish exchange name=qpid.management routingkey=broker\n') + aclf.write('acl allow-log publishusers publish exchange name=qmf.default.topic routingkey=*\n') + aclf.write('acl allow-log publishusers publish exchange name=qmf.default.direct routingkey=*\n') + aclf.write('acl allow-log all bind exchange name=company.topic routingkey=tatung.*\n') + aclf.write('acl allow-log all bind exchange name=company.direct routingkey=tatung-service-queue\n') + aclf.write('acl allow-log all consume queue\n') + aclf.write('acl allow-log all access exchange\n') + aclf.write('acl allow-log all access queue\n') + aclf.write('acl allow-log all create queue name=tmp.* durable=false autodelete=true exclusive=true policytype=ring\n') + aclf.write('acl allow mrQ create queue queuemaxsizelowerlimit=100 queuemaxsizeupperlimit=200 queuemaxcountlowerlimit=300 queuemaxcountupperlimit=400\n') + aclf.write('acl deny-log all all\n') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + # + # define some group lists + # + g_admins = ['moe@COMPANY.COM', \ + 'larry@COMPANY.COM', \ + 'curly@COMPANY.COM', \ + 'shemp@COMPANY.COM'] + + g_auditors = [ 'aaudit@COMPANY.COM','baudit@COMPANY.COM','caudit@COMPANY.COM', \ + 'daudit@COMPANY.COM','eaduit@COMPANY.COM','eaudit@COMPANY.COM'] + + g_tatunghosts = ['tatung01@COMPANY.COM', \ + 'tatung02/x86.build.company.com@COMPANY.COM', \ + 'tatung03/x86.build.company.com@COMPANY.COM', \ + 'tatung04/x86.build.company.com@COMPANY.COM'] + + g_publishusers = ['publish@COMPANY.COM', 'x-pubs@COMPANY.COM'] + + g_public = ['jpublic@COMPANY.COM', 'me@yahoo.com'] + + g_all = g_admins + g_auditors + g_tatunghosts + g_publishusers + g_public + + action_all = ['consume','publish','create','access','bind','unbind','delete','purge','update'] + + # + # Run some tests verifying against users who are in and who are out of given groups. + # + + for u in g_admins: + self.Lookup(u, "create", "queue", "anything", {"durable":"true"}, "allow-log") + uInTest = g_auditors + g_admins + uOutTest = self.AllBut(g_all, uInTest) + + for u in uInTest: + self.LookupPublish(u, "company.topic", "private.audit.This", "allow-log") + + for u in uInTest: + for a in ['bind', 'unbind', 'access', 'publish']: + self.Lookup(u, a, "exchange", "company.topic", {"routingkey":"private.audit.This"}, "allow-log") + + for u in uOutTest: + self.LookupPublish(u, "company.topic", "private.audit.This", "deny-log") + self.Lookup(u, "bind", "exchange", "company.topic", {"routingkey":"private.audit.This"}, "deny-log") + + uInTest = g_admins + g_tatunghosts + uOutTest = self.AllBut(g_all, uInTest) + + for u in uInTest: + self.LookupPublish(u, "company.topic", "tatung.this2", "allow-log") + self.LookupPublish(u, "company.direct", "tatung-service-queue", "allow-log") + + for u in uOutTest: + self.LookupPublish(u, "company.topic", "tatung.this2", "deny-log") + self.LookupPublish(u, "company.direct", "tatung-service-queue", "deny-log") + + for u in uOutTest: + for a in ["bind", "access"]: + self.Lookup(u, a, "exchange", "company.topic", {"routingkey":"tatung.this2"}, "allow-log") + self.Lookup(u, a, "exchange", "company.direct", {"routingkey":"tatung-service-queue"}, "allow-log") + + uInTest = g_admins + g_publishusers + uOutTest = self.AllBut(g_all, uInTest) + + for u in uInTest: + self.LookupPublish(u, "qpid.management", "broker", "allow-log") + self.LookupPublish(u, "qmf.default.topic", "this3", "allow-log") + self.LookupPublish(u, "qmf.default.direct", "this4", "allow-log") + + for u in uOutTest: + self.LookupPublish(u, "qpid.management", "broker", "deny-log") + self.LookupPublish(u, "qmf.default.topic", "this3", "deny-log") + self.LookupPublish(u, "qmf.default.direct", "this4", "deny-log") + + for u in uOutTest: + for a in ["bind"]: + self.Lookup(u, a, "exchange", "qpid.management", {"routingkey":"broker"}, "deny-log") + self.Lookup(u, a, "exchange", "qmf.default.topic", {"routingkey":"this3"}, "deny-log") + self.Lookup(u, a, "exchange", "qmf.default.direct", {"routingkey":"this4"}, "deny-log") + for a in ["access"]: + self.Lookup(u, a, "exchange", "qpid.management", {"routingkey":"broker"}, "allow-log") + self.Lookup(u, a, "exchange", "qmf.default.topic", {"routingkey":"this3"}, "allow-log") + self.Lookup(u, a, "exchange", "qmf.default.direct", {"routingkey":"this4"}, "allow-log") + + # Test against queue size limits + + self.Lookup('mrQ', 'create', 'queue', 'abc', {"maxqueuesize":"150", "maxqueuecount":"350"}, "allow") + self.Lookup('mrQ', 'create', 'queue', 'def', {"maxqueuesize":"99", "maxqueuecount":"350"}, "deny") + self.Lookup('mrQ', 'create', 'queue', 'uvw', {"maxqueuesize":"201", "maxqueuecount":"350"}, "deny") + self.Lookup('mrQ', 'create', 'queue', 'xyz', {"maxqueuesize":"150", "maxqueuecount":"299"}, "deny") + self.Lookup('mrQ', 'create', 'queue', '', {"maxqueuesize":"150", "maxqueuecount":"401"}, "deny") + self.Lookup('mrQ', 'create', 'queue', '', {"maxqueuesize":"0", "maxqueuecount":"401"}, "deny") + self.Lookup('mrQ', 'create', 'queue', '', {"maxqueuesize":"150", "maxqueuecount":"0" }, "deny") + + + #===================================== + # Routingkey lookup using Topic Exchange tests + #===================================== + + def test_topic_exchange_publish_tests(self): + """ + Test using QMF method hooks into ACL logic + """ + aclf = self.get_acl_file() + aclf.write('# begin hack alert: allow anonymous to access the lookup debug functions\n') + aclf.write('acl allow-log anonymous create queue\n') + aclf.write('acl allow-log anonymous all exchange name=qmf.*\n') + aclf.write('acl allow-log anonymous all exchange name=amq.direct\n') + aclf.write('acl allow-log anonymous all exchange name=qpid.management\n') + aclf.write('acl allow-log anonymous access method name=*\n') + aclf.write('# end hack alert\n') + aclf.write('acl allow-log uPlain1@COMPANY publish exchange name=X routingkey=ab.cd.e\n') + aclf.write('acl allow-log uPlain2@COMPANY publish exchange name=X routingkey=.\n') + aclf.write('acl allow-log uStar1@COMPANY publish exchange name=X routingkey=a.*.b\n') + aclf.write('acl allow-log uStar2@COMPANY publish exchange name=X routingkey=*.x\n') + aclf.write('acl allow-log uStar3@COMPANY publish exchange name=X routingkey=x.x.*\n') + aclf.write('acl allow-log uHash1@COMPANY publish exchange name=X routingkey=a.#.b\n') + aclf.write('acl allow-log uHash2@COMPANY publish exchange name=X routingkey=a.#\n') + aclf.write('acl allow-log uHash3@COMPANY publish exchange name=X routingkey=#.a\n') + aclf.write('acl allow-log uHash4@COMPANY publish exchange name=X routingkey=a.#.b.#.c\n') + aclf.write('acl allow-log uMixed1@COMPANY publish exchange name=X routingkey=*.x.#.y\n') + aclf.write('acl allow-log uMixed2@COMPANY publish exchange name=X routingkey=a.#.b.*\n') + aclf.write('acl allow-log uMixed3@COMPANY publish exchange name=X routingkey=*.*.*.#\n') + + aclf.write('acl allow-log all publish exchange name=X routingkey=MN.OP.Q\n') + aclf.write('acl allow-log all publish exchange name=X routingkey=M.*.N\n') + aclf.write('acl allow-log all publish exchange name=X routingkey=M.#.N\n') + aclf.write('acl allow-log all publish exchange name=X routingkey=*.M.#.N\n') + + aclf.write('acl deny-log all all\n') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + # aclKey: "ab.cd.e" + self.LookupPublish("uPlain1@COMPANY", "X", "ab.cd.e", "allow-log") + self.LookupPublish("uPlain1@COMPANY", "X", "abx.cd.e", "deny-log") + self.LookupPublish("uPlain1@COMPANY", "X", "ab.cd", "deny-log") + self.LookupPublish("uPlain1@COMPANY", "X", "ab.cd..e.", "deny-log") + self.LookupPublish("uPlain1@COMPANY", "X", "ab.cd.e.", "deny-log") + self.LookupPublish("uPlain1@COMPANY", "X", ".ab.cd.e", "deny-log") + # aclKey: "." + self.LookupPublish("uPlain2@COMPANY", "X", ".", "allow-log") + + # aclKey: "a.*.b" + self.LookupPublish("uStar1@COMPANY", "X", "a.xx.b", "allow-log") + self.LookupPublish("uStar1@COMPANY", "X", "a.b", "deny-log") + # aclKey: "*.x" + self.LookupPublish("uStar2@COMPANY", "X", "y.x", "allow-log") + self.LookupPublish("uStar2@COMPANY", "X", ".x", "allow-log") + self.LookupPublish("uStar2@COMPANY", "X", "x", "deny-log") + # aclKey: "x.x.*" + self.LookupPublish("uStar3@COMPANY", "X", "x.x.y", "allow-log") + self.LookupPublish("uStar3@COMPANY", "X", "x.x.", "allow-log") + self.LookupPublish("uStar3@COMPANY", "X", "x.x", "deny-log") + self.LookupPublish("uStar3@COMPANY", "X", "q.x.y", "deny-log") + + # aclKey: "a.#.b" + self.LookupPublish("uHash1@COMPANY", "X", "a.b", "allow-log") + self.LookupPublish("uHash1@COMPANY", "X", "a.x.b", "allow-log") + self.LookupPublish("uHash1@COMPANY", "X", "a..x.y.zz.b", "allow-log") + self.LookupPublish("uHash1@COMPANY", "X", "a.b.", "deny-log") + self.LookupPublish("uHash1@COMPANY", "X", "q.x.b", "deny-log") + + # aclKey: "a.#" + self.LookupPublish("uHash2@COMPANY", "X", "a", "allow-log") + self.LookupPublish("uHash2@COMPANY", "X", "a.b", "allow-log") + self.LookupPublish("uHash2@COMPANY", "X", "a.b.c", "allow-log") + + # aclKey: "#.a" + self.LookupPublish("uHash3@COMPANY", "X", "a", "allow-log") + self.LookupPublish("uHash3@COMPANY", "X", "x.y.a", "allow-log") + + # aclKey: "a.#.b.#.c" + self.LookupPublish("uHash4@COMPANY", "X", "a.b.c", "allow-log") + self.LookupPublish("uHash4@COMPANY", "X", "a.x.b.y.c", "allow-log") + self.LookupPublish("uHash4@COMPANY", "X", "a.x.x.b.y.y.c", "allow-log") + + # aclKey: "*.x.#.y" + self.LookupPublish("uMixed1@COMPANY", "X", "a.x.y", "allow-log") + self.LookupPublish("uMixed1@COMPANY", "X", "a.x.p.qq.y", "allow-log") + self.LookupPublish("uMixed1@COMPANY", "X", "a.a.x.y", "deny-log") + self.LookupPublish("uMixed1@COMPANY", "X", "aa.x.b.c", "deny-log") + + # aclKey: "a.#.b.*" + self.LookupPublish("uMixed2@COMPANY", "X", "a.b.x", "allow-log") + self.LookupPublish("uMixed2@COMPANY", "X", "a.x.x.x.b.x", "allow-log") + + # aclKey: "*.*.*.#" + self.LookupPublish("uMixed3@COMPANY", "X", "x.y.z", "allow-log") + self.LookupPublish("uMixed3@COMPANY", "X", "x.y.z.a.b.c", "allow-log") + self.LookupPublish("uMixed3@COMPANY", "X", "x.y", "deny-log") + self.LookupPublish("uMixed3@COMPANY", "X", "x", "deny-log") + + # Repeat the keys with wildcard user spec + self.LookupPublish("uPlain1@COMPANY", "X", "MN.OP.Q", "allow-log") + self.LookupPublish("uStar1@COMPANY" , "X", "M.xx.N", "allow-log") + self.LookupPublish("uHash1@COMPANY" , "X", "M.N", "allow-log") + self.LookupPublish("uHash1@COMPANY" , "X", "M.x.N", "allow-log") + self.LookupPublish("uHash1@COMPANY" , "X", "M..x.y.zz.N", "allow-log") + self.LookupPublish("uMixed1@COMPANY", "X", "a.M.N", "allow-log") + self.LookupPublish("uMixed1@COMPANY", "X", "a.M.p.qq.N", "allow-log") + + self.LookupPublish("dev@QPID", "X", "MN.OP.Q", "allow-log") + self.LookupPublish("dev@QPID", "X", "M.xx.N", "allow-log") + self.LookupPublish("dev@QPID", "X", "M.N", "allow-log") + self.LookupPublish("dev@QPID", "X", "M.x.N", "allow-log") + self.LookupPublish("dev@QPID", "X", "M..x.y.zz.N", "allow-log") + self.LookupPublish("dev@QPID", "X", "a.M.N", "allow-log") + self.LookupPublish("dev@QPID", "X", "a.M.p.qq.N", "allow-log") + + def test_topic_exchange_other_tests(self): + """ + Test using QMF method hooks into ACL logic + """ + action_list = ['access','bind','unbind'] + + aclf = self.get_acl_file() + aclf.write('# begin hack alert: allow anonymous to access the lookup debug functions\n') + aclf.write('acl allow-log anonymous create queue\n') + aclf.write('acl allow-log anonymous all exchange name=qmf.*\n') + aclf.write('acl allow-log anonymous all exchange name=amq.direct\n') + aclf.write('acl allow-log anonymous all exchange name=qpid.management\n') + aclf.write('acl allow-log anonymous access method name=*\n') + aclf.write('# end hack alert\n') + for action in action_list: + aclf.write('acl allow-log uPlain1@COMPANY ' + action + ' exchange name=X routingkey=ab.cd.e\n') + aclf.write('acl allow-log uPlain2@COMPANY ' + action + ' exchange name=X routingkey=.\n') + aclf.write('acl allow-log uStar1@COMPANY ' + action + ' exchange name=X routingkey=a.*.b\n') + aclf.write('acl allow-log uStar2@COMPANY ' + action + ' exchange name=X routingkey=*.x\n') + aclf.write('acl allow-log uStar3@COMPANY ' + action + ' exchange name=X routingkey=x.x.*\n') + aclf.write('acl allow-log uHash1@COMPANY ' + action + ' exchange name=X routingkey=a.#.b\n') + aclf.write('acl allow-log uHash2@COMPANY ' + action + ' exchange name=X routingkey=a.#\n') + aclf.write('acl allow-log uHash3@COMPANY ' + action + ' exchange name=X routingkey=#.a\n') + aclf.write('acl allow-log uHash4@COMPANY ' + action + ' exchange name=X routingkey=a.#.b.#.c\n') + aclf.write('acl allow-log uMixed1@COMPANY ' + action + ' exchange name=X routingkey=*.x.#.y\n') + aclf.write('acl allow-log uMixed2@COMPANY ' + action + ' exchange name=X routingkey=a.#.b.*\n') + aclf.write('acl allow-log uMixed3@COMPANY ' + action + ' exchange name=X routingkey=*.*.*.#\n') + + aclf.write('acl allow-log all ' + action + ' exchange name=X routingkey=MN.OP.Q\n') + aclf.write('acl allow-log all ' + action + ' exchange name=X routingkey=M.*.N\n') + aclf.write('acl allow-log all ' + action + ' exchange name=X routingkey=M.#.N\n') + aclf.write('acl allow-log all ' + action + ' exchange name=X routingkey=*.M.#.N\n') + + aclf.write('acl deny-log all all\n') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + for action in action_list: + # aclKey: "ab.cd.e" + self.Lookup("uPlain1@COMPANY", action, "exchange", "X", {"routingkey":"ab.cd.e"}, "allow-log") + self.Lookup("uPlain1@COMPANY", action, "exchange", "X", {"routingkey":"ab.cd.e"}, "allow-log") + + self.Lookup("uPlain1@COMPANY", action, "exchange", "X", {"routingkey":"ab.cd.e"}, "allow-log") + self.Lookup("uPlain1@COMPANY", action, "exchange", "X", {"routingkey":"abx.cd.e"}, "deny-log") + self.Lookup("uPlain1@COMPANY", action, "exchange", "X", {"routingkey":"ab.cd"}, "deny-log") + self.Lookup("uPlain1@COMPANY", action, "exchange", "X", {"routingkey":"ab.cd..e."}, "deny-log") + self.Lookup("uPlain1@COMPANY", action, "exchange", "X", {"routingkey":"ab.cd.e."}, "deny-log") + self.Lookup("uPlain1@COMPANY", action, "exchange", "X", {"routingkey":".ab.cd.e"}, "deny-log") + # aclKey: "." + self.Lookup("uPlain2@COMPANY", action, "exchange", "X", {"routingkey":"."}, "allow-log") + + # aclKey: "a.*.b" + self.Lookup("uStar1@COMPANY", action, "exchange", "X", {"routingkey":"a.xx.b"}, "allow-log") + self.Lookup("uStar1@COMPANY", action, "exchange", "X", {"routingkey":"a.b"}, "deny-log") + # aclKey: "*.x" + self.Lookup("uStar2@COMPANY", action, "exchange", "X", {"routingkey":"y.x"}, "allow-log") + self.Lookup("uStar2@COMPANY", action, "exchange", "X", {"routingkey":".x"}, "allow-log") + self.Lookup("uStar2@COMPANY", action, "exchange", "X", {"routingkey":"x"}, "deny-log") + # aclKey: "x.x.*" + self.Lookup("uStar3@COMPANY", action, "exchange", "X", {"routingkey":"x.x.y"}, "allow-log") + self.Lookup("uStar3@COMPANY", action, "exchange", "X", {"routingkey":"x.x."}, "allow-log") + self.Lookup("uStar3@COMPANY", action, "exchange", "X", {"routingkey":"x.x"}, "deny-log") + self.Lookup("uStar3@COMPANY", action, "exchange", "X", {"routingkey":"q.x.y"}, "deny-log") + + # aclKey: "a.#.b" + self.Lookup("uHash1@COMPANY", action, "exchange", "X", {"routingkey":"a.b"}, "allow-log") + self.Lookup("uHash1@COMPANY", action, "exchange", "X", {"routingkey":"a.x.b"}, "allow-log") + self.Lookup("uHash1@COMPANY", action, "exchange", "X", {"routingkey":"a..x.y.zz.b"}, "allow-log") + self.Lookup("uHash1@COMPANY", action, "exchange", "X", {"routingkey":"a.b."}, "deny-log") + self.Lookup("uHash1@COMPANY", action, "exchange", "X", {"routingkey":"q.x.b"}, "deny-log") + + # aclKey: "a.#" + self.Lookup("uHash2@COMPANY", action, "exchange", "X", {"routingkey":"a"}, "allow-log") + self.Lookup("uHash2@COMPANY", action, "exchange", "X", {"routingkey":"a.b"}, "allow-log") + self.Lookup("uHash2@COMPANY", action, "exchange", "X", {"routingkey":"a.b.c"}, "allow-log") + + # aclKey: "#.a" + self.Lookup("uHash3@COMPANY", action, "exchange", "X", {"routingkey":"a"}, "allow-log") + self.Lookup("uHash3@COMPANY", action, "exchange", "X", {"routingkey":"x.y.a"}, "allow-log") + + # aclKey: "a.#.b.#.c" + self.Lookup("uHash4@COMPANY", action, "exchange", "X", {"routingkey":"a.b.c"}, "allow-log") + self.Lookup("uHash4@COMPANY", action, "exchange", "X", {"routingkey":"a.x.b.y.c"}, "allow-log") + self.Lookup("uHash4@COMPANY", action, "exchange", "X", {"routingkey":"a.x.x.b.y.y.c"}, "allow-log") + + # aclKey: "*.x.#.y" + self.Lookup("uMixed1@COMPANY", action, "exchange", "X", {"routingkey":"a.x.y"}, "allow-log") + self.Lookup("uMixed1@COMPANY", action, "exchange", "X", {"routingkey":"a.x.p.qq.y"}, "allow-log") + self.Lookup("uMixed1@COMPANY", action, "exchange", "X", {"routingkey":"a.a.x.y"}, "deny-log") + self.Lookup("uMixed1@COMPANY", action, "exchange", "X", {"routingkey":"aa.x.b.c"}, "deny-log") + + # aclKey: "a.#.b.*" + self.Lookup("uMixed2@COMPANY", action, "exchange", "X", {"routingkey":"a.b.x"}, "allow-log") + self.Lookup("uMixed2@COMPANY", action, "exchange", "X", {"routingkey":"a.x.x.x.b.x"}, "allow-log") + + # aclKey: "*.*.*.#" + self.Lookup("uMixed3@COMPANY", action, "exchange", "X", {"routingkey":"x.y.z"}, "allow-log") + self.Lookup("uMixed3@COMPANY", action, "exchange", "X", {"routingkey":"x.y.z.a.b.c"}, "allow-log") + self.Lookup("uMixed3@COMPANY", action, "exchange", "X", {"routingkey":"x.y"}, "deny-log") + self.Lookup("uMixed3@COMPANY", action, "exchange", "X", {"routingkey":"x"}, "deny-log") + + # Repeat the keys with wildcard user spec + self.Lookup("uPlain1@COMPANY", action, "exchange", "X", {"routingkey":"MN.OP.Q"}, "allow-log") + self.Lookup("uStar1@COMPANY" , action, "exchange", "X", {"routingkey":"M.xx.N"}, "allow-log") + self.Lookup("uHash1@COMPANY" , action, "exchange", "X", {"routingkey":"M.N"}, "allow-log") + self.Lookup("uHash1@COMPANY" , action, "exchange", "X", {"routingkey":"M.x.N"}, "allow-log") + self.Lookup("uHash1@COMPANY" , action, "exchange", "X", {"routingkey":"M..x.y.zz.N"}, "allow-log") + self.Lookup("uMixed1@COMPANY", action, "exchange", "X", {"routingkey":"a.M.N"}, "allow-log") + self.Lookup("uMixed1@COMPANY", action, "exchange", "X", {"routingkey":"a.M.p.qq.N"}, "allow-log") + + self.Lookup("dev@QPID", action, "exchange", "X", {"routingkey": "MN.OP.Q"}, "allow-log") + self.Lookup("dev@QPID", action, "exchange", "X", {"routingkey": "M.xx.N"}, "allow-log") + self.Lookup("dev@QPID", action, "exchange", "X", {"routingkey": "M.N"}, "allow-log") + self.Lookup("dev@QPID", action, "exchange", "X", {"routingkey": "M.x.N"}, "allow-log") + self.Lookup("dev@QPID", action, "exchange", "X", {"routingkey": "M..x.y.zz.N"}, "allow-log") + self.Lookup("dev@QPID", action, "exchange", "X", {"routingkey": "a.M.N"}, "allow-log") + self.Lookup("dev@QPID", action, "exchange", "X", {"routingkey": "a.M.p.qq.N"}, "allow-log") + + #===================================== + # Connection limits + #===================================== + + def test_connection_limits_cli_sets_all(self): + + try: + sessiona1 = self.get_session_by_port('alice','alice', self.port_u()) + sessiona2 = self.get_session_by_port('alice','alice', self.port_u()) + except Exception, e: + self.fail("Could not create two connections for user alice: " + str(e)) + + # Third session should fail + try: + sessiona3 = self.get_session_by_port('alice','alice', self.port_u()) + self.fail("Should not be able to create third connection for user alice") + except Exception, e: + result = None + + + + def test_connection_limits_by_named_user(self): + """ + Test ACL control connection limits + """ + aclf = self.get_acl_file() + aclf.write('quota connections 2 aliceCL@QPID bobCL@QPID\n') + aclf.write('quota connections 0 evildude@QPID\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + # By username should be able to connect twice per user + try: + conna1 = self.get_connection('aliceCL','aliceCL') + conna2 = self.get_connection('aliceCL','aliceCL') + except Exception, e: + self.fail("Could not create two connections for user aliceCL: " + str(e)) + + # Third session should fail + try: + conna3 = self.get_connection('aliceCL','aliceCL') + self.fail("Should not be able to create third connection for user aliceCL") + except Exception, e: + result = None + + # Disconnecting should allow another session. + conna1.close() + try: + conna3 = self.get_connection('aliceCL','aliceCL') + except Exception, e: + self.fail("Could not recreate second connection for user aliceCL: " + str(e)) + + # By username should be able to connect twice per user + try: + connb1 = self.get_connection('bobCL','bobCL') + connb2 = self.get_connection('bobCL','bobCL') + except Exception, e: + self.fail("Could not create two connections for user bobCL: " + str(e)) + + # Third session should fail + try: + connb3 = self.get_connection('bobCL','bobCL') + self.fail("Should not be able to create third connection for user bobCL") + except Exception, e: + result = None + + + # User with quota of 0 is denied + try: + conne1 = self.get_connection('evildude','evildude') + self.fail("Should not be able to create a connection for user evildude") + except Exception, e: + result = None + + + # User not named in quotas is denied + try: + connc1 = self.get_connection('charlie','charlie') + self.fail("Should not be able to create a connection for user charlie") + except Exception, e: + result = None + + # Clean up the connections + conna2.close() + conna3.close() + connb1.close() + connb2.close() + + + + def test_connection_limits_by_unnamed_all(self): + """ + Test ACL control connection limits + """ + aclf = self.get_acl_file() + aclf.write('quota connections 2 aliceUA@QPID bobUA@QPID\n') + aclf.write('quota connections 1 all\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + # By username should be able to connect twice per user + try: + connectiona1 = self.get_connection('aliceUA','alice') + connectiona2 = self.get_connection('aliceUA','alice') + except Exception, e: + self.fail("Could not create two connections for user alice: " + str(e)) + + # Third connection should fail + try: + connectiona3 = self.get_connection('aliceUA','alice') + self.fail("Should not be able to create third connection for user alice") + except Exception, e: + result = None + + # By username should be able to connect twice per user + try: + connectionb1 = self.get_connection('bobUA','bob') + connectionb2 = self.get_connection('bobUA','bob') + except Exception, e: + self.fail("Could not create two connections for user bob: " + str(e)) + + # Third connection should fail + try: + connectionb3 = self.get_connection('bobUA','bob') + self.fail("Should not be able to create third connection for user bob") + except Exception, e: + result = None + + # User not named in quotas gets 'all' quota + try: + connectionc1 = self.get_connection('charlieUA','charlie') + except Exception, e: + self.fail("Could not create one connection for user charlie: " + str(e)) + + # Next connection should fail + try: + connectionc2 = self.get_connection('charlieUA','charlie') + self.fail("Should not be able to create second connection for user charlie") + except Exception, e: + result = None + + # Clean up the connections + connectiona1.close() + connectiona2.close() + connectionb1.close() + connectionb2.close() + connectionc1.close() + + + def test_connection_limits_by_group(self): + """ + Test ACL control connection limits + """ + aclf = self.get_acl_file() + aclf.write('group stooges moeGR@QPID larryGR@QPID curlyGR@QPID\n') + aclf.write('quota connections 2 aliceGR@QPID bobGR@QPID\n') + aclf.write('quota connections 2 stooges charlieGR@QPID\n') + aclf.write('# user and groups may be overwritten. Should use last value\n') + aclf.write('quota connections 3 bobGR@QPID stooges\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + # Alice gets 2 + try: + connectiona1 = self.get_connection('aliceGR','alice') + connectiona2 = self.get_connection('aliceGR','alice') + except Exception, e: + self.fail("Could not create two connections for user alice: " + str(e)) + + # Third connection should fail + try: + connectiona3 = self.get_connection('aliceGR','alice') + self.fail("Should not be able to create third connection for user alice") + except Exception, e: + result = None + + # Bob gets 3 + try: + connectionb1 = self.get_connection('bobGR','bob') + connectionb2 = self.get_connection('bobGR','bob') + connectionb3 = self.get_connection('bobGR','bob') + except Exception, e: + self.fail("Could not create three connections for user bob: " + str(e)) + + # Fourth connection should fail + try: + connectionb4 = self.get_connection('bobGR','bob') + self.fail("Should not be able to create fourth connection for user bob") + except Exception, e: + result = None + + # Moe gets 3 + try: + connectionm1 = self.get_connection('moeGR','moe') + connectionm2 = self.get_connection('moeGR','moe') + connectionm3 = self.get_connection('moeGR','moe') + except Exception, e: + self.fail("Could not create three connections for user moe: " + str(e)) + + # Fourth connection should fail + try: + connectionb4 = self.get_connection('moeGR','moe') + self.fail("Should not be able to create fourth connection for user ,pe") + except Exception, e: + result = None + + # User not named in quotas is denied + try: + connections1 = self.get_connection('shempGR','shemp') + self.fail("Should not be able to create a connection for user shemp") + except Exception, e: + result = None + + # Clean up the connections + connectiona1.close() + connectiona2.close() + connectionb1.close() + connectionb2.close() + connectionb3.close() + connectionm1.close() + connectionm2.close() + connectionm3.close() + + + def test_connection_limits_by_ip_address(self): + """ + Test ACL control connection limits by ip address + """ + # By IP address should be able to connect twice per client address + try: + sessionb1 = self.get_session_by_port('alice','alice', self.port_i()) + sessionb2 = self.get_session_by_port('bob','bob', self.port_i()) + except Exception, e: + self.fail("Could not create two connections for client address: " + str(e)) + + # Third session should fail + try: + sessionb3 = self.get_session_by_port('charlie','charlie', self.port_i()) + self.fail("Should not be able to create third connection for client address") + except Exception, e: + result = None + + sessionb1.close() + sessionb2.close() + + #===================================== + # User name substitution + #===================================== + + def test_user_name_substitution(self): + """ + Test name substitution internals, limits, and edge cases. + """ + aclf = self.get_acl_file() + aclf.write('# begin hack alert: allow anonymous to access the lookup debug functions\n') + aclf.write('acl allow-log anonymous create queue\n') + aclf.write('acl allow-log anonymous all exchange name=qmf.*\n') + aclf.write('acl allow-log anonymous all exchange name=amq.direct\n') + aclf.write('acl allow-log anonymous all exchange name=qpid.management\n') + aclf.write('acl allow-log anonymous access method name=*\n') + aclf.write('# end hack alert\n') + aclf.write('acl allow all create queue name=tmp-${userdomain}\n') + aclf.write('acl allow all create queue name=${userdomain}-tmp\n') + aclf.write('acl allow all create queue name=tmp-${userdomain}-tmp\n') + aclf.write('acl allow all create queue name=tmp-${userdomain}-tmp-${userdomain}\n') + aclf.write('acl allow all create queue name=temp0-${userdomain}\n') + aclf.write('acl allow all access queue name=temp0-${userdomain}\n') + aclf.write('acl allow all purge queue name=temp0-${userdomain}\n') + aclf.write('acl allow all consume queue name=temp0-${userdomain}\n') + aclf.write('acl allow all delete queue name=temp0-${userdomain}\n') + aclf.write('acl allow all create exchange name=temp0-${userdomain}\n') + aclf.write('acl allow all access exchange name=temp0-${userdomain}\n') + aclf.write('acl allow all bind exchange name=temp0-${userdomain}\n') + aclf.write('acl allow all unbind exchange name=temp0-${userdomain}\n') + aclf.write('acl allow all delete exchange name=temp0-${userdomain}\n') + aclf.write('acl allow all publish exchange name=temp0-${userdomain}\n') + + aclf.write('acl allow all publish exchange name=X routingkey=${userdomain}.cd.e\n') + aclf.write('acl allow all publish exchange name=X routingkey=a.*.${userdomain}\n') + aclf.write('acl allow all publish exchange name=X routingkey=b.#.${userdomain}\n') + aclf.write('acl allow all publish exchange name=X routingkey=*.${userdomain}.#.y\n') + + aclf.write('acl allow all create queue name=user-${user}\n') + aclf.write('acl allow all publish exchange name=U routingkey=${user}.cd.e\n') + aclf.write('acl allow all publish exchange name=U routingkey=a.*.${user}\n') + aclf.write('acl allow all publish exchange name=U routingkey=b.#.${user}\n') + aclf.write('acl allow all publish exchange name=U routingkey=*.${user}.#.y\n') + + aclf.write('acl allow all create queue name=domain-${domain}\n') + aclf.write('acl allow all publish exchange name=D routingkey=${domain}.cd.e\n') + aclf.write('acl allow all publish exchange name=D routingkey=a.*.${domain}\n') + aclf.write('acl allow all publish exchange name=D routingkey=b.#.${domain}\n') + aclf.write('acl allow all publish exchange name=D routingkey=*.${domain}.#.y\n') + + # Resolving ${user}_${domain} into ${userdomain} works for everything but routing keys + aclf.write('acl allow all create queue name=mixed-OK-${user}_${domain}\n') + # For routing keys ${user}_${domain} will be parsed into ${userdomain}. + # Routing keys not be found when the rule specifies ${user}_${domain}. + aclf.write('acl allow all publish exchange name=NOGO routingkey=${user}_${domain}.cd.e\n') + # This works since it is does not conflict with ${userdomain} + aclf.write('acl allow all publish exchange name=OK routingkey=${user}___${domain}.cd.e\n') + + aclf.write('acl deny-log all all\n') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + self.Lookup("alice@QPID", "create", "queue", "tmp-alice_QPID", {}, "allow") + self.Lookup("bob@QPID", "create", "queue", "bob_QPID-tmp", {}, "allow") + self.Lookup("charlie@QPID", "create", "queue", "tmp-charlie_QPID-tmp", {}, "allow") + self.Lookup("dave@QPID", "create", "queue", "tmp-dave_QPID-tmp-dave_QPID", {}, "allow") + self.Lookup("ed@BIG.COM", "create", "queue", "tmp-ed_BIG_COM", {}, "allow") + self.Lookup("c.e.r@BIG.GER.COM", "create", "queue", "tmp-c_e_r_BIG_GER_COM", {}, "allow") + self.Lookup("c@", "create", "queue", "tmp-c_", {}, "allow") + self.Lookup("someuser", "create", "queue", "tmp-someuser", {}, "allow") + + self.Lookup("alice@QPID", "create", "queue", "tmp-${user}", {}, "deny-log") + + self.Lookup("bob@QPID", "create", "exchange", "temp0-bob_QPID", {}, "allow") + self.Lookup("bob@QPID", "access", "exchange", "temp0-bob_QPID", {}, "allow") + self.Lookup("bob@QPID", "bind", "exchange", "temp0-bob_QPID", {}, "allow") + self.Lookup("bob@QPID", "unbind", "exchange", "temp0-bob_QPID", {}, "allow") + self.Lookup("bob@QPID", "delete", "exchange", "temp0-bob_QPID", {}, "allow") + self.LookupPublish("bob@QPID", "temp0-bob_QPID", "x", "allow") + + self.Lookup("bob@QPID", "create", "queue", "temp0-bob_QPID", {}, "allow") + self.Lookup("bob@QPID", "access", "queue", "temp0-bob_QPID", {}, "allow") + self.Lookup("bob@QPID", "purge", "queue", "temp0-bob_QPID", {}, "allow") + self.Lookup("bob@QPID", "consume", "queue", "temp0-bob_QPID", {}, "allow") + self.Lookup("bob@QPID", "delete", "queue", "temp0-bob_QPID", {}, "allow") + + self.Lookup("alice@QPID", "access", "queue", "temp0-bob_QPID", {}, "deny-log") + + # aclKey: "${userdomain}.cd.e" + self.LookupPublish("uPlain1@COMPANY", "X", "uPlain1_COMPANY.cd.e", "allow") + # aclKey: "a.*.${userdomain}" + self.LookupPublish("uStar1@COMPANY", "X", "a.xx.uStar1_COMPANY", "allow") + self.LookupPublish("uStar1@COMPANY", "X", "a.b", "deny-log") + # aclKey: "b.#.${userdomain}" + self.LookupPublish("uHash1@COMPANY", "X", "b.uHash1_COMPANY", "allow") + self.LookupPublish("uHash1@COMPANY", "X", "b.x.uHash1_COMPANY", "allow") + self.LookupPublish("uHash1@COMPANY", "X", "b..x.y.zz.uHash1_COMPANY", "allow") + self.LookupPublish("uHash1@COMPANY", "X", "b.uHash1_COMPANY.", "deny-log") + self.LookupPublish("uHash1@COMPANY", "X", "q.x.uHash1_COMPANY", "deny-log") + # aclKey: "*.${userdomain}.#.y" + self.LookupPublish("uMixed1@COMPANY", "X", "a.uMixed1_COMPANY.y", "allow") + self.LookupPublish("uMixed1@COMPANY", "X", "a.uMixed1_COMPANY.p.qq.y", "allow") + self.LookupPublish("uMixed1@COMPANY", "X", "a.a.uMixed1_COMPANY.y", "deny-log") + self.LookupPublish("uMixed1@COMPANY", "X", "aa.uMixed1_COMPANY.b.c", "deny-log") + self.LookupPublish("uMixed1@COMPANY.COM", "X", "a.uMixed1_COMPANY_COM.y", "allow") + + + self.Lookup("bob@QPID", "create", "queue", "user-bob", {}, "allow") + # aclKey: "${user}.cd.e" + self.LookupPublish("uPlain1@COMPANY", "U", "uPlain1.cd.e", "allow") + # aclKey: "a.*.${user}" + self.LookupPublish("uStar1@COMPANY", "U", "a.xx.uStar1", "allow") + self.LookupPublish("uStar1@COMPANY", "U", "a.b", "deny-log") + # aclKey: "b.#.${user}" + self.LookupPublish("uHash1@COMPANY", "U", "b.uHash1", "allow") + self.LookupPublish("uHash1@COMPANY", "U", "b.x.uHash1", "allow") + self.LookupPublish("uHash1@COMPANY", "U", "b..x.y.zz.uHash1", "allow") + self.LookupPublish("uHash1@COMPANY", "U", "b.uHash1.", "deny-log") + self.LookupPublish("uHash1@COMPANY", "U", "q.x.uHash1", "deny-log") + # aclKey: "*.${user}.#.y" + self.LookupPublish("uMixed1@COMPANY", "U", "a.uMixed1.y", "allow") + self.LookupPublish("uMixed1@COMPANY", "U", "a.uMixed1.p.qq.y", "allow") + self.LookupPublish("uMixed1@COMPANY", "U", "a.a.uMixed1.y", "deny-log") + self.LookupPublish("uMixed1@COMPANY", "U", "aa.uMixed1.b.c", "deny-log") + self.LookupPublish("uMixed1@COMPANY.COM", "U", "a.uMixed1.y", "allow") + + + self.Lookup("bob@QPID", "create", "queue", "domain-QPID", {}, "allow") + # aclKey: "${domain}.cd.e" + self.LookupPublish("uPlain1@COMPANY", "D", "COMPANY.cd.e", "allow") + # aclKey: "a.*.${domain}" + self.LookupPublish("uStar1@COMPANY", "D", "a.xx.COMPANY", "allow") + self.LookupPublish("uStar1@COMPANY", "D", "a.b", "deny-log") + # aclKey: "b.#.${domain}" + self.LookupPublish("uHash1@COMPANY", "D", "b.COMPANY", "allow") + self.LookupPublish("uHash1@COMPANY", "D", "b.x.COMPANY", "allow") + self.LookupPublish("uHash1@COMPANY", "D", "b..x.y.zz.COMPANY", "allow") + self.LookupPublish("uHash1@COMPANY", "D", "b.COMPANY.", "deny-log") + self.LookupPublish("uHash1@COMPANY", "D", "q.x.COMPANY", "deny-log") + # aclKey: "*.${domain}.#.y" + self.LookupPublish("uMixed1@COMPANY", "D", "a.COMPANY.y", "allow") + self.LookupPublish("uMixed1@COMPANY", "D", "a.COMPANY.p.qq.y", "allow") + self.LookupPublish("uMixed1@COMPANY", "D", "a.a.COMPANY.y", "deny-log") + self.LookupPublish("uMixed1@COMPANY", "D", "aa.COMPANY.b.c", "deny-log") + self.LookupPublish("uMixed1@COMPANY.COM", "D", "a.COMPANY_COM.y", "allow") + + self.Lookup("uPlain1@COMPANY", "create", "queue", "mixed-OK-uPlain1_COMPANY", {}, "allow") + self.LookupPublish("uPlain1@COMPANY", "NOGO", "uPlain1_COMPANY.cd.e", "deny-log") + self.LookupPublish("uPlain1@COMPANY", "OK", "uPlain1___COMPANY.cd.e", "allow") + + + #===================================== + # User name substitution details + #===================================== + # User name substitution allows for three flavors of keyword in the Acl file. + # Given a user name of bob.user@QPID.COM the keywords are normalized and resolve as follows: + # ${userdomain} - bob_user_QPID_COM + # ${user} - bob_user + # ${domain} - QPID_COM + # + # The following substitution tests are very similar but differ in the flavor of keyword used + # in the rules. The tests results using the different keywords differ slightly in how permissive + # the rules become. + # ${userdomain} limits access to one authenticated user + # ${user} limits access to a user name regardless of user's domain + # ${domain} limits access to a domain regardless of user name + # + + def test_user_name_substitution_userdomain(self): + """ + Test a setup where users can create, bind, and publish to a main exchange and queue. + Allow access to a single alternate exchange and queue. + """ + aclf = self.get_acl_file() + aclf.write('# begin hack alert: allow anonymous to access the lookup debug functions\n') + aclf.write('acl allow-log anonymous create queue\n') + aclf.write('acl allow-log anonymous all exchange name=qmf.*\n') + aclf.write('acl allow-log anonymous all exchange name=amq.direct\n') + aclf.write('acl allow-log anonymous all exchange name=qpid.management\n') + aclf.write('acl allow-log anonymous access method name=*\n') + aclf.write('# end hack alert\n') + # Create primary queue and exchange: + # allow predefined alternate + # deny any other alternate + # allow no alternate + aclf.write('acl allow all create queue name=${userdomain}-work alternate=${userdomain}-work2\n') + aclf.write('acl deny all create queue name=${userdomain}-work alternate=*\n') + aclf.write('acl allow all create queue name=${userdomain}-work\n') + aclf.write('acl allow all create exchange name=${userdomain}-work alternate=${userdomain}-work2\n') + aclf.write('acl deny all create exchange name=${userdomain}-work alternate=*\n') + aclf.write('acl allow all create exchange name=${userdomain}-work\n') + # Create backup queue and exchange + # Deny any alternate + aclf.write('acl deny all create queue name=${userdomain}-work2 alternate=*\n') + aclf.write('acl allow all create queue name=${userdomain}-work2\n') + aclf.write('acl deny all create exchange name=${userdomain}-work2 alternate=*\n') + aclf.write('acl allow all create exchange name=${userdomain}-work2\n') + # Bind/unbind primary exchange + # Use only predefined routingkey and queuename + aclf.write('acl allow all bind exchange name=${userdomain}-work routingkey=${userdomain} queuename=${userdomain}-work\n') + aclf.write('acl allow all unbind exchange name=${userdomain}-work routingkey=${userdomain} queuename=${userdomain}-work\n') + # Bind/unbind backup exchange + # Use only predefined routingkey and queuename + aclf.write('acl allow all bind exchange name=${userdomain}-work2 routingkey=${userdomain} queuename=${userdomain}-work2\n') + aclf.write('acl allow all unbind exchange name=${userdomain}-work2 routingkey=${userdomain} queuename=${userdomain}-work2\n') + # Access primary exchange + # Use only predefined routingkey and queuename + aclf.write('acl allow all access exchange name=${userdomain}-work routingkey=${userdomain} queuename=${userdomain}-work\n') + # Access backup exchange + # Use only predefined routingkey and queuename + aclf.write('acl allow all access exchange name=${userdomain}-work2 routingkey=${userdomain} queuename=${userdomain}-work2\n') + # Publish primary exchange + # Use only predefined routingkey + aclf.write('acl allow all publish exchange name=${userdomain}-work routingkey=${userdomain}\n') + # Publish backup exchange + # Use only predefined routingkey + aclf.write('acl allow all publish exchange name=${userdomain}-work2 routingkey=${userdomain}\n') + # deny mode + aclf.write('acl deny all all\n') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + # create queues + self.Lookup("bob@QPID", "create", "queue", "bob_QPID-work", {}, "allow") + self.Lookup("bob@QPID", "create", "queue", "bob_QPID-work2", {}, "allow") + self.Lookup("bob@QPID", "create", "queue", "joe_QPID-work", {}, "deny") + self.Lookup("bob@QPID", "create", "queue", "joe_QPID-work2", {}, "deny") + self.Lookup("bob@QPID", "create", "queue", "bob_QPID-work3", {}, "deny") + self.Lookup("bob@QPID", "create", "queue", "bob_QPID-work", {"alternate":"bob_QPID-work2"}, "allow") + self.Lookup("bob@QPID", "create", "queue", "bob_QPID-work", {"alternate":"joe_QPID-work2"}, "deny") + self.Lookup("bob@QPID", "create", "queue", "bob_QPID-work2", {"alternate":"someexchange"}, "deny") + # create exchanges + self.Lookup("bob@QPID", "create", "exchange", "bob_QPID-work", {}, "allow") + self.Lookup("bob@QPID", "create", "exchange", "bob_QPID-work2",{}, "allow") + self.Lookup("bob@QPID", "create", "exchange", "joe_QPID-work", {}, "deny") + self.Lookup("bob@QPID", "create", "exchange", "joe_QPID-work2",{}, "deny") + self.Lookup("bob@QPID", "create", "exchange", "bob_QPID-work3",{}, "deny") + self.Lookup("bob@QPID", "create", "exchange", "bob_QPID-work", {"alternate":"bob_QPID-work2"}, "allow") + self.Lookup("bob@QPID", "create", "exchange", "bob_QPID-work2",{"alternate":"someexchange"}, "deny") + # bind/unbind/access + self.Lookup("bob@QPID", "bind", "exchange", "bob_QPID-work", {}, "deny") + self.Lookup("bob@QPID", "bind", "exchange", "bob_QPID-work", {"routingkey":"bob_QPID"}, "deny") + self.Lookup("bob@QPID", "bind", "exchange", "bob_QPID-work", { "queuename":"bob_QPID-work"}, "deny") + self.Lookup("bob@QPID", "bind", "exchange", "bob_QPID-work", {"routingkey":"bob_QPID", "queuename":"bob_QPID-work"}, "allow") + self.Lookup("bob@QPID", "bind", "exchange", "joe_QPID-work", {"routingkey":"bob_QPID", "queuename":"bob_QPID-work"}, "deny") + self.Lookup("bob@QPID", "bind", "exchange", "bob_QPID-work", {"routingkey":"joe_QPID", "queuename":"bob_QPID-work"}, "deny") + self.Lookup("bob@QPID", "bind", "exchange", "bob_QPID-work", {"routingkey":"bob_QPID", "queuename":"joe_QPID-work"}, "deny") + + self.Lookup("bob@QPID", "bind", "exchange", "bob_QPID-work2", {}, "deny") + self.Lookup("bob@QPID", "bind", "exchange", "bob_QPID-work2", {"routingkey":"bob_QPID"}, "deny") + self.Lookup("bob@QPID", "bind", "exchange", "bob_QPID-work2", { "queuename":"bob_QPID-work2"}, "deny") + self.Lookup("bob@QPID", "bind", "exchange", "bob_QPID-work2", {"routingkey":"bob_QPID", "queuename":"bob_QPID-work2"}, "allow") + self.Lookup("bob@QPID", "bind", "exchange", "joe_QPID-work2", {"routingkey":"bob_QPID", "queuename":"bob_QPID-work2"}, "deny") + self.Lookup("bob@QPID", "bind", "exchange", "bob_QPID-work2", {"routingkey":"joe_QPID", "queuename":"bob_QPID-work2"}, "deny") + self.Lookup("bob@QPID", "bind", "exchange", "bob_QPID-work2", {"routingkey":"bob_QPID", "queuename":"joe_QPID-work2"}, "deny") + + self.Lookup("bob@QPID", "unbind", "exchange", "bob_QPID-work", {}, "deny") + self.Lookup("bob@QPID", "unbind", "exchange", "bob_QPID-work", {"routingkey":"bob_QPID"}, "deny") + self.Lookup("bob@QPID", "unbind", "exchange", "bob_QPID-work", { "queuename":"bob_QPID-work"}, "deny") + self.Lookup("bob@QPID", "unbind", "exchange", "bob_QPID-work", {"routingkey":"bob_QPID", "queuename":"bob_QPID-work"}, "allow") + self.Lookup("bob@QPID", "unbind", "exchange", "joe_QPID-work", {"routingkey":"bob_QPID", "queuename":"bob_QPID-work"}, "deny") + self.Lookup("bob@QPID", "unbind", "exchange", "bob_QPID-work", {"routingkey":"joe_QPID", "queuename":"bob_QPID-work"}, "deny") + self.Lookup("bob@QPID", "unbind", "exchange", "bob_QPID-work", {"routingkey":"bob_QPID", "queuename":"joe_QPID-work"}, "deny") + + self.Lookup("bob@QPID", "unbind", "exchange", "bob_QPID-work2", {}, "deny") + self.Lookup("bob@QPID", "unbind", "exchange", "bob_QPID-work2", {"routingkey":"bob_QPID"}, "deny") + self.Lookup("bob@QPID", "unbind", "exchange", "bob_QPID-work2", { "queuename":"bob_QPID-work2"}, "deny") + self.Lookup("bob@QPID", "unbind", "exchange", "bob_QPID-work2", {"routingkey":"bob_QPID", "queuename":"bob_QPID-work2"}, "allow") + self.Lookup("bob@QPID", "unbind", "exchange", "joe_QPID-work2", {"routingkey":"bob_QPID", "queuename":"bob_QPID-work2"}, "deny") + self.Lookup("bob@QPID", "unbind", "exchange", "bob_QPID-work2", {"routingkey":"joe_QPID", "queuename":"bob_QPID-work2"}, "deny") + self.Lookup("bob@QPID", "unbind", "exchange", "bob_QPID-work2", {"routingkey":"bob_QPID", "queuename":"joe_QPID-work2"}, "deny") + + self.Lookup("bob@QPID", "access", "exchange", "bob_QPID-work", {}, "deny") + self.Lookup("bob@QPID", "access", "exchange", "bob_QPID-work", {"routingkey":"bob_QPID"}, "deny") + self.Lookup("bob@QPID", "access", "exchange", "bob_QPID-work", { "queuename":"bob_QPID-work"}, "deny") + self.Lookup("bob@QPID", "access", "exchange", "bob_QPID-work", {"routingkey":"bob_QPID", "queuename":"bob_QPID-work"}, "allow") + self.Lookup("bob@QPID", "access", "exchange", "joe_QPID-work", {"routingkey":"bob_QPID", "queuename":"bob_QPID-work"}, "deny") + self.Lookup("bob@QPID", "access", "exchange", "bob_QPID-work", {"routingkey":"joe_QPID", "queuename":"bob_QPID-work"}, "deny") + self.Lookup("bob@QPID", "access", "exchange", "bob_QPID-work", {"routingkey":"bob_QPID", "queuename":"joe_QPID-work"}, "deny") + + self.Lookup("bob@QPID", "access", "exchange", "bob_QPID-work2", {}, "deny") + self.Lookup("bob@QPID", "access", "exchange", "bob_QPID-work2", {"routingkey":"bob_QPID"}, "deny") + self.Lookup("bob@QPID", "access", "exchange", "bob_QPID-work2", { "queuename":"bob_QPID-work2"}, "deny") + self.Lookup("bob@QPID", "access", "exchange", "bob_QPID-work2", {"routingkey":"bob_QPID", "queuename":"bob_QPID-work2"}, "allow") + self.Lookup("bob@QPID", "access", "exchange", "joe_QPID-work2", {"routingkey":"bob_QPID", "queuename":"bob_QPID-work2"}, "deny") + self.Lookup("bob@QPID", "access", "exchange", "bob_QPID-work2", {"routingkey":"joe_QPID", "queuename":"bob_QPID-work2"}, "deny") + self.Lookup("bob@QPID", "access", "exchange", "bob_QPID-work2", {"routingkey":"bob_QPID", "queuename":"joe_QPID-work2"}, "deny") + # publish + self.LookupPublish("bob@QPID", "bob_QPID-work", "bob_QPID", "allow") + self.LookupPublish("bob@QPID", "bob_QPID-work2", "bob_QPID", "allow") + self.LookupPublish("bob@QPID", "joe_QPID-work", "bob_QPID", "deny") + self.LookupPublish("bob@QPID", "joe_QPID-work2", "bob_QPID", "deny") + self.LookupPublish("bob@QPID", "bob_QPID-work", "joe_QPID", "deny") + self.LookupPublish("bob@QPID", "bob_QPID-work2", "joe_QPID", "deny") + + + def test_user_name_substitution_user(self): + """ + Test a setup where users can create, bind, and publish to a main exchange and queue. + Allow access to a single backup exchange and queue. + """ + aclf = self.get_acl_file() + aclf.write('# begin hack alert: allow anonymous to access the lookup debug functions\n') + aclf.write('acl allow-log anonymous create queue\n') + aclf.write('acl allow-log anonymous all exchange name=qmf.*\n') + aclf.write('acl allow-log anonymous all exchange name=amq.direct\n') + aclf.write('acl allow-log anonymous all exchange name=qpid.management\n') + aclf.write('acl allow-log anonymous access method name=*\n') + aclf.write('# end hack alert\n') + # Create primary queue and exchange + # allow predefined alternate + # deny any other alternate + # allow no alternate + aclf.write('acl allow all create queue name=${user}-work alternate=${user}-work2\n') + aclf.write('acl deny all create queue name=${user}-work alternate=*\n') + aclf.write('acl allow all create queue name=${user}-work\n') + aclf.write('acl allow all create exchange name=${user}-work alternate=${user}-work2\n') + aclf.write('acl deny all create exchange name=${user}-work alternate=*\n') + aclf.write('acl allow all create exchange name=${user}-work\n') + # Create backup queue and exchange + # Deny any alternate + aclf.write('acl deny all create queue name=${user}-work2 alternate=*\n') + aclf.write('acl allow all create queue name=${user}-work2\n') + aclf.write('acl deny all create exchange name=${user}-work2 alternate=*\n') + aclf.write('acl allow all create exchange name=${user}-work2\n') + # Bind/unbind primary exchange + # Use only predefined routingkey and queuename + aclf.write('acl allow all bind exchange name=${user}-work routingkey=${user} queuename=${user}-work\n') + aclf.write('acl allow all unbind exchange name=${user}-work routingkey=${user} queuename=${user}-work\n') + # Bind/unbind backup exchange + # Use only predefined routingkey and queuename + aclf.write('acl allow all bind exchange name=${user}-work2 routingkey=${user} queuename=${user}-work2\n') + aclf.write('acl allow all unbind exchange name=${user}-work2 routingkey=${user} queuename=${user}-work2\n') + # Access primary exchange + # Use only predefined routingkey and queuename + aclf.write('acl allow all access exchange name=${user}-work routingkey=${user} queuename=${user}-work\n') + # Access backup exchange + # Use only predefined routingkey and queuename + aclf.write('acl allow all access exchange name=${user}-work2 routingkey=${user} queuename=${user}-work2\n') + # Publish primary exchange + # Use only predefined routingkey + aclf.write('acl allow all publish exchange name=${user}-work routingkey=${user}\n') + # Publish backup exchange + # Use only predefined routingkey + aclf.write('acl allow all publish exchange name=${user}-work2 routingkey=${user}\n') + # deny mode + aclf.write('acl deny all all\n') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + # create queues + self.Lookup("bob@QPID", "create", "queue", "bob-work", {}, "allow") + self.Lookup("bob@QPID", "create", "queue", "bob-work2", {}, "allow") + self.Lookup("bob@QPID", "create", "queue", "joe-work", {}, "deny") + self.Lookup("bob@QPID", "create", "queue", "joe-work2", {}, "deny") + self.Lookup("bob@QPID", "create", "queue", "bob-work3", {}, "deny") + self.Lookup("bob@QPID", "create", "queue", "bob-work", {"alternate":"bob-work2"}, "allow") + self.Lookup("bob@QPID", "create", "queue", "bob-work", {"alternate":"joe-work2"}, "deny") + self.Lookup("bob@QPID", "create", "queue", "bob-work2", {"alternate":"someexchange"},"deny") + # create exchanges + self.Lookup("bob@QPID", "create", "exchange", "bob-work", {}, "allow") + self.Lookup("bob@QPID", "create", "exchange", "bob-work2",{}, "allow") + self.Lookup("bob@QPID", "create", "exchange", "joe-work", {}, "deny") + self.Lookup("bob@QPID", "create", "exchange", "joe-work2",{}, "deny") + self.Lookup("bob@QPID", "create", "exchange", "bob-work3",{}, "deny") + self.Lookup("bob@QPID", "create", "exchange", "bob-work", {"alternate":"bob-work2"}, "allow") + self.Lookup("bob@QPID", "create", "exchange", "bob-work2",{"alternate":"someexchange"},"deny") + # bind/unbind/access + self.Lookup("bob@QPID", "bind", "exchange", "bob-work", {}, "deny") + self.Lookup("bob@QPID", "bind", "exchange", "bob-work", {"routingkey":"bob"}, "deny") + self.Lookup("bob@QPID", "bind", "exchange", "bob-work", { "queuename":"bob-work"}, "deny") + self.Lookup("bob@QPID", "bind", "exchange", "bob-work", {"routingkey":"bob", "queuename":"bob-work"}, "allow") + self.Lookup("bob@QPID", "bind", "exchange", "joe-work", {"routingkey":"bob", "queuename":"bob-work"}, "deny") + self.Lookup("bob@QPID", "bind", "exchange", "bob-work", {"routingkey":"joe", "queuename":"bob-work"}, "deny") + self.Lookup("bob@QPID", "bind", "exchange", "bob-work", {"routingkey":"bob", "queuename":"joe-work"}, "deny") + + self.Lookup("bob@QPID", "bind", "exchange", "bob-work2", {}, "deny") + self.Lookup("bob@QPID", "bind", "exchange", "bob-work2", {"routingkey":"bob"}, "deny") + self.Lookup("bob@QPID", "bind", "exchange", "bob-work2", { "queuename":"bob-work2"}, "deny") + self.Lookup("bob@QPID", "bind", "exchange", "bob-work2", {"routingkey":"bob", "queuename":"bob-work2"}, "allow") + self.Lookup("bob@QPID", "bind", "exchange", "joe-work2", {"routingkey":"bob", "queuename":"bob-work2"}, "deny") + self.Lookup("bob@QPID", "bind", "exchange", "bob-work2", {"routingkey":"joe", "queuename":"bob-work2"}, "deny") + self.Lookup("bob@QPID", "bind", "exchange", "bob-work2", {"routingkey":"bob", "queuename":"joe-work2"}, "deny") + + self.Lookup("bob@QPID", "unbind", "exchange", "bob-work", {}, "deny") + self.Lookup("bob@QPID", "unbind", "exchange", "bob-work", {"routingkey":"bob"}, "deny") + self.Lookup("bob@QPID", "unbind", "exchange", "bob-work", { "queuename":"bob-work"}, "deny") + self.Lookup("bob@QPID", "unbind", "exchange", "bob-work", {"routingkey":"bob", "queuename":"bob-work"}, "allow") + self.Lookup("bob@QPID", "unbind", "exchange", "joe-work", {"routingkey":"bob", "queuename":"bob-work"}, "deny") + self.Lookup("bob@QPID", "unbind", "exchange", "bob-work", {"routingkey":"joe", "queuename":"bob-work"}, "deny") + self.Lookup("bob@QPID", "unbind", "exchange", "bob-work", {"routingkey":"bob", "queuename":"joe-work"}, "deny") + + self.Lookup("bob@QPID", "unbind", "exchange", "bob-work2", {}, "deny") + self.Lookup("bob@QPID", "unbind", "exchange", "bob-work2", {"routingkey":"bob"}, "deny") + self.Lookup("bob@QPID", "unbind", "exchange", "bob-work2", { "queuename":"bob-work2"}, "deny") + self.Lookup("bob@QPID", "unbind", "exchange", "bob-work2", {"routingkey":"bob", "queuename":"bob-work2"}, "allow") + self.Lookup("bob@QPID", "unbind", "exchange", "joe-work2", {"routingkey":"bob", "queuename":"bob-work2"}, "deny") + self.Lookup("bob@QPID", "unbind", "exchange", "bob-work2", {"routingkey":"joe", "queuename":"bob-work2"}, "deny") + self.Lookup("bob@QPID", "unbind", "exchange", "bob-work2", {"routingkey":"bob", "queuename":"joe-work2"}, "deny") + + self.Lookup("bob@QPID", "access", "exchange", "bob-work", {}, "deny") + self.Lookup("bob@QPID", "access", "exchange", "bob-work", {"routingkey":"bob"}, "deny") + self.Lookup("bob@QPID", "access", "exchange", "bob-work", { "queuename":"bob-work"}, "deny") + self.Lookup("bob@QPID", "access", "exchange", "bob-work", {"routingkey":"bob", "queuename":"bob-work"}, "allow") + self.Lookup("bob@QPID", "access", "exchange", "joe-work", {"routingkey":"bob", "queuename":"bob-work"}, "deny") + self.Lookup("bob@QPID", "access", "exchange", "bob-work", {"routingkey":"joe", "queuename":"bob-work"}, "deny") + self.Lookup("bob@QPID", "access", "exchange", "bob-work", {"routingkey":"bob", "queuename":"joe-work"}, "deny") + + self.Lookup("bob@QPID", "access", "exchange", "bob-work2", {}, "deny") + self.Lookup("bob@QPID", "access", "exchange", "bob-work2", {"routingkey":"bob"}, "deny") + self.Lookup("bob@QPID", "access", "exchange", "bob-work2", { "queuename":"bob-work2"}, "deny") + self.Lookup("bob@QPID", "access", "exchange", "bob-work2", {"routingkey":"bob", "queuename":"bob-work2"}, "allow") + self.Lookup("bob@QPID", "access", "exchange", "joe-work2", {"routingkey":"bob", "queuename":"bob-work2"}, "deny") + self.Lookup("bob@QPID", "access", "exchange", "bob-work2", {"routingkey":"joe", "queuename":"bob-work2"}, "deny") + self.Lookup("bob@QPID", "access", "exchange", "bob-work2", {"routingkey":"bob", "queuename":"joe-work2"}, "deny") + # publish + self.LookupPublish("bob@QPID", "bob-work", "bob", "allow") + self.LookupPublish("bob@QPID", "bob-work2", "bob", "allow") + self.LookupPublish("bob@QPID", "joe-work", "bob", "deny") + self.LookupPublish("bob@QPID", "joe-work2", "bob", "deny") + self.LookupPublish("bob@QPID", "bob-work", "joe", "deny") + self.LookupPublish("bob@QPID", "bob-work2", "joe", "deny") + + + def test_user_name_substitution_domain(self): + """ + Test a setup where users can create, bind, and publish to a main exchange and queue. + Allow access to a single backup exchange and queue. + """ + aclf = self.get_acl_file() + aclf.write('# begin hack alert: allow anonymous to access the lookup debug functions\n') + aclf.write('acl allow-log anonymous create queue\n') + aclf.write('acl allow-log anonymous all exchange name=qmf.*\n') + aclf.write('acl allow-log anonymous all exchange name=amq.direct\n') + aclf.write('acl allow-log anonymous all exchange name=qpid.management\n') + aclf.write('acl allow-log anonymous access method name=*\n') + aclf.write('# end hack alert\n') + # Create primary queue and exchange + # allow predefined alternate + # deny any other alternate + # allow no alternate + aclf.write('acl allow all create queue name=${domain}-work alternate=${domain}-work2\n') + aclf.write('acl deny all create queue name=${domain}-work alternate=*\n') + aclf.write('acl allow all create queue name=${domain}-work\n') + aclf.write('acl allow all create exchange name=${domain}-work alternate=${domain}-work2\n') + aclf.write('acl deny all create exchange name=${domain}-work alternate=*\n') + aclf.write('acl allow all create exchange name=${domain}-work\n') + # Create backup queue and exchange + # Deny any alternate + aclf.write('acl deny all create queue name=${domain}-work2 alternate=*\n') + aclf.write('acl allow all create queue name=${domain}-work2\n') + aclf.write('acl deny all create exchange name=${domain}-work2 alternate=*\n') + aclf.write('acl allow all create exchange name=${domain}-work2\n') + # Bind/unbind primary exchange + # Use only predefined routingkey and queuename + aclf.write('acl allow all bind exchange name=${domain}-work routingkey=${domain} queuename=${domain}-work\n') + aclf.write('acl allow all unbind exchange name=${domain}-work routingkey=${domain} queuename=${domain}-work\n') + # Bind/unbind backup exchange + # Use only predefined routingkey and queuename + aclf.write('acl allow all bind exchange name=${domain}-work2 routingkey=${domain} queuename=${domain}-work2\n') + aclf.write('acl allow all unbind exchange name=${domain}-work2 routingkey=${domain} queuename=${domain}-work2\n') + # Access primary exchange + # Use only predefined routingkey and queuename + aclf.write('acl allow all access exchange name=${domain}-work routingkey=${domain} queuename=${domain}-work\n') + # Access backup exchange + # Use only predefined routingkey and queuename + aclf.write('acl allow all access exchange name=${domain}-work2 routingkey=${domain} queuename=${domain}-work2\n') + # Publish primary exchange + # Use only predefined routingkey + aclf.write('acl allow all publish exchange name=${domain}-work routingkey=${domain}\n') + # Publish backup exchange + # Use only predefined routingkey + aclf.write('acl allow all publish exchange name=${domain}-work2 routingkey=${domain}\n') + # deny mode + aclf.write('acl deny all all\n') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + # create queues + self.Lookup("bob@QPID", "create", "queue", "QPID-work", {}, "allow") + self.Lookup("bob@QPID", "create", "queue", "QPID-work2", {}, "allow") + self.Lookup("bob@QPID", "create", "queue", "QPID-work3", {}, "deny") + self.Lookup("bob@QPID", "create", "queue", "QPID-work", {"alternate":"QPID-work2"}, "allow") + self.Lookup("bob@QPID", "create", "queue", "QPID-work", {"alternate":"bob_QPID-work2"},"deny") + self.Lookup("bob@QPID", "create", "queue", "QPID-work", {"alternate":"joe_QPID-work2"},"deny") + self.Lookup("bob@QPID", "create", "queue", "QPID-work2", {"alternate":"someexchange"}, "deny") + # create exchanges + self.Lookup("bob@QPID", "create", "exchange", "QPID-work", {}, "allow") + self.Lookup("bob@QPID", "create", "exchange", "QPID-work2",{}, "allow") + self.Lookup("bob@QPID", "create", "exchange", "QPID-work3",{}, "deny") + self.Lookup("bob@QPID", "create", "exchange", "QPID-work", {"alternate":"QPID-work2"}, "allow") + self.Lookup("bob@QPID", "create", "exchange", "QPID-work2",{"alternate":"someexchange"}, "deny") + # bind/unbind/access + self.Lookup("bob@QPID", "bind", "exchange", "QPID-work", {}, "deny") + self.Lookup("bob@QPID", "bind", "exchange", "QPID-work", {"routingkey":"QPID"}, "deny") + self.Lookup("bob@QPID", "bind", "exchange", "QPID-work", { "queuename":"QPID-work"}, "deny") + self.Lookup("bob@QPID", "bind", "exchange", "QPID-work", {"routingkey":"QPID", "queuename":"QPID-work"}, "allow") + + self.Lookup("bob@QPID", "bind", "exchange", "QPID-work2", {}, "deny") + self.Lookup("bob@QPID", "bind", "exchange", "QPID-work2", {"routingkey":"QPID"}, "deny") + self.Lookup("bob@QPID", "bind", "exchange", "QPID-work2", { "queuename":"QPID-work2"}, "deny") + self.Lookup("bob@QPID", "bind", "exchange", "QPID-work2", {"routingkey":"QPID", "queuename":"QPID-work2"}, "allow") + + self.Lookup("bob@QPID", "unbind", "exchange", "QPID-work", {}, "deny") + self.Lookup("bob@QPID", "unbind", "exchange", "QPID-work", {"routingkey":"QPID"}, "deny") + self.Lookup("bob@QPID", "unbind", "exchange", "QPID-work", { "queuename":"QPID-work"}, "deny") + self.Lookup("bob@QPID", "unbind", "exchange", "QPID-work", {"routingkey":"QPID", "queuename":"QPID-work"}, "allow") + + self.Lookup("bob@QPID", "unbind", "exchange", "QPID-work2", {}, "deny") + self.Lookup("bob@QPID", "unbind", "exchange", "QPID-work2", {"routingkey":"QPID"}, "deny") + self.Lookup("bob@QPID", "unbind", "exchange", "QPID-work2", { "queuename":"QPID-work2"}, "deny") + self.Lookup("bob@QPID", "unbind", "exchange", "QPID-work2", {"routingkey":"QPID", "queuename":"QPID-work2"}, "allow") + + self.Lookup("bob@QPID", "access", "exchange", "QPID-work", {}, "deny") + self.Lookup("bob@QPID", "access", "exchange", "QPID-work", {"routingkey":"QPID"}, "deny") + self.Lookup("bob@QPID", "access", "exchange", "QPID-work", { "queuename":"QPID-work"}, "deny") + self.Lookup("bob@QPID", "access", "exchange", "QPID-work", {"routingkey":"QPID", "queuename":"QPID-work"}, "allow") + + self.Lookup("bob@QPID", "access", "exchange", "QPID-work2", {}, "deny") + self.Lookup("bob@QPID", "access", "exchange", "QPID-work2", {"routingkey":"QPID"}, "deny") + self.Lookup("bob@QPID", "access", "exchange", "QPID-work2", { "queuename":"QPID-work2"}, "deny") + self.Lookup("bob@QPID", "access", "exchange", "QPID-work2", {"routingkey":"QPID", "queuename":"QPID-work2"}, "allow") + # publish + self.LookupPublish("bob@QPID", "QPID-work", "QPID", "allow") + self.LookupPublish("bob@QPID", "QPID-work2", "QPID", "allow") + self.LookupPublish("joe@QPID", "QPID-work", "QPID", "allow") + self.LookupPublish("joe@QPID", "QPID-work2", "QPID", "allow") + + #===================================== + # Queue per-user quota + #===================================== + + def queue_quota(self, user, passwd, count, byPort=None): + """ Helper method to: + - create a number of queues (should succeed) + - create too many queues (should fail) + - create another queue after deleting a queue (should succeed) + """ + + try: + if byPort: + session = self.get_session_by_port(user, passwd, byPort) + else: + session = self.get_session(user, passwd) + except Exception, e: + self.fail("Unexpected error creating session for %s: %s" % (user, str(e))) + + # Should be able to create count queues per user + try: + for i in range(count): + session.queue_declare(queue="%s%d" % (user, i)) + except Exception, e: + self.fail("Could not create %s for %s: %s" % ("%s%d" % (user, i), user, str(e))) + + # next queue should fail + try: + session.queue_declare(queue="%s%d" % (user, count)) + self.fail("Should not be able to create another queue for user %s" % user) + except Exception, e: + if byPort: + session = self.get_session_by_port(user, passwd, byPort) + else: + session = self.get_session(user, passwd) + + if count > 0: + # Deleting a queue should allow another queue. + session.queue_delete(queue="%s0" % user) + try: + session.queue_declare(queue="%s%d" % (user, count)) + except Exception, e: + self.fail("Could not recreate additional queue for user %s: %s " % (user, str(e))) + + # Clean up + for i in range(1, count+1): + session.queue_delete(queue="%s%d" % (user, i)) + try: + session.close() + except Exception, e: + pass + + def test_queue_per_named_user_quota(self): + """ + Test ACL queue counting limits per named user. + """ + aclf = self.get_acl_file() + aclf.write('quota queues 2 ted@QPID carrol@QPID\n') + aclf.write('quota queues 1 edward@QPID\n') + aclf.write('quota queues 0 mick@QPID\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + # named users should be able to create specified number of queues + self.queue_quota("ted", 'ted', 2) + self.queue_quota("carrol", 'carrol', 2) + self.queue_quota("edward", 'edward', 1) + + # User with quota of 0 is denied + self.queue_quota("mick", 'mick', 0) + + # User not named in quotas is denied + self.queue_quota("dan", 'dan', 0) + + def test_queue_per_user_quota(self): + """ + Test ACL queue counting limits. + port_q has a limit of 2 + """ + # bob should be able to create two queues + self.queue_quota("bob", 'bob', 2, self.port_q()) + + # alice should be able to create two queues + self.queue_quota("alice", 'alice', 2, self.port_q()) + + + def test_queue_limits_by_unnamed_all(self): + """ + Test ACL control queue limits + """ + aclf = self.get_acl_file() + aclf.write('quota queues 2 aliceQUA@QPID bobQUA@QPID\n') + aclf.write('quota queues 1 all\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + # By username should be able to connect twice per user + self.queue_quota('aliceQUA', 'alice', 2) + self.queue_quota('bobQUA', 'bob', 2) + + # User not named in quotas gets 'all' quota + self.queue_quota('charlieQUA', 'charlie', 1) + + + def test_queue_limits_by_group(self): + """ + Test ACL control queue limits + """ + aclf = self.get_acl_file() + aclf.write('group hobbits frodoGR@QPID samGR@QPID merryGR@QPID\n') + aclf.write('quota queues 2 gandalfGR@QPID aragornGR@QPID\n') + aclf.write('quota queues 2 hobbits rosieGR@QPID\n') + aclf.write('# user and groups may be overwritten. Should use last value\n') + aclf.write('quota queues 3 aragornGR@QPID hobbits\n') + aclf.write('acl allow all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + # gandalf gets 2 + self.queue_quota('gandalfGR', 'gandalf', 2) + + # aragorn gets 3 + self.queue_quota('aragornGR', 'aragorn', 3) + + # frodo gets 3 + self.queue_quota('frodoGR', 'frodo', 3) + + # User not named in quotas is denied + self.queue_quota('bilboGR', 'bilbo', 0) + + def test_queue_delete_with_properties(self): + """ + Test cases for queue delete with properties + """ + aclf = self.get_acl_file() + aclf.write('acl allow bob@QPID access queue\n') + aclf.write('acl allow bob@QPID access exchange\n') + aclf.write('acl allow bob@QPID create queue name=qdaq1 durable=true\n') + aclf.write('acl allow bob@QPID create queue name=qdaq2 exclusive=true\n') + aclf.write('acl allow bob@QPID create queue name=qdaq3 policytype=ring\n') + aclf.write('acl allow bob@QPID create queue name=qdaq4 durable=false\n') + aclf.write('acl allow bob@QPID create queue name=qdaq5 exclusive=false\n') + aclf.write('acl allow bob@QPID create queue name=qdaq6 policytype=reject\n') + aclf.write('acl allow bob@QPID create queue name=qdaq7 autodelete=true\n') + aclf.write('acl allow bob@QPID create queue name=qdaq8 autodelete=false\n') + aclf.write('acl allow bob@QPID create queue name=qdaq9\n') + aclf.write('acl allow bob@QPID create exchange name=qdae9\n') + aclf.write('acl deny bob@QPID delete queue name=qdaq1 durable=true\n') + aclf.write('acl deny bob@QPID delete queue name=qdaq2 exclusive=true\n') + aclf.write('acl deny bob@QPID delete queue name=qdaq3 policytype=ring\n') + aclf.write('acl deny bob@QPID delete queue name=qdaq4 durable=false\n') + aclf.write('acl deny bob@QPID delete queue name=qdaq5 exclusive=false\n') + aclf.write('acl deny bob@QPID delete queue name=qdaq6 policytype=reject\n') + aclf.write('acl deny bob@QPID delete queue name=qdaq7 autodelete=true\n') + aclf.write('acl deny bob@QPID delete queue name=qdaq8 autodelete=false\n') + aclf.write('acl deny bob@QPID delete queue name=qdaq9 alternate=qdaq9a\n') + aclf.write('acl allow all access queue\n') + aclf.write('acl allow anonymous all all\n') + aclf.write('acl deny all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + session = self.get_session('bob','bob') + + try: + session.queue_declare(queue="qdaq1", durable=True) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request with name=qdaq1 durable=true"); + + try: + session.queue_delete(queue="qdaq1") + self.fail("ACL should deny queue delete request for qdaq1"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.queue_declare(queue="qdaq2", exclusive=True) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request with name=qdaq2 exclusive=true"); + + try: + session.queue_delete(queue="qdaq2") + self.fail("ACL should deny queue delete request for qdaq2"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.policy_type"] = "ring" + session.queue_declare(queue="qdaq3", arguments=queue_options) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request for qdaq3 with policytype=ring"); + + try: + session.queue_delete(queue="qdaq3") + self.fail("ACL should deny queue delete request for qdaq3"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.queue_declare(queue="qdaq4", durable=False) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request with name=qdaq4 durable=false"); + + try: + session.queue_delete(queue="qdaq4") + self.fail("ACL should deny queue delete request for qdaq4"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.queue_declare(queue="qdaq5", exclusive=False) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request with name=qdaq5 exclusive=false"); + + try: + session.queue_delete(queue="qdaq5") + self.fail("ACL should deny queue delete request for qdaq5"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + queue_options = {} + queue_options["qpid.policy_type"] = "reject" + session.queue_declare(queue="qdaq6", arguments=queue_options) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request for qdaq6 with policytype=reject"); + + try: + session.queue_delete(queue="qdaq6") + self.fail("ACL should deny queue delete request for qdaq6"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.queue_declare(queue="qdaq7", auto_delete=True) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request with name=qdaq7 autodelete=true"); + + try: + session.queue_delete(queue="qdaq7") + self.fail("ACL should deny queue delete request for qdaq7"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.queue_declare(queue="qdaq8", auto_delete=False) + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request with name=qdaq8 autodelete=false"); + + try: + session.queue_delete(queue="qdaq8") + self.fail("ACL should deny queue delete request for qdaq8"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_declare(exchange='qdae9', type='direct') + except qpid.session.SessionException, e: + self.fail("ACL should allow exchange create request with name=qdae9"); + + try: + session.queue_declare(queue="qdaq9", alternate_exchange="qdae9") + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue create request with name=qdaq9 alternate=qdaq9a"); + + try: + session.queue_delete(queue="qdaq9") + self.fail("ACL should deny queue delete request for qdaq9"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + + def test_exchange_delete_with_properties(self): + """ + Test cases for exchange delete with properties + """ + aclf = self.get_acl_file() + aclf.write('acl allow bob@QPID access exchange\n') + aclf.write('acl allow bob@QPID create exchange\n') + aclf.write('acl deny bob@QPID delete exchange name=edae1 durable=true\n') + aclf.write('acl deny bob@QPID delete exchange name=edae2 alternate=edae2a\n') + aclf.write('acl deny bob@QPID delete exchange type=direct\n') + aclf.write('acl allow bob@QPID delete exchange type=headers\n') + aclf.write('acl allow anonymous all all\n') + aclf.write('acl deny all all') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + session = self.get_session('bob','bob') + + try: + session.exchange_declare(exchange='edae1', type='direct', durable=True) + except qpid.session.SessionException, e: + self.fail("ACL should allow exchange create request with name=edae1"); + + try: + session.exchange_delete(exchange="edae1") + self.fail("ACL should deny exchange delete request for edae1"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_declare(exchange='edae2a', type='direct') + except qpid.session.SessionException, e: + self.fail("ACL should allow exchange create request with name=edae2a"); + + try: + session.exchange_declare(exchange='edae2', type='direct', alternate_exchange='edae2a') + except qpid.session.SessionException, e: + self.fail("ACL should allow exchange create request with name=edae2"); + + try: + session.exchange_delete(exchange="edae2") + self.fail("ACL should deny exchange delete request for edae2"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_declare(exchange='edae3d', type='direct') + except qpid.session.SessionException, e: + self.fail("ACL should allow exchange create request with name=edae3d"); + + try: + session.exchange_declare(exchange='edae3h', type='headers') + except qpid.session.SessionException, e: + self.fail("ACL should allow exchange create request with name=eda3h"); + + try: + session.exchange_delete(exchange="edae3d") + self.fail("ACL should deny exchange delete request for edae3d"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + try: + session.exchange_delete(exchange="edae3h") + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + self.fail("ACL should allow exchange delete request for edae3h"); + + #===================================== + # 'create connection' tests + #===================================== +# def test_connect_mode_file_rejects_two_defaults(self): +# """ +# Should reject a file with two connect mode statements +# """ +# aclf = self.get_acl_file() +# aclf.write('acl allow all create connection host=all\n') +# aclf.write('acl allow all create connection host=all\n') +# aclf.close() +# +# result = self.reload_acl() +# if (result): +# pass +# else: +# self.fail(result) + + def test_connect_mode_accepts_host_spec_formats(self): + """ + Should accept host specs of various forms + """ + aclf = self.get_acl_file() + aclf.write('acl allow bob@QPID create connection host=all\n') + aclf.write('acl allow bob@QPID create connection host=1.1.1.1\n') + aclf.write('acl allow bob@QPID create connection host=1.1.1.1,2.2.2.2\n') + aclf.write('acl allow bob@QPID create connection host=localhost\n') + aclf.write('acl allow all all\n') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + def test_connect_mode_allow_all_mode(self): + """ + Should allow one 'all', 'all' + """ + aclf = self.get_acl_file() + aclf.write('acl allow all create connection host=all\n') + aclf.write('acl allow all all\n') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + session = self.get_session('bob','bob') + + + def test_connect_mode_allow_all_localhost(self): + """ + Should allow 'all' 'localhost' + """ + aclf = self.get_acl_file() + aclf.write('acl allow all create connection host=localhost\n') + aclf.write('acl deny all create connection host=all\n') + aclf.write('acl allow all all\n') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + session = self.get_session('bob','bob') + + + def test_connect_mode_global_deny(self): + """ + Should allow 'all' 'localhost' + """ + aclf = self.get_acl_file() + aclf.write('acl allow all create connection host=localhost\n') + aclf.write('acl deny all create connection host=all\n') + aclf.write('acl allow all all\n') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + session = self.get_session('bob','bob') + + self.Lookup("bob@QPID", "create", "connection", "", {"host":"127.0.0.1"}, "allow") + self.Lookup("bob@QPID", "create", "connection", "", {"host":"127.0.0.2"}, "deny") + + + def test_connect_mode_global_range(self): + """ + Should allow 'all' 'localhost' + """ + aclf = self.get_acl_file() + aclf.write('acl allow all create connection host=10.0.0.0,10.255.255.255\n') + aclf.write('acl allow all create connection host=localhost\n') + aclf.write('acl deny all create connection host=all\n') + aclf.write('acl allow all all\n') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + session = self.get_session('bob','bob') + + self.Lookup("bob@QPID", "create", "connection", "", {"host":"0.0.0.0"}, "deny") + self.Lookup("bob@QPID", "create", "connection", "", {"host":"9.255.255.255"}, "deny") + self.Lookup("bob@QPID", "create", "connection", "", {"host":"10.0.0.0"}, "allow") + self.Lookup("bob@QPID", "create", "connection", "", {"host":"10.255.255.255"}, "allow") + self.Lookup("bob@QPID", "create", "connection", "", {"host":"11.0.0.0"}, "deny") + self.Lookup("bob@QPID", "create", "connection", "", {"host":"255.255.255.255"},"deny") + + + def test_connect_mode_nested_ranges(self): + """ + Tests nested ranges for single user + """ + aclf = self.get_acl_file() + aclf.write('acl deny-log bob@QPID create connection host=10.0.1.0,10.0.1.255\n') + aclf.write('acl allow-log bob@QPID create connection host=10.0.0.0,10.255.255.255\n') + aclf.write('acl deny-log bob@QPID create connection host=all\n') + aclf.write('acl allow all create connection host=localhost\n') + aclf.write('acl allow all all\n') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + session = self.get_session('bob','bob') + + self.Lookup("bob@QPID", "create", "connection", "", {"host":"0.0.0.0"}, "deny-log") + self.Lookup("bob@QPID", "create", "connection", "", {"host":"9.255.255.255"}, "deny-log") + self.Lookup("bob@QPID", "create", "connection", "", {"host":"10.0.0.0"}, "allow-log") + self.Lookup("bob@QPID", "create", "connection", "", {"host":"10.0.0.255"}, "allow-log") + self.Lookup("bob@QPID", "create", "connection", "", {"host":"10.0.1.0"}, "deny-log") + self.Lookup("bob@QPID", "create", "connection", "", {"host":"10.0.1.255"}, "deny-log") + self.Lookup("bob@QPID", "create", "connection", "", {"host":"10.0.2.0"}, "allow-log") + self.Lookup("bob@QPID", "create", "connection", "", {"host":"10.255.255.255"}, "allow-log") + self.Lookup("bob@QPID", "create", "connection", "", {"host":"11.0.0.0"}, "deny-log") + self.Lookup("bob@QPID", "create", "connection", "", {"host":"255.255.255.255"},"deny-log") + + + def test_connect_mode_user_ranges(self): + """ + Two user ranges should not interfere with each other + """ + aclf = self.get_acl_file() + aclf.write('acl allow-log bob@QPID create connection host=10.0.0.0,10.255.255.255\n') + aclf.write('acl deny-log bob@QPID create connection host=all\n') + aclf.write('acl allow-log cat@QPID create connection host=192.168.0.0,192.168.255.255\n') + aclf.write('acl deny-log cat@QPID create connection host=all\n') + aclf.write('acl allow all create connection host=localhost\n') + aclf.write('acl allow all all\n') + aclf.close() + + result = self.reload_acl() + if (result): + self.fail(result) + + session = self.get_session('bob','bob') + + self.Lookup("bob@QPID", "create", "connection", "", {"host":"0.0.0.0"}, "deny-log") + self.Lookup("bob@QPID", "create", "connection", "", {"host":"9.255.255.255"}, "deny-log") + self.Lookup("bob@QPID", "create", "connection", "", {"host":"10.0.0.0"}, "allow-log") + self.Lookup("bob@QPID", "create", "connection", "", {"host":"10.255.255.255"}, "allow-log") + self.Lookup("bob@QPID", "create", "connection", "", {"host":"11.0.0.0"}, "deny-log") + self.Lookup("bob@QPID", "create", "connection", "", {"host":"255.255.255.255"},"deny-log") + self.Lookup("cat@QPID", "create", "connection", "", {"host":"0.0.0.0"}, "deny-log") + self.Lookup("cat@QPID", "create", "connection", "", {"host":"192.167.255.255"},"deny-log") + self.Lookup("cat@QPID", "create", "connection", "", {"host":"192.168.0.0"}, "allow-log") + self.Lookup("cat@QPID", "create", "connection", "", {"host":"192.168.255.255"},"allow-log") + self.Lookup("cat@QPID", "create", "connection", "", {"host":"192.169.0.0"}, "deny-log") + self.Lookup("cat@QPID", "create", "connection", "", {"host":"255.255.255.255"},"deny-log") + + +class BrokerAdmin: + def __init__(self, broker, username=None, password=None): + self.connection = qpid.messaging.Connection(broker) + if username: + self.connection.username = username + self.connection.password = password + self.connection.sasl_mechanisms = "PLAIN" + self.connection.open() + self.session = self.connection.session() + self.sender = self.session.sender("qmf.default.direct/broker") + self.reply_to = "responses-#; {create:always}" + self.receiver = self.session.receiver(self.reply_to) + + def invoke(self, method, arguments): + content = { + "_object_id": {"_object_name": "org.apache.qpid.broker:broker:amqp-broker"}, + "_method_name": method, + "_arguments": arguments + } + request = qpid.messaging.Message(reply_to=self.reply_to, content=content) + request.properties["x-amqp-0-10.app-id"] = "qmf2" + request.properties["qmf.opcode"] = "_method_request" + self.sender.send(request) + response = self.receiver.fetch() + self.session.acknowledge() + if response.properties['x-amqp-0-10.app-id'] == 'qmf2': + if response.properties['qmf.opcode'] == '_method_response': + return response.content['_arguments'] + elif response.properties['qmf.opcode'] == '_exception': + raise Exception(response.content['_values']) + else: raise Exception("Invalid response received, unexpected opcode: %s" % response.properties['qmf.opcode']) + else: raise Exception("Invalid response received, not a qmfv2 method: %s" % response.properties['x-amqp-0-10.app-id']) + + def query(self, object_name=None, class_name=None): + content = { "_what": "OBJECT" } + if object_name is not None: + content["_object_id"] = {"_object_name": object_name } + if class_name is not None: + content["_schema_id"] = {"_class_name": class_name } + request = qpid.messaging.Message(reply_to=self.reply_to, content=content) + request.properties["x-amqp-0-10.app-id"] = "qmf2" + request.properties["qmf.opcode"] = "_query_request" + self.sender.send(request) + response = self.receiver.fetch() + self.session.acknowledge() + if response.properties['x-amqp-0-10.app-id'] == 'qmf2': + if response.properties['qmf.opcode'] == '_query_response': + return + elif response.properties['qmf.opcode'] == '_exception': + raise Exception(response.content['_values']) + else: raise Exception("Invalid response received, unexpected opcode: %s" % response.properties['qmf.opcode']) + else: raise Exception("Invalid response received, not a qmfv2 method: %s" % response.properties['x-amqp-0-10.app-id']) + + def create_exchange(self, name, exchange_type=None, options={}): + properties = options + if exchange_type: properties["exchange_type"] = exchange_type + self.invoke("create", {"type": "exchange", "name":name, "properties":properties}) + + def create_queue(self, name, properties={}): + self.invoke("create", {"type": "queue", "name":name, "properties":properties}) + + def delete_exchange(self, name): + self.invoke("delete", {"type": "exchange", "name":name}) + + def delete_queue(self, name): + self.invoke("delete", {"type": "queue", "name":name}) + + def get_timestamp_cfg(self): + return self.invoke("getTimestampConfig", {}) + + def set_timestamp_cfg(self, receive): + return self.invoke("getTimestampConfig", {"receive":receive}) diff --git a/qpid/cpp/src/tests/ais_test.cpp b/qpid/cpp/src/tests/ais_test.cpp new file mode 100644 index 0000000000..00c61242e4 --- /dev/null +++ b/qpid/cpp/src/tests/ais_test.cpp @@ -0,0 +1,23 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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. + * + */ + +// Defines test_main function to link with actual unit test code. +#define BOOST_AUTO_TEST_MAIN // Boost 1.33 +#define BOOST_TEST_MAIN +#include "unit_test.h" + diff --git a/qpid/cpp/src/tests/allhosts b/qpid/cpp/src/tests/allhosts new file mode 100755 index 0000000000..07bc04fff5 --- /dev/null +++ b/qpid/cpp/src/tests/allhosts @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +usage() { + echo "Usage: $0 [options] command. +Run a command on each host in \$HOSTS. +Options: + -l USER passed to ssh - run as USER. + -t passed to ssh - create a terminal. + -b run in background, wait for commands to complete. + -d run in background, don't wait for commands to complete. + -s SECONDS sleep between starting commands. + -q don't print banner lines for each host. + -o SUFFIX log output of each command to <host>.SUFFIX + -X passed to ssh - forward X connection. +" + exit 1 +} + +while getopts "tl:bs:dqo:X" opt; do + case $opt in + l) SSHOPTS="-l$OPTARG $SSHOPTS" ;; + t) SSHOPTS="-t $SSHOPTS" ;; + b) BACKGROUND=1 ;; + d) BACKGROUND=1; DISOWN=1 ;; + s) SLEEP="sleep $OPTARG" ;; + q) NOBANNER=1 ;; + o) SUFFIX=$OPTARG ;; + X) SSHOPTS="-X $SSHOPTS" ;; + *) usage;; + esac +done +shift `expr $OPTIND - 1` +test "$*" || usage; + +OK_FILE=`mktemp` # Will be deleted if anything goes wrong. +trap "rm -f $OK_FILE" EXIT + +do_ssh() { + h=$1; shift + if test $SUFFIX ; then ssh $SSHOPTS $h "$@" &> $h.$SUFFIX + else ssh $SSHOPTS $h "$@"; fi || rm -rf $OK_FILE; +} + +for h in $HOSTS ; do + test "$NOBANNER" || echo "== ssh $SSHOPTS $h $@ ==" + if [ "$BACKGROUND" = 1 ]; then + do_ssh $h "$@" & + CHILDREN="$! $CHILDREN" + else + do_ssh $h "$@" + fi + $SLEEP +done + +if [ "$DISOWN" = 1 ]; then + for c in $CHILDREN; do disown $c; done +else + wait +fi + +test -f $OK_FILE diff --git a/qpid/cpp/src/tests/assertions.py b/qpid/cpp/src/tests/assertions.py new file mode 100644 index 0000000000..930afd124d --- /dev/null +++ b/qpid/cpp/src/tests/assertions.py @@ -0,0 +1,194 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from qpid.tests.messaging.implementation import * +from qpid.tests.messaging import VersionTest + +class AssertionTests (VersionTest): + """ + Tests for assertions with qpidd + """ + def test_queues_alternate_exchange1(self): + name = str(uuid4()) + self.ssn.sender("%s; {create:always, node:{properties:{alternate-exchange:amq.fanout}}}" % name) + self.ssn.sender("%s; {assert:always, node:{properties:{alternate-exchange:amq.fanout}}}" % name) + try: + self.ssn.sender("%s; {assert:always, node:{properties:{alternate-exchange:amq.topic}}}" % name) + assert False, "Expected assertion to fail on alternate-exchange" + except AssertionFailed: None + except MessagingError: None + + def test_queues_alternate_exchange2(self): + name = str(uuid4()) + self.ssn.sender("%s; {create:always, node:{x-declare:{alternate-exchange:amq.fanout}}}" % name) + self.ssn.sender("%s; {assert:always, node:{x-declare:{alternate-exchange:amq.fanout}}}" % name) + try: + self.ssn.sender("%s; {assert:always, node:{x-declare:{alternate-exchange:amq.topic}}}" % name) + assert False, "Expected assertion to fail on alternate-exchange" + except AssertionFailed: None + except MessagingError: None + + def test_queue_type(self): + name = str(uuid4()) + self.ssn.sender("%s; {create:always}" % name) + self.ssn.sender("%s; {assert:always, node:{type:queue}}" % name) + try: + self.ssn.sender("%s; {assert:always, node:{type:topic}}" % name) + assert False, "Expected assertion to fail on type" + except AssertionFailed: None + except MessagingError: None + + def test_queue_not_durable(self): + name = str(uuid4()) + self.ssn.sender("%s; {create:always}" % name) + self.ssn.sender("%s; {assert:always, node:{durable:False}}" % name) + try: + self.ssn.sender("%s; {assert:always, node:{durable:True}}" % name) + assert False, "Expected assertion to fail on durability" + except AssertionFailed: None + except MessagingError: None + + def test_queue_is_durable(self): + name = str(uuid4()) + self.ssn.sender("%s; {create:always, node:{durable:True}}" % name) + self.ssn.sender("%s; {assert:always, node:{durable:True}}" % name) + + def test_queue_is_autodelete(self): + name = str(uuid4()) + self.ssn.sender("%s; {create:always, node:{x-declare:{auto-delete:True}}}" % name) + self.ssn.sender("%s; {assert:always, node:{x-declare:{auto-delete:True}}}" % name) + try: + self.ssn.sender("%s; {assert:always, node:{x-declare:{auto-delete:False}}}" % name) + assert False, "Expected assertion to fail for auto-delete" + except AssertionFailed: None + except MessagingError: None + + def do_test_queue_options(self, name): + self.ssn.sender("%s; {create:always, node:{x-declare:{arguments:{foo:bar,'qpid.last_value_queue_key':abc}}}}" % name) + self.ssn.sender("%s; {assert:always, node:{x-declare:{arguments:{'qpid.last_value_queue_key':abc}}}}" % name) + try: + self.ssn.sender("%s; {assert:always, node:{x-declare:{arguments:{foo:bar}}}}" % name) + assert False, "Expected assertion to fail on unrecognised option" + except AssertionFailed: None + except MessagingError: None + try: + self.ssn.sender("%s; {assert:always, node:{x-declare:{arguments:{'qpid.max_count':10}}}}" % name) + assert False, "Expected assertion to fail on unspecified option" + except AssertionFailed: None + except MessagingError: None + try: + self.ssn.sender("%s; {assert:always, node:{x-declare:{arguments:{'qpid.last_value_key':xyz}}}}" % name) + assert False, "Expected assertion to fail on option with different value" + except AssertionFailed: None + except MessagingError: None + + def test_queue_options(self): + self.do_test_queue_options(str(uuid4())) + + def test_queue_options_from_0_10(self): + name = str(uuid4()) + self.do_test_queue_options(name) + ssn_0_10 = self.create_connection("amqp0-10", True).session() + ssn_0_10.sender("%s; {assert:always, node:{x-declare:{arguments:{'qpid.last_value_queue_key':abc}}}}" % name) + try: + ssn_0_10.sender("%s; {assert:always, node:{x-declare:{arguments:{'qpid.last_value_key':xyz}}}}" % name) + assert False, "Expected assertion to fail on option with different value" + except AssertionFailed: None + except MessagingError: None + + + def test_exchanges_alternate_exchange1(self): + name = str(uuid4()) + self.ssn.sender("%s; {create:always, node:{type:topic, properties:{alternate-exchange:amq.fanout}}}" % name) + self.ssn.sender("%s; {assert:always, node:{type:topic, properties:{alternate-exchange:amq.fanout}}}" % name) + try: + self.ssn.sender("%s; {assert:always, node:{properties:{alternate-exchange:amq.topic}}}" % name) + assert False, "Expected assertion to fail on alternate-exchange" + except AssertionFailed: None + except MessagingError: None + + def test_exchanges_alternate_exchange2(self): + name = str(uuid4()) + self.ssn.sender("%s; {create:always, node:{type:topic, x-declare:{alternate-exchange:amq.fanout}}}" % name) + self.ssn.sender("%s; {assert:always, node:{type:topic, x-declare:{alternate-exchange:amq.fanout}}}" % name) + try: + self.ssn.sender("%s; {assert:always, node:{x-declare:{alternate-exchange:amq.topic}}}" % name) + assert False, "Expected assertion to fail on alternate-exchange" + except AssertionFailed: None + except MessagingError: None + + def test_exchange_type(self): + name = str(uuid4()) + self.ssn.sender("%s; {create:always, node:{type:topic}}" % name) + self.ssn.sender("%s; {assert:always, node:{type:topic}}" % name) + try: + self.ssn.sender("%s; {assert:always, node:{type:queue}}" % name) + assert False, "Expected assertion to fail on type" + except AssertionFailed: None + except MessagingError: None + + def test_exchange_durability(self): + name = str(uuid4()) + self.ssn.sender("%s; {create:always, node:{type:topic}}" % name) + self.ssn.sender("%s; {assert:always, node:{durable:False}}" % name) + try: + self.ssn.sender("%s; {assert:always, node:{durable:True}}" % name) + assert False, "Expected assertion to fail on durability" + except AssertionFailed: None + except MessagingError: None + + def test_exchange_is_autodelete(self): + name = str(uuid4()) + self.ssn.sender("%s; {create:always, node:{type:topic, x-declare:{auto-delete:True}}}" % name) + self.ssn.sender("%s; {assert:always, node:{x-declare:{auto-delete:True}}}" % name) + try: + self.ssn.sender("%s; {assert:always, node:{x-declare:{auto-delete:False}}}" % name) + assert False, "Expected assertion to fail for auto-delete" + except AssertionFailed: None + except MessagingError: None + + def test_exchange_options(self): + name = str(uuid4()) + self.ssn.sender("%s; {create:always, node:{type:topic, x-declare:{arguments:{foo:bar,'qpid.msg_sequence':True}}}}" % name) + self.ssn.sender("%s; {assert:always, node:{x-declare:{arguments:{'qpid.msg_sequence':True}}}}" % name) + try: + self.ssn.sender("%s; {assert:always, node:{x-declare:{arguments:{foo:bar}}}}" % name) + assert False, "Expected assertion to fail on unrecognised option" + except AssertionFailed: None + except MessagingError: None + try: + self.ssn.sender("%s; {assert:always, node:{x-declare:{arguments:{'qpid.ive':True}}}}" % name) + assert False, "Expected assertion to fail on unspecified option" + except AssertionFailed: None + except MessagingError: None + + def test_queue_autodelete_timeout(self): + name = str(uuid4()) + # create subscription queue with 0-10 to be sure of name + ssn_0_10 = self.create_connection("amqp0-10", True).session() + ssn_0_10.receiver("amq.direct; {link:{name:%s,timeout:30}}" % name) + self.ssn.sender("%s; {assert:always, node:{x-declare:{arguments: {qpid.auto_delete_timeout: 30}}}}" % name) + ssn_0_10_other = self.create_connection("amqp0-10", True).session() + ssn_0_10_other.sender("%s; {assert:always, node:{x-declare:{arguments: {qpid.auto_delete_timeout: 30}}}}" % name) + try: + self.ssn.sender("%s; {assert:always, node:{x-declare:{arguments: {qpid.auto_delete_timeout: 60}}}}" % name) + ssn_0_10_other.sender("%s; {assert:always, node:{x-declare:{arguments: {qpid.auto_delete_timeout: 60}}}}" % name) + assert False, "Expected assertion to fail for auto_delete_timeout" + except AssertionFailed: None + except MessagingError: None diff --git a/qpid/cpp/src/tests/background.ps1 b/qpid/cpp/src/tests/background.ps1 new file mode 100644 index 0000000000..36e9e4e6e9 --- /dev/null +++ b/qpid/cpp/src/tests/background.ps1 @@ -0,0 +1,55 @@ +#
+# 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.
+#
+
+# Run a PowerShell scriptblock in a background process.
+param(
+ [scriptblock] $script # scriptblock to run
+)
+
+# break out of the script on any errors
+trap { break }
+
+# In order to pass a scriptblock to another powershell instance, it must
+# be encoded to pass through the command line.
+$encodedScript = [convert]::ToBase64String(
+ [Text.Encoding]::Unicode.GetBytes([string] $script))
+
+#$p = new-object System.Diagnostics.Process
+$si = new-object System.Diagnostics.ProcessStartInfo
+$si.WorkingDirectory = $pwd
+$si.FileName = (get-command powershell.exe).Definition
+$si.Arguments = "-encodedCommand $encodedScript"
+
+###### debugging setup
+#$si.CreateNoWindow = $true
+# UseShellExecute false required for RedirectStandard(Error, Output)
+#$si.UseShellExecute = $false
+#$si.RedirectStandardError = $true
+#$si.RedirectStandardOutput = $true
+######
+$si.UseShellExecute = $true
+
+##### Debugging, instead of the plain Start() above.
+#$output = [io.File]::AppendText("start.out")
+#$error = [io.File]::AppendText("start.err")
+$p = [System.Diagnostics.Process]::Start($si)
+#$output.WriteLine($p.StandardOutput.ReadToEnd())
+#$error.WriteLine($p.StandardError.ReadToEnd())
+#$p.WaitForExit()
+#$output.Close()
diff --git a/qpid/cpp/src/tests/brokertest.py b/qpid/cpp/src/tests/brokertest.py new file mode 100644 index 0000000000..6fae88092b --- /dev/null +++ b/qpid/cpp/src/tests/brokertest.py @@ -0,0 +1,752 @@ +# +# 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. +# + +# Support library for tests that start multiple brokers, e.g. HA or federation + +import os, signal, string, tempfile, subprocess, socket, threading, time, imp, re +import qpid, traceback, signal +from qpid import connection, util +from qpid.compat import format_exc +from unittest import TestCase +from copy import copy +from threading import Thread, Lock, Condition +from logging import getLogger +from qpidtoollibs import BrokerAgent + +# NOTE: Always import native client qpid.messaging, import swigged client +# qpid_messaging if possible. qpid_messaing is set to None if not available. +# +# qm is set to qpid_messaging if it is available, qpid.messaging if not. +# Use qm.X to specify names from the default messaging module. +# +# Set environment variable QPID_PY_NO_SWIG=1 to prevent qpid_messaging from loading. +# +# BrokerTest can be configured to determine which protocol is used by default: +# +# -DPROTOCOL="amqpX": Use protocol "amqpX". Defaults to amqp1.0 if available. +# +# The configured defaults can be over-ridden on BrokerTest.connect and some +# other methods by specifying native=True|False and protocol="amqpX" +# + +import qpid.messaging +qm = qpid.messaging +qpid_messaging = None + +def env_has_log_config(): + """True if there are qpid log configuratoin settings in the environment.""" + return "QPID_LOG_ENABLE" in os.environ or "QPID_TRACE" in os.environ + +if not os.environ.get("QPID_PY_NO_SWIG"): + try: + import qpid_messaging + from qpid.datatypes import uuid4 + qm = qpid_messaging + # Silence warnings from swigged messaging library unless enabled in environment. + if not env_has_log_config(): + qm.Logger.configure(["--log-enable=error"]) + except ImportError: + print "Cannot load python SWIG bindings, falling back to native qpid.messaging." + +log = getLogger("brokertest") + +# Values for expected outcome of process at end of test +EXPECT_EXIT_OK=1 # Expect to exit with 0 status before end of test. +EXPECT_EXIT_FAIL=2 # Expect to exit with non-0 status before end of test. +EXPECT_RUNNING=3 # Expect to still be running at end of test +EXPECT_UNKNOWN=4 # No expectation, don't check exit status. + +def find_exe(program): + """Find an executable in the system PATH""" + def is_exe(fpath): + return os.path.isfile(fpath) and os.access(fpath, os.X_OK) + mydir, name = os.path.split(program) + if mydir: + if is_exe(program): return program + else: + for path in os.environ["PATH"].split(os.pathsep): + exe_file = os.path.join(path, program) + if is_exe(exe_file): return exe_file + return None + +def is_running(pid): + try: + os.kill(pid, 0) + return True + except: + return False + +class BadProcessStatus(Exception): + pass + +def error_line(filename, n=1): + """Get the last n line(s) of filename for error messages""" + result = [] + try: + f = open(filename) + try: + for l in f: + if len(result) == n: result.pop(0) + result.append(" "+l) + finally: + f.close() + except: return "" + return ":\n" + "".join(result) + +def retry(function, timeout=10, delay=.001, max_delay=1): + """Call function until it returns a true value or timeout expires. + Double the delay for each retry up to max_delay. + Returns what function returns if true, None if timeout expires.""" + deadline = time.time() + timeout + ret = None + while True: + ret = function() + if ret: return ret + remaining = deadline - time.time() + if remaining <= 0: return False + delay = min(delay, remaining) + time.sleep(delay) + delay = min(delay*2, max_delay) + +class AtomicCounter: + def __init__(self): + self.count = 0 + self.lock = Lock() + + def next(self): + self.lock.acquire(); + ret = self.count + self.count += 1 + self.lock.release(); + return ret + +_popen_id = AtomicCounter() # Popen identifier for use in output file names. + +# Constants for file descriptor arguments to Popen +FILE = "FILE" # Write to file named after process +from subprocess import PIPE, STDOUT + +class Popen(subprocess.Popen): + """ + Can set and verify expectation of process status at end of test. + Dumps command line, stdout, stderr to data dir for debugging. + """ + + def __init__(self, cmd, expect=EXPECT_EXIT_OK, stdin=None, stdout=FILE, stderr=FILE): + """Run cmd (should be a list of program and arguments) + expect - if set verify expectation at end of test. + stdout, stderr - can have the same values as for subprocess.Popen as well as + FILE (the default) which means write to a file named after the process. + stdin - like subprocess.Popen but defauts to PIPE + """ + self._clean = False + self._clean_lock = Lock() + assert find_exe(cmd[0]), "executable not found: "+cmd[0] + if type(cmd) is type(""): cmd = [cmd] # Make it a list. + self.cmd = [ str(x) for x in cmd ] + self.expect = expect + self.id = _popen_id.next() + self.pname = "%s-%d" % (os.path.split(self.cmd[0])[1], self.id) + if stdout == FILE: stdout = open(self.outfile("out"), "w") + if stderr == FILE: stderr = open(self.outfile("err"), "w") + subprocess.Popen.__init__(self, self.cmd, bufsize=0, executable=None, + stdin=stdin, stdout=stdout, stderr=stderr) + f = open(self.outfile("cmd"), "w") + try: f.write("%s\n%d"%(self.cmd_str(), self.pid)) + finally: f.close() + log.debug("Started process %s: %s" % (self.pname, " ".join(self.cmd))) + + def __repr__(self): return "Popen<%s>"%(self.pname) + + def outfile(self, ext): return "%s.%s" % (self.pname, ext) + + def unexpected(self,msg): + err = error_line(self.outfile("err")) or error_line(self.outfile("out")) + raise BadProcessStatus("%s %s%s" % (self.pname, msg, err)) + + def teardown(self): # Clean up at end of test. + if self.expect == EXPECT_UNKNOWN: + try: self.kill() # Just make sure its dead + except: pass + elif self.expect == EXPECT_RUNNING: + if self.poll() != None: + self.unexpected("expected running, exit code %d" % self.returncode) + else: + try: + self.kill() + except Exception,e: + self.unexpected("exception from kill: %s" % str(e)) + else: + retry(lambda: self.poll() is not None) + if self.returncode is None: # Still haven't stopped + self.kill() + self.unexpected("still running") + elif self.expect == EXPECT_EXIT_OK and self.returncode != 0: + self.unexpected("exit code %d" % self.returncode) + elif self.expect == EXPECT_EXIT_FAIL and self.returncode == 0: + self.unexpected("expected error") + self.wait() + + + def communicate(self, input=None): + ret = subprocess.Popen.communicate(self, input) + self._cleanup() + return ret + + def is_running(self): return self.poll() is None + + def assert_running(self): + if not self.is_running(): self.unexpected("Exit code %d" % self.returncode) + + def wait(self): + ret = subprocess.Popen.wait(self) + self._cleanup() + return ret + + def assert_exit_ok(self): + if self.wait() != 0: self.unexpected("Exit code %d" % self.returncode) + + def terminate(self): + try: subprocess.Popen.terminate(self) + except AttributeError: # No terminate method + try: + os.kill( self.pid , signal.SIGTERM) + except AttributeError: # no os.kill, using taskkill.. (Windows only) + os.popen('TASKKILL /PID ' +str(self.pid) + ' /F') + self.wait() + + def kill(self): + # Set to EXPECT_UNKNOWN, EXPECT_EXIT_FAIL creates a race condition + # if the process exits normally concurrent with the call to kill. + self.expect = EXPECT_UNKNOWN + try: subprocess.Popen.kill(self) + except AttributeError: # No terminate method + try: + os.kill( self.pid , signal.SIGKILL) + except AttributeError: # no os.kill, using taskkill.. (Windows only) + os.popen('TASKKILL /PID ' +str(self.pid) + ' /F') + self.wait() + + def _cleanup(self): + """Clean up after a dead process""" + self._clean_lock.acquire() + if not self._clean: + self._clean = True + try: self.stdin.close() + except: pass + try: self.stdout.close() + except: pass + try: self.stderr.close() + except: pass + self._clean_lock.release() + + def cmd_str(self): return " ".join([str(s) for s in self.cmd]) + + +def checkenv(name): + value = os.getenv(name) + if not value: raise Exception("Environment variable %s is not set" % name) + return value + +def find_in_file(str, filename): + if not os.path.exists(filename): return False + f = open(filename) + try: return str in f.read() + finally: f.close() + +class Broker(Popen): + "A broker process. Takes care of start, stop and logging." + _broker_count = 0 + _log_count = 0 + + def __repr__(self): return "<Broker:%s:%d>"%(self.log, self.port()) + + def get_log(self): + return os.path.abspath(self.log) + + def __init__(self, test, args=[], test_store=False, name=None, expect=EXPECT_RUNNING, port=0, wait=None, show_cmd=False): + """Start a broker daemon. name determines the data-dir and log + file names.""" + + self.test = test + self._port=port + args = copy(args) + if BrokerTest.amqp_lib: args += ["--load-module", BrokerTest.amqp_lib] + if BrokerTest.store_lib and not test_store: + args += ['--load-module', BrokerTest.store_lib] + if BrokerTest.sql_store_lib: + args += ['--load-module', BrokerTest.sql_store_lib] + args += ['--catalog', BrokerTest.sql_catalog] + if BrokerTest.sql_clfs_store_lib: + args += ['--load-module', BrokerTest.sql_clfs_store_lib] + args += ['--catalog', BrokerTest.sql_catalog] + cmd = [BrokerTest.qpidd_exec, "--port", port, "--interface", "127.0.0.1", "--no-module-dir"] + args + if not "--auth" in args: cmd.append("--auth=no") + if wait != None: + cmd += ["--wait", str(wait)] + if name: self.name = name + else: + self.name = "broker%d" % Broker._broker_count + Broker._broker_count += 1 + + self.log = "%03d:%s.log" % (Broker._log_count, self.name) + self.store_log = "%03d:%s.store.log" % (Broker._log_count, self.name) + Broker._log_count += 1 + + cmd += ["--log-to-file", self.log] + cmd += ["--log-to-stderr=no"] + + # Add default --log-enable arguments unless args already has --log arguments. + if not env_has_log_config() and not [l for l in args if l.startswith("--log")]: + args += ["--log-enable=info+"] + + if test_store: cmd += ["--load-module", BrokerTest.test_store_lib, + "--test-store-events", self.store_log] + + self.datadir = os.path.abspath(self.name) + cmd += ["--data-dir", self.datadir] + if show_cmd: print cmd + Popen.__init__(self, cmd, expect, stdout=PIPE) + test.teardown_add(self) + self._host = "127.0.0.1" + self._agent = None + + log.debug("Started broker %s" % self) + + def host(self): return self._host + + def port(self): + # Read port from broker process stdout if not already read. + if (self._port == 0): + try: self._port = int(self.stdout.readline()) + except ValueError, e: + raise Exception("Can't get port for broker %s (%s)%s: %s" % + (self.name, self.pname, error_line(self.log,5), e)) + return self._port + + def unexpected(self,msg): + raise BadProcessStatus("%s: %s (%s)" % (msg, self.name, self.pname)) + + def connect(self, timeout=5, native=False, **kwargs): + """New API connection to the broker. + @param native if True force use of the native qpid.messaging client + even if swig client is available. + """ + if native: connection_class = qpid.messaging.Connection + else: + connection_class = qm.Connection + if (self.test.protocol and qm == qpid_messaging): + kwargs.setdefault("protocol", self.test.protocol) + return connection_class.establish(self.host_port(), timeout=timeout, **kwargs) + + @property + def agent(self, **kwargs): + """Return a BrokerAgent for this broker""" + if not self._agent: self._agent = BrokerAgent(self.connect(**kwargs)) + return self._agent + + + def declare_queue(self, queue): + self.agent.addQueue(queue) + + def _prep_sender(self, queue, durable, xprops): + s = queue + "; {create:always, node:{durable:" + str(durable) + if xprops != None: s += ", x-declare:{" + xprops + "}" + return s + "}}" + + def send_message(self, queue, message, durable=True, xprops=None, session=None): + if session == None: + s = self.connect().session() + else: + s = session + s.sender(self._prep_sender(queue, durable, xprops)).send(message) + if session == None: + s.connection.close() + + def send_messages(self, queue, messages, durable=True, xprops=None, session=None): + if session == None: + s = self.connect().session() + else: + s = session + sender = s.sender(self._prep_sender(queue, durable, xprops)) + for m in messages: sender.send(m) + if session == None: + s.connection.close() + + def get_message(self, queue): + s = self.connect().session() + m = s.receiver(queue+"; {create:always}", capacity=1).fetch(timeout=1) + s.acknowledge() + s.connection.close() + return m + + def get_messages(self, queue, n): + s = self.connect().session() + receiver = s.receiver(queue+"; {create:always}", capacity=n) + m = [receiver.fetch(timeout=1) for i in range(n)] + s.acknowledge() + s.connection.close() + return m + + def host_port(self): return "%s:%s" % (self.host(), self.port()) + + def ready(self, timeout=10, **kwargs): + """Wait till broker is ready to serve clients""" + deadline = time.time()+timeout + while True: + try: + c = self.connect(timeout=timeout, **kwargs) + try: + c.session() + return # All good + finally: c.close() + except Exception,e: # Retry up to timeout + if time.time() > deadline: + raise RethrownException( + "Broker %s not responding: (%s)%s"%( + self.name,e,error_line(self.log, 5))) + + def assert_log_clean(self, ignore=None): + log = open(self.get_log()) + try: + error = re.compile("] error|] critical") + if ignore: ignore = re.compile(ignore) + else: ignore = re.compile("\000") # Won't match anything + for line in log.readlines(): + assert not error.search(line) or ignore.search(line), "Errors in log file %s: %s"%(log, line) + finally: log.close() + +def receiver_iter(receiver, timeout=0): + """Make an iterator out of a receiver. Returns messages till Empty is raised.""" + try: + while True: + yield receiver.fetch(timeout=timeout) + except qm.Empty: + pass + +def browse(session, queue, timeout=0, transform=lambda m: m.content): + """Return a list with the contents of each message on queue.""" + r = session.receiver("%s;{mode:browse}"%(queue)) + r.capacity = 100 + try: + return [transform(m) for m in receiver_iter(r, timeout)] + finally: + r.close() + +def assert_browse(session, queue, expect_contents, timeout=0, transform=lambda m: m.content, msg=None): + """Assert that the contents of messages on queue (as retrieved + using session and timeout) exactly match the strings in + expect_contents""" + if msg is None: msg = "browse '%s' failed" % queue + actual_contents = browse(session, queue, timeout, transform=transform) + if msg: msg = "%s: %r != %r"%(msg, expect_contents, actual_contents) + assert expect_contents == actual_contents, msg + +def assert_browse_retry(session, queue, expect_contents, timeout=1, delay=.001, transform=lambda m:m.content, msg="browse failed"): + """Wait up to timeout for contents of queue to match expect_contents""" + test = lambda: browse(session, queue, 0, transform=transform) == expect_contents + retry(test, timeout, delay) + actual_contents = browse(session, queue, 0, transform=transform) + if msg: msg = "%s: %r != %r"%(msg, expect_contents, actual_contents) + assert expect_contents == actual_contents, msg + +class BrokerTest(TestCase): + """ + Tracks processes started by test and kills at end of test. + Provides a well-known working directory for each test. + """ + + def __init__(self, *args, **kwargs): + self.longMessage = True # Enable long messages for assert*(..., msg=xxx) + TestCase.__init__(self, *args, **kwargs) + + # Environment settings. + qpidd_exec = os.path.abspath(checkenv("QPIDD_EXEC")) + ha_lib = os.getenv("HA_LIB") + xml_lib = os.getenv("XML_LIB") + amqp_lib = os.getenv("AMQP_LIB") + qpid_config_exec = os.getenv("QPID_CONFIG_EXEC") + qpid_route_exec = os.getenv("QPID_ROUTE_EXEC") + receiver_exec = os.getenv("RECEIVER_EXEC") + sender_exec = os.getenv("SENDER_EXEC") + sql_store_lib = os.getenv("STORE_SQL_LIB") + sql_clfs_store_lib = os.getenv("STORE_SQL_CLFS_LIB") + sql_catalog = os.getenv("STORE_CATALOG") + store_lib = os.getenv("STORE_LIB") + test_store_lib = os.getenv("TEST_STORE_LIB") + rootdir = os.getcwd() + + try: + import proton + PN_VERSION = (proton.VERSION_MAJOR, proton.VERSION_MINOR) + except ImportError: + # proton not on path, can't determine version + PN_VERSION = (0, 0) + except AttributeError: + # prior to 0.8 proton did not expose version info + PN_VERSION = (0, 7) + + PN_TX_VERSION = (0, 9) + + amqp_tx_supported = PN_VERSION >= PN_TX_VERSION + + @classmethod + def amqp_tx_warning(cls): + if not cls.amqp_tx_supported: + if cls.PN_VERSION == (0, 0): + print "WARNING: Cannot test transactions over AMQP 1.0, proton not on path so version could not be determined" + elif cls.PN_VERSION == (0, 7): + print "WARNING: Cannot test transactions over AMQP 1.0, proton version is 0.7 or less, %s.%s required" % cls.PN_TX_VERSION + else: + print "WARNING: Cannot test transactions over AMQP 1.0, proton version %s.%s < %s.%s" % (cls.PN_VERSION + cls.PN_TX_VERSION) + return False + return True + + def configure(self, config): self.config=config + + def setUp(self): + defs = self.config.defines + outdir = defs.get("OUTDIR") or "brokertest.tmp" + self.dir = os.path.join(self.rootdir, outdir, self.id()) + os.makedirs(self.dir) + os.chdir(self.dir) + self.teardown_list = [] # things to tear down at end of test + if qpid_messaging and self.amqp_lib: default_protocol="amqp1.0" + else: default_protocol="amqp0-10" + self.protocol = defs.get("PROTOCOL") or default_protocol + self.tx_protocol = self.protocol + if not self.amqp_tx_supported: self.tx_protocol = "amqp0-10" + + def tearDown(self): + err = [] + self.teardown_list.reverse() # Tear down in reverse order + for p in self.teardown_list: + log.debug("Tearing down %s", p) + try: + # Call the first of the methods that is available on p. + for m in ["teardown", "close"]: + a = getattr(p, m, None) + if a: a(); break + else: raise Exception("Don't know how to tear down %s", p) + except Exception, e: + if m != "close": # Ignore connection close errors. + err.append("%s: %s"%(e.__class__.__name__, str(e))) + self.teardown_list = [] # reset in case more processes start + os.chdir(self.rootdir) + if err: raise Exception("Unexpected process status:\n "+"\n ".join(err)) + + def teardown_add(self, thing): + """Call thing.teardown() or thing.close() at end of test""" + self.teardown_list.append(thing) + + def popen(self, cmd, expect=EXPECT_EXIT_OK, stdin=None, stdout=FILE, stderr=FILE): + """Start a process that will be killed at end of test, in the test dir.""" + os.chdir(self.dir) + p = Popen(cmd, expect, stdin=stdin, stdout=stdout, stderr=stderr) + self.teardown_add(p) + return p + + def broker(self, args=[], name=None, expect=EXPECT_RUNNING, wait=True, port=0, show_cmd=False, **kw): + """Create and return a broker ready for use""" + b = Broker(self, args=args, name=name, expect=expect, port=port, show_cmd=show_cmd, **kw) + if (wait): + try: b.ready() + except Exception, e: + raise RethrownException("Failed to start broker %s(%s): %s" % (b.name, b.log, e)) + return b + + def check_output(self, args, stdin=None): + p = self.popen(args, stdout=PIPE, stderr=STDOUT) + out = p.communicate(stdin) + if p.returncode != 0: + raise Exception("%s exit code %s, output:\n%s" % (args, p.returncode, out[0])) + return out[0] + + def browse(self, *args, **kwargs): browse(*args, **kwargs) + def assert_browse(self, *args, **kwargs): assert_browse(*args, **kwargs) + def assert_browse_retry(self, *args, **kwargs): assert_browse_retry(*args, **kwargs) + + def protocol_option(self, connection_options=""): + if "protocol" in connection_options: return connection_options + else: return ",".join(filter(None, [connection_options,"protocol:'%s'"%self.protocol])) + + +def join(thread, timeout=30): + thread.join(timeout) + if thread.isAlive(): raise Exception("Timed out joining thread %s"%thread) + +class RethrownException(Exception): + """Captures the stack trace of the current exception to be thrown later""" + def __init__(self, msg=""): + Exception.__init__(self, msg+"\n"+format_exc()) + +class StoppableThread(Thread): + """ + Base class for threads that do something in a loop and periodically check + to see if they have been stopped. + """ + def __init__(self): + self.stopped = False + self.error = None + Thread.__init__(self) + + def stop(self): + self.stopped = True + join(self) + if self.error: raise self.error + +# Options for a client that wants to reconnect automatically. +RECONNECT_OPTIONS="reconnect:true,reconnect-timeout:10,reconnect-urls-replace:true" + +class NumberedSender(Thread): + """ + Thread to run a sender client and send numbered messages until stopped. + """ + + def __init__(self, broker, max_depth=None, queue="test-queue", + connection_options=RECONNECT_OPTIONS, + failover_updates=False, url=None, args=[]): + """ + max_depth: enable flow control, ensure sent - received <= max_depth. + Requires self.notify_received(n) to be called each time messages are received. + """ + Thread.__init__(self) + cmd = ["qpid-send", + "--broker", url or broker.host_port(), + "--address", "%s;{create:always}"%queue, + "--connection-options", "{%s}"%(broker.test.protocol_option(connection_options)), + "--content-stdin" + ] + args + if failover_updates: cmd += ["--failover-updates"] + self.sender = broker.test.popen( + cmd, expect=EXPECT_RUNNING, stdin=PIPE) + self.condition = Condition() + self.max = max_depth + self.received = 0 + self.stopped = False + self.error = None + self.queue = queue + + def write_message(self, n): + self.sender.stdin.write(str(n)+"\n") + self.sender.stdin.flush() + + def run(self): + try: + self.sent = 0 + while not self.stopped: + self.sender.assert_running() + if self.max: + self.condition.acquire() + while not self.stopped and self.sent - self.received > self.max: + self.condition.wait() + self.condition.release() + self.write_message(self.sent) + self.sent += 1 + except Exception, e: + self.error = RethrownException( + "%s: (%s)%s"%(self.sender.pname,e, + error_line(self.sender.outfile("err")))) + + + def notify_received(self, count): + """Called by receiver to enable flow control. count = messages received so far.""" + self.condition.acquire() + self.received = count + self.condition.notify() + self.condition.release() + + def stop(self): + self.condition.acquire() + try: + self.stopped = True + self.condition.notify() + finally: self.condition.release() + join(self) + self.write_message(-1) # end-of-messages marker. + if self.error: raise self.error + +class NumberedReceiver(Thread): + """ + Thread to run a receiver client and verify it receives + sequentially numbered messages. + """ + def __init__(self, broker, sender=None, queue="test-queue", + connection_options=RECONNECT_OPTIONS, + failover_updates=False, url=None, args=[]): + """ + sender: enable flow control. Call sender.received(n) for each message received. + """ + Thread.__init__(self) + self.test = broker.test + cmd = ["qpid-receive", + "--broker", url or broker.host_port(), + "--address", "%s;{create:always}"%queue, + "--connection-options", "{%s}"%(broker.test.protocol_option(connection_options)), + "--forever" + ] + if failover_updates: cmd += [ "--failover-updates" ] + cmd += args + self.receiver = self.test.popen( + cmd, expect=EXPECT_RUNNING, stdout=PIPE) + self.lock = Lock() + self.error = None + self.sender = sender + self.received = 0 + self.queue = queue + + def read_message(self): + n = int(self.receiver.stdout.readline()) + return n + + def run(self): + try: + m = self.read_message() + while m != -1: + self.receiver.assert_running() + assert m <= self.received, "%s missing message %s>%s"%(self.queue, m, self.received) + if (m == self.received): # Ignore duplicates + self.received += 1 + if self.sender: + self.sender.notify_received(self.received) + m = self.read_message() + except Exception, e: + self.error = RethrownException( + "%s: (%s)%s"%(self.receiver.pname,e, + error_line(self.receiver.outfile("err")))) + + def check(self): + """Raise an exception if there has been an error""" + if self.error: raise self.error + + def stop(self): + """Returns when termination message is received""" + join(self) + self.check() + +def import_script(path): + """ + Import executable script at path as a module. + Requires some trickery as scripts are not in standard module format + """ + f = open(path) + try: + name=os.path.split(path)[1].replace("-","_") + return imp.load_module(name, f, path, ("", "r", imp.PY_SOURCE)) + finally: f.close() diff --git a/qpid/cpp/src/tests/cli_tests.py b/qpid/cpp/src/tests/cli_tests.py new file mode 100755 index 0000000000..eee9bc648c --- /dev/null +++ b/qpid/cpp/src/tests/cli_tests.py @@ -0,0 +1,477 @@ +#!/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. +# + +import sys +import os +import imp +from qpid.testlib import TestBase010 +from qpid.datatypes import Message +from qpid.queue import Empty +from time import sleep + +def import_script(path): + """ + Import executable script at path as a module. + Requires some trickery as scripts are not in standard module format + """ + f = open(path) + try: + name=os.path.split(path)[1].replace("-","_") + return imp.load_module(name, f, path, ("", "r", imp.PY_SOURCE)) + finally: f.close() + +def checkenv(name): + value = os.getenv(name) + if not value: raise Exception("Environment variable %s is not set" % name) + return value + +class CliTests(TestBase010): + + def remote_host(self): + return self.defines.get("remote-host", "localhost") + + def remote_port(self): + return int(self.defines["remote-port"]) + + def cli_dir(self): + return self.defines["cli-dir"] + + def makeQueue(self, qname, arguments, api=False): + if api: + ret = self.qpid_config_api(" add queue " + qname + " " + arguments) + else: + ret = os.system(self.qpid_config_command(" add queue " + qname + " " + arguments)) + + self.assertEqual(ret, 0) + queue = self.broker_access.getQueue(qname) + if queue: + return queue + assert False + + def test_queue_params(self): + self.startBrokerAccess() + queue1 = self.makeQueue("test_queue_params1", "--limit-policy none") + queue2 = self.makeQueue("test_queue_params2", "--limit-policy reject") + queue3 = self.makeQueue("test_queue_params3", "--limit-policy ring") + + LIMIT = "qpid.policy_type" + assert LIMIT not in queue1.arguments + self.assertEqual(queue2.arguments[LIMIT], "reject") + self.assertEqual(queue3.arguments[LIMIT], "ring") + + queue4 = self.makeQueue("test_queue_params4", "--lvq-key lkey") + + LVQKEY = "qpid.last_value_queue_key" + + assert LVQKEY not in queue3.arguments + assert LVQKEY in queue4.arguments + assert queue4.arguments[LVQKEY] == "lkey" + + def test_queue_params_api(self): + self.startBrokerAccess() + queue1 = self.makeQueue("test_queue_params_api1", "--limit-policy none", True) + queue2 = self.makeQueue("test_queue_params_api2", "--limit-policy reject", True) + queue3 = self.makeQueue("test_queue_params_api3", "--limit-policy ring", True) + + LIMIT = "qpid.policy_type" + assert LIMIT not in queue1.arguments + self.assertEqual(queue2.arguments[LIMIT], "reject") + self.assertEqual(queue3.arguments[LIMIT], "ring") + + queue4 = self.makeQueue("test_queue_params_api4", "--lvq-key lkey") + + LVQKEY = "qpid.last_value_queue_key" + + assert LVQKEY not in queue3.arguments + assert LVQKEY in queue4.arguments + assert queue4.arguments[LVQKEY] == "lkey" + + + def test_qpid_config(self): + self.startBrokerAccess(); + qname = "test_qpid_config" + + ret = os.system(self.qpid_config_command(" add queue " + qname)) + self.assertEqual(ret, 0) + queues = self.broker_access.getAllQueues() + found = False + for queue in queues: + if queue.name == qname: + self.assertEqual(queue.durable, False) + found = True + self.assertEqual(found, True) + + ret = os.system(self.qpid_config_command(" del queue " + qname)) + self.assertEqual(ret, 0) + queues = self.broker_access.getAllQueues() + found = False + for queue in queues: + if queue.name == qname: + found = True + self.assertEqual(found, False) + + def test_qpid_config_del_nonempty_queue(self): + self.startBrokerAccess(); + qname = "test_qpid_config_del" + + ret = os.system(self.qpid_config_command(" add queue " + qname)) + self.assertEqual(ret, 0) + queues = self.broker_access.getAllQueues() + found = False + for queue in queues: + if queue.name == qname: + self.assertEqual(queue.durable, False) + found = True + self.assertEqual(found, True) + + self.startBrokerAccess() + + sess = self.broker_conn.session() + tx = sess.sender(qname) + tx.send("MESSAGE") + + ret = os.system(self.qpid_config_command(" del queue " + qname)) + queues = self.broker_access.getAllQueues() + found = False + for queue in queues: + if queue.name == qname: + found = True + self.assertEqual(found, True) + + ret = os.system(self.qpid_config_command(" del queue " + qname + " --force")) + self.assertEqual(ret, 0) + queues = self.broker_access.getAllQueues() + found = False + for queue in queues: + if queue.name == qname: + found = True + self.assertEqual(found, False) + + + def test_qpid_config_api(self): + self.startBrokerAccess(); + qname = "test_qpid_config_api" + + ret = self.qpid_config_api(" add queue " + qname) + self.assertEqual(ret, 0) + queues = self.broker_access.getAllQueues() + found = False + for queue in queues: + if queue.name == qname: + self.assertEqual(queue.durable, False) + found = True + self.assertEqual(found, True) + + ret = self.qpid_config_api(" del queue " + qname) + self.assertEqual(ret, 0) + queues = self.broker_access.getAllQueues() + found = False + for queue in queues: + if queue.name == qname: + found = True + self.assertEqual(found, False) + + + def test_qpid_config_sasl_plain_expect_succeed(self): + self.startBrokerAccess(); + qname = "test_qpid_config_sasl_plain_expect_succeed" + cmd = " --sasl-mechanism PLAIN -b guest/guest@localhost:"+str(self.broker.port) + " add queue " + qname + ret = self.qpid_config_api(cmd) + self.assertEqual(ret, 0) + + def test_qpid_config_sasl_plain_expect_fail(self): + """Fails because no user name and password is supplied""" + self.startBrokerAccess(); + qname = "test_qpid_config_sasl_plain_expect_fail" + cmd = " --sasl-mechanism PLAIN -b localhost:"+str(self.broker.port) + " add queue " + qname + ret = self.qpid_config_api(cmd) + assert ret != 0 + + # helpers for some of the test methods + def helper_find_exchange(self, xchgname, typ, expected=True): + xchgs = self.broker_access.getAllExchanges() + found = False + for xchg in xchgs: + if xchg.name == xchgname: + if typ: + self.assertEqual(xchg.type, typ) + found = True + self.assertEqual(found, expected) + + def helper_create_exchange(self, xchgname, typ="direct", opts=""): + foo = self.qpid_config_command(opts + " add exchange " + typ + " " + xchgname) + # print foo + ret = os.system(foo) + self.assertEqual(ret, 0) + self.helper_find_exchange(xchgname, typ, True) + + def helper_destroy_exchange(self, xchgname): + foo = self.qpid_config_command(" del exchange " + xchgname) + # print foo + ret = os.system(foo) + self.assertEqual(ret, 0) + self.helper_find_exchange(xchgname, False, expected=False) + + def helper_find_queue(self, qname, expected=True): + queues = self.broker_access.getAllQueues() + found = False + for queue in queues: + if queue.name == qname: + self.assertEqual(queue.durable, False) + found = True + self.assertEqual(found, expected) + + def helper_create_queue(self, qname): + foo = self.qpid_config_command(" add queue " + qname) + # print foo + ret = os.system(foo) + self.assertEqual(ret, 0) + self.helper_find_queue(qname, True) + + def helper_destroy_queue(self, qname): + foo = self.qpid_config_command(" del queue " + qname) + # print foo + ret = os.system(foo) + self.assertEqual(ret, 0) + self.helper_find_queue(qname, False) + + # test the bind-queue-to-header-exchange functionality + def test_qpid_config_headers(self): + self.startBrokerAccess(); + qname = "test_qpid_config" + xchgname = "test_xchg" + + # first create a header xchg + self.helper_create_exchange(xchgname, typ="headers") + + # create the queue + self.helper_create_queue(qname) + + # now bind the queue to the xchg + foo = self.qpid_config_command(" bind " + xchgname + " " + qname + + " key all foo=bar baz=quux") + # print foo + ret = os.system(foo) + self.assertEqual(ret, 0) + + # he likes it, mikey. Ok, now tear it all down. first the binding + ret = os.system(self.qpid_config_command(" unbind " + xchgname + " " + qname + + " key")) + self.assertEqual(ret, 0) + + # then the queue + self.helper_destroy_queue(qname) + + # then the exchange + self.helper_destroy_exchange(xchgname) + + + def test_qpid_config_xml(self): + self.startBrokerAccess(); + qname = "test_qpid_config" + xchgname = "test_xchg" + + # first create a header xchg + self.helper_create_exchange(xchgname, typ="xml") + + # create the queue + self.helper_create_queue(qname) + + # now bind the queue to the xchg + foo = self.qpid_config_command("-f test.xquery bind " + xchgname + " " + qname) + # print foo + ret = os.system(foo) + self.assertEqual(ret, 0) + + # he likes it, mikey. Ok, now tear it all down. first the binding + ret = os.system(self.qpid_config_command(" unbind " + xchgname + " " + qname + + " key")) + self.assertEqual(ret, 0) + + # then the queue + self.helper_destroy_queue(qname) + + # then the exchange + self.helper_destroy_exchange(xchgname) + + def test_qpid_config_durable(self): + self.startBrokerAccess(); + qname = "test_qpid_config" + + ret = os.system(self.qpid_config_command(" add queue --durable " + qname)) + self.assertEqual(ret, 0) + queues = self.broker_access.getAllQueues() + found = False + for queue in queues: + if queue.name == qname: + self.assertEqual(queue.durable, True) + found = True + self.assertEqual(found, True) + + ret = os.system(self.qpid_config_command(" del queue " + qname)) + self.assertEqual(ret, 0) + queues = self.broker_access.getAllQueues() + found = False + for queue in queues: + if queue.name == qname: + found = True + self.assertEqual(found, False) + + def test_qpid_config_altex(self): + self.startBrokerAccess(); + exName = "testalt" + qName = "testqalt" + altName = "amq.direct" + + ret = os.system(self.qpid_config_command(" add exchange topic %s --alternate-exchange=%s" % (exName, altName))) + self.assertEqual(ret, 0) + + exchanges = self.broker_access.getAllExchanges() + found = False + for exchange in exchanges: + if exchange.name == altName: + self.assertEqual(exchange.altExchange, None) + + if exchange.name == exName: + found = True + if not exchange.altExchange: + self.fail("Alternate exchange not set") + self.assertEqual(exchange.altExchange, altName) + self.assertEqual(found, True) + + ret = os.system(self.qpid_config_command(" add queue %s --alternate-exchange=%s" % (qName, altName))) + self.assertEqual(ret, 0) + + ret = os.system(self.qpid_config_command(" queues")) + self.assertEqual(ret, 0) + + queues = self.broker_access.getAllQueues() + found = False + for queue in queues: + if queue.name == qName: + found = True + if not queue.altExchange: + self.fail("Alternate exchange not set") + self.assertEqual(queue.altExchange, altName) + self.assertEqual(found, True) + + def test_qpid_config_list_queues_arguments(self): + """ + Test to verify that when the type of a policy limit is + actually a string (though still a valid value), it does not + upset qpid-config + """ + self.startBrokerAccess(); + + names = ["queue_capacity%s" % (i) for i in range(1, 6)] + for name in names: + self.session.queue_declare(queue=name, exclusive=True, + arguments={'qpid.max_count' : str(i), 'qpid.max_size': '100'}) + + output = os.popen(self.qpid_config_command(" queues")).readlines() + queues = [line.split()[0] for line in output[2:len(output)]] #ignore first two lines (header) + + for name in names: + assert name in queues, "%s not in %s" % (name, queues) + + def test_qpid_route(self): + self.startBrokerAccess(); + + command = self.cli_dir() + "/qpid-route dynamic add guest/guest@localhost:%d %s:%d amq.topic" %\ + (self.broker.port, self.remote_host(), self.remote_port()) + ret = os.system(command) + self.assertEqual(ret, 0) + + links = self.broker_access.getAllLinks() + found = False + for link in links: + if link.port == self.remote_port(): + found = True + self.assertEqual(found, True) + + def test_qpid_route_api(self): + self.startBrokerAccess(); + + ret = self.qpid_route_api("dynamic add " + + "guest/guest@localhost:"+str(self.broker.port) + " " + + str(self.remote_host())+":"+str(self.remote_port()) + " " + +"amq.direct") + + self.assertEqual(ret, 0) + + links = self.broker_access.getAllLinks() + found = False + for link in links: + if link.port == self.remote_port(): + found = True + self.assertEqual(found, True) + + + def test_qpid_route_api(self): + self.startBrokerAccess(); + + ret = self.qpid_route_api("dynamic add " + + " --client-sasl-mechanism PLAIN " + + "guest/guest@localhost:"+str(self.broker.port) + " " + + str(self.remote_host())+":"+str(self.remote_port()) + " " + +"amq.direct") + + self.assertEqual(ret, 0) + + links = self.broker_access.getAllLinks() + found = False + for link in links: + if link.port == self.remote_port(): + found = True + self.assertEqual(found, True) + + def test_qpid_route_api_expect_fail(self): + self.startBrokerAccess(); + + ret = self.qpid_route_api("dynamic add " + + " --client-sasl-mechanism PLAIN " + + "localhost:"+str(self.broker.port) + " " + + str(self.remote_host())+":"+str(self.remote_port()) + " " + +"amq.direct") + assert ret != 0 + + + def getProperty(self, msg, name): + for h in msg.headers: + if hasattr(h, name): return getattr(h, name) + return None + + def getAppHeader(self, msg, name): + headers = self.getProperty(msg, "application_headers") + if headers: + return headers[name] + return None + + def qpid_config_command(self, arg = ""): + return self.cli_dir() + "/qpid-config -b localhost:%d" % self.broker.port + " " + arg + + def qpid_config_api(self, arg = ""): + script = import_script(checkenv("QPID_CONFIG_EXEC")) + broker = ["-b", "localhost:"+str(self.broker.port)] + return script.main(broker + arg.split()) + + def qpid_route_api(self, arg = ""): + script = import_script(checkenv("QPID_ROUTE_EXEC")) + return script.main(arg.split()) diff --git a/qpid/cpp/src/tests/config.null b/qpid/cpp/src/tests/config.null new file mode 100644 index 0000000000..e2f355768b --- /dev/null +++ b/qpid/cpp/src/tests/config.null @@ -0,0 +1,21 @@ +# +# 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. +# + +# Deliberately empty configuration file for tests. + diff --git a/qpid/cpp/src/tests/consume.cpp b/qpid/cpp/src/tests/consume.cpp new file mode 100644 index 0000000000..69110d151f --- /dev/null +++ b/qpid/cpp/src/tests/consume.cpp @@ -0,0 +1,131 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include <algorithm> +#include <iostream> +#include <memory> +#include <sstream> +#include <vector> + +#include "TestOptions.h" +#include "qpid/client/Connection.h" +#include "qpid/client/Message.h" +#include "qpid/client/Session.h" +#include "qpid/client/SubscriptionManager.h" + +using namespace qpid; +using namespace qpid::client; +using namespace qpid::sys; +using namespace std; + +namespace qpid { +namespace tests { + +typedef vector<string> StringSet; + +struct Args : public qpid::TestOptions { + uint count; + uint ack; + string queue; + bool declare; + bool summary; + bool print; + bool durable; + + Args() : count(1000), ack(0), queue("publish-consume"), + declare(false), summary(false), print(false) + { + addOptions() + ("count", optValue(count, "N"), "number of messages to publish") + ("ack-frequency", optValue(ack, "N"), "ack every N messages (0 means use no-ack mode)") + ("queue", optValue(queue, "<queue name>"), "queue to consume from") + ("declare", optValue(declare), "declare the queue") + ("durable", optValue(durable), "declare the queue durable, use with declare") + ("print-data", optValue(print), "Print the recieved data at info level") + ("s,summary", optValue(summary), "Print undecorated rate."); + } +}; + +Args opts; + +struct Client +{ + Connection connection; + Session session; + + Client() + { + opts.open(connection); + session = connection.newSession(); + } + + void consume() + { + if (opts.declare) + session.queueDeclare(arg::queue=opts.queue, arg::durable=opts.durable); + SubscriptionManager subs(session); + LocalQueue lq; + SubscriptionSettings settings; + settings.acceptMode = opts.ack > 0 ? ACCEPT_MODE_EXPLICIT : ACCEPT_MODE_NONE; + settings.flowControl = FlowControl(opts.count, SubscriptionManager::UNLIMITED,false); + Subscription sub = subs.subscribe(lq, opts.queue, settings); + Message msg; + AbsTime begin=now(); + for (size_t i = 0; i < opts.count; ++i) { + msg=lq.pop(); + QPID_LOG(info, "Received: " << msg.getMessageProperties().getCorrelationId()); + if (opts.print) QPID_LOG(info, "Data: " << msg.getData()); + } + if (opts.ack != 0) + sub.accept(sub.getUnaccepted()); // Cumulative ack for final batch. + AbsTime end=now(); + double secs(double(Duration(begin,end))/TIME_SEC); + if (opts.summary) cout << opts.count/secs << endl; + else cout << "Time: " << secs << "s Rate: " << opts.count/secs << endl; + } + + ~Client() + { + try{ + session.close(); + connection.close(); + } catch(const exception& e) { + cout << e.what() << endl; + } + } +}; + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char** argv) +{ + try { + opts.parse(argc, argv); + Client client; + client.consume(); + return 0; + } catch(const exception& e) { + cout << e.what() << endl; + } + return 1; +} diff --git a/qpid/cpp/src/tests/datagen.cpp b/qpid/cpp/src/tests/datagen.cpp new file mode 100644 index 0000000000..acbc07d63c --- /dev/null +++ b/qpid/cpp/src/tests/datagen.cpp @@ -0,0 +1,103 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include <exception> +#include <iostream> +#include <stdlib.h> +#include <time.h> +#include "qpid/Options.h" + +namespace qpid { +namespace tests { + +struct Args : public qpid::Options +{ + uint count; + uint minSize; + uint maxSize; + uint minChar; + uint maxChar; + bool help; + + Args() : qpid::Options("Random data generator"), + count(1), minSize(8), maxSize(4096), + minChar(32), maxChar(126),//safely printable ascii chars + help(false) + { + addOptions() + ("count", qpid::optValue(count, "N"), "number of data strings to generate") + ("min-size", qpid::optValue(minSize, "N"), "minimum size of data string") + ("max-size", qpid::optValue(maxSize, "N"), "maximum size of data string") + ("min-char", qpid::optValue(minChar, "N"), "minimum char value used in data string") + ("max-char", qpid::optValue(maxChar, "N"), "maximum char value used in data string") + ("help", qpid::optValue(help), "print this usage statement"); + } + + bool parse(int argc, char** argv) { + try { + qpid::Options::parse(argc, argv); + if (maxSize < minSize) throw qpid::Options::Exception("max-size must be greater than min-size"); + if (maxChar < minChar) throw qpid::Options::Exception("max-char must be greater than min-char"); + + if (help) { + std::cerr << *this << std::endl << std::endl; + } else { + return true; + } + } catch (const std::exception& e) { + std::cerr << *this << std::endl << std::endl << e.what() << std::endl; + } + return false; + } + +}; + +uint random(uint min, uint max) +{ + return (rand() % (max-min+1)) + min; +} + +std::string generateData(uint size, uint min, uint max) +{ + std::string data; + for (uint i = 0; i < size; i++) { + data += (char) random(min, max); + } + return data; +} + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char** argv) +{ + Args opts; + if (opts.parse(argc, argv)) { + srand(time(0)); + for (uint i = 0; i < opts.count; i++) { + std::cout << generateData(random(opts.minSize, opts.maxSize), opts.minChar, opts.maxChar) << std::endl; + } + return 0; + } else { + return 1; + } +} diff --git a/qpid/cpp/src/tests/declare_queues.cpp b/qpid/cpp/src/tests/declare_queues.cpp new file mode 100644 index 0000000000..bf85b9c04b --- /dev/null +++ b/qpid/cpp/src/tests/declare_queues.cpp @@ -0,0 +1,101 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include <qpid/client/FailoverManager.h> +#include <qpid/client/Session.h> +#include <qpid/sys/Time.h> +#include <qpid/Exception.h> + +#include <cstdlib> +#include <iostream> +#include <sstream> + +using namespace qpid::client; + +using namespace std; + +int +main(int argc, char ** argv) +{ + ConnectionSettings settings; + if ( argc != 6 ) + { + cerr << "Usage: declare_queues host port durability queue_name_prefix n_queues\n"; + return 1; + } + + settings.host = argv[1]; + settings.port = atoi(argv[2]); + int durability = atoi(argv[3]); + char const * queue_name_prefix = argv[4]; + int n_queues = atoi(argv[5]); + + FailoverManager connection(settings); + + int max_fail = 13; + for ( int i = 0; i < n_queues; ++ i ) { + stringstream queue_name; + queue_name << queue_name_prefix << '_' << i; + + bool queue_created = false; + int failure_count; + + // Any non-transport failure is Bad. + try + { + while ( ! queue_created ) { + Session session = connection.connect().newSession(); + // TransportFailures aren't too bad -- they might happen because + // we are doing a cluster failover test. But if we get too many, + // we will still bug out. + failure_count = 0; + try { + if ( durability ) + session.queueDeclare(arg::queue=queue_name.str(), arg::durable=true); + else + session.queueDeclare(arg::queue=queue_name.str()); + queue_created = true; + cout << "declare_queues: Created queue " << queue_name.str() << endl; + } + catch ( const qpid::TransportFailure& ) { + if ( ++ failure_count >= max_fail ) { + cerr << "declare_queues failed: too many transport errors.\n"; + cerr << " host: " << settings.host + << " port: " << settings.port << endl; + return 1; + } + qpid::sys::sleep ( 1 ); + } + } + } + catch ( const exception & error) { + cerr << "declare_queues failed:" << error.what() << endl; + cerr << " host: " << settings.host + << " port: " << settings.port << endl; + return 1; + } + } +} + + + + + diff --git a/qpid/cpp/src/tests/dlclose_noop.c b/qpid/cpp/src/tests/dlclose_noop.c new file mode 100644 index 0000000000..b78cf486d8 --- /dev/null +++ b/qpid/cpp/src/tests/dlclose_noop.c @@ -0,0 +1,30 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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. + * + */ + +/* + * Loaded via LD_PRELOAD this will turn dlclose into a no-op. + * + * Allows valgrind to generate useful reports from programs that + * dynamically unload libraries before exit, such as CppUnit's + * DllPlugInTester. + * + */ + +#include <stdio.h> +void* dlclose(void* handle) { return 0; } + diff --git a/qpid/cpp/src/tests/dynamic_log_hires_timestamp b/qpid/cpp/src/tests/dynamic_log_hires_timestamp new file mode 100755 index 0000000000..75034f9902 --- /dev/null +++ b/qpid/cpp/src/tests/dynamic_log_hires_timestamp @@ -0,0 +1,75 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Run a simple test to verify dynamic log highres timestamp changes +source ./test_env.sh +test -d $PYTHON_DIR || { echo "Skipping python tests, no python dir."; exit 0; } + +LOG_FILE=hires_test.log +trap cleanup EXIT + +cleanup() { + test -n "$PORT" && $QPIDD_EXEC --no-module-dir --quit --port $PORT +} + +error() { + echo $*; + exit 1; +} + +rm -rf $LOG_FILE +PORT=$($QPIDD_EXEC --auth=no --no-module-dir --daemon --port=0 --interface 127.0.0.1 --log-to-file $LOG_FILE) || error "Could not start broker" + +echo Broker for log highres timestamp test started on $PORT, pid is $($QPIDD_EXEC --no-module-dir --check --port $PORT) + +$srcdir/qpid-ctrl -b localhost:$PORT setLogLevel level='debug+:Broker' > /dev/null +$srcdir/qpid-ctrl -b localhost:$PORT echo sequence=1 body=LOWRES > /dev/null +$srcdir/qpid-ctrl -b localhost:$PORT setLogHiresTimestamp logHires='true' > /dev/null +$srcdir/qpid-ctrl -b localhost:$PORT echo sequence=2 body=HI_RES > /dev/null +$srcdir/qpid-ctrl -b localhost:$PORT setLogHiresTimestamp logHires='false' > /dev/null +$srcdir/qpid-ctrl -b localhost:$PORT echo sequence=3 body=LOWRES > /dev/null + +# Expect 3 log entries with 'echo' in them +if [[ $(grep echo $LOG_FILE | wc -l) -ne 3 ]]; then + cat $LOG_FILE + error "Log content error - expected 3 echo log entries" +fi + +# Lines 1 and 3 are length X +# Line 2 is length X+10 because of timestamp addition +LEN1=$(grep echo $LOG_FILE | grep \(1 | wc -m) +LEN2=$(grep echo $LOG_FILE | grep \(2 | wc -m) +LEN3=$(grep echo $LOG_FILE | grep \(3 | wc -m) +EXPECTED_LEN2=$(( $LEN1 + 10 )) + +if [ $LEN1 -ne $LEN3 ]; then + cat $LOG_FILE + error "Log content error - expected echo 3 to be same line length as echo 1" +fi + +if [ $LEN2 -ne $EXPECTED_LEN2 ]; then + cat $LOG_FILE + error "Log content error - expected echo 2 to be 10 characters longer than echo 1" +fi + +rm -rf $LOG_FILE +echo OK + diff --git a/qpid/cpp/src/tests/dynamic_log_level_test b/qpid/cpp/src/tests/dynamic_log_level_test new file mode 100755 index 0000000000..f8fd7a8dd8 --- /dev/null +++ b/qpid/cpp/src/tests/dynamic_log_level_test @@ -0,0 +1,90 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Run a simple test to verify dynamic log level changes +source ./test_env.sh +test -d $PYTHON_DIR || { echo "Skipping python tests, no python dir."; exit 0; } + +LOG_FILE=log_test.log +trap cleanup EXIT + +cleanup() { + test -n "$PORT" && $QPIDD_EXEC --no-module-dir --quit --port $PORT +} + +error() { + echo $*; + exit 1; +} + +checklog() { + if [[ $(grep echo $LOG_FILE | wc -l) -ne $1 ]]; then + cat $LOG_FILE + error "Log contents not as expected - " $2 + fi +} + +rm -rf $LOG_FILE +PORT=$($QPIDD_EXEC --auth=no --no-module-dir --daemon --port=0 --interface 127.0.0.1 --log-to-file $LOG_FILE) || error "Could not start broker" + +echo Broker for log level test started on $PORT, pid is $($QPIDD_EXEC --no-module-dir --check --port $PORT) + +# Set level to notice+ and send an echo request +# The 'echo' in the log is hidden since it is at debug level. +$srcdir/qpid-ctrl -b localhost:$PORT setLogLevel level='notice+' > /dev/null +$srcdir/qpid-ctrl -b localhost:$PORT echo sequence=1 body=HIDDEN > /dev/null +checklog 0 "Step 1 Expected no echo log entries" + +# Next, enable all Broker logs at debug and higher levels and send another echo +# This 'echo' should be in the log. +$srcdir/qpid-ctrl -b localhost:$PORT setLogLevel level='debug+:Broker' > /dev/null +$srcdir/qpid-ctrl -b localhost:$PORT echo sequence=2 body=VISIBLE > /dev/null +checklog 1 "Step 2 Expected one echo log entry" + +# Now turn on Broker debug messages but specifically disable ManagementMethod logs +# The 'echo' should be hidden. +$srcdir/qpid-ctrl -b localhost:$PORT setLogLevel level='debug+:Broker !debug+:broker::Broker::ManagementMethod' > /dev/null +$srcdir/qpid-ctrl -b localhost:$PORT echo sequence=3 body=HIDDEN > /dev/null +checklog 1 "Step 3 Expected one echo log entry" + +# Verify that the management get returns what was just set +$srcdir/qpid-ctrl -b localhost:$PORT getLogLevel > dynamic_log_level.tmp +if [[ $(grep 'level=debug+:Broker,!debug+:broker::Broker::ManagementMethod' dynamic_log_level.tmp | wc -l) -ne 1 ]]; then + error "Step 4 getLogLevel returned unexpected value: " `cat dynamic_log_level.tmp` +fi +rm -rf dynamic_log_level.tmp + +cleanup + +# Start another broker with --log-disable settings and make sure the management string receives them +rm -rf $LOG_FILE +PORT=$($QPIDD_EXEC --auth=no --no-module-dir --daemon --port=0 --interface 127.0.0.1 --log-to-file $LOG_FILE --log-enable debug:foo --log-disable debug:bar) || error "Could not start broker" +echo Broker for log level test started on $PORT, pid is $($QPIDD_EXEC --no-module-dir --check --port $PORT) + +$srcdir/qpid-ctrl -b localhost:$PORT getLogLevel > dynamic_log_level.tmp +if [[ $(grep 'level=debug:foo,!debug:bar' dynamic_log_level.tmp | wc -l) -ne 1 ]]; then + error "Step 5 getLogLevel returned unexpected value: " `cat dynamic_log_level.tmp` +fi +rm -rf dynamic_log_level.tmp + +rm -rf $LOG_FILE +echo OK + diff --git a/qpid/cpp/src/tests/echotest.cpp b/qpid/cpp/src/tests/echotest.cpp new file mode 100644 index 0000000000..7c30989098 --- /dev/null +++ b/qpid/cpp/src/tests/echotest.cpp @@ -0,0 +1,157 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include <qpid/client/Connection.h> +#include <qpid/client/SubscriptionManager.h> +#include <qpid/client/AsyncSession.h> +#include <qpid/client/Message.h> +#include <qpid/client/MessageListener.h> +#include <qpid/sys/Time.h> +#include <qpid/Options.h> + +#include <iostream> + +using namespace qpid::client; +using namespace qpid::framing; +using namespace qpid::sys; +using namespace std; + +namespace qpid { +namespace tests { + +struct Args : public qpid::Options, + public qpid::client::ConnectionSettings +{ + bool help; + uint count; + uint size; + bool summary; + + Args() : qpid::Options("Simple latency test optins"), help(false), count(20), size(0), summary() + { + using namespace qpid; + addOptions() + ("help", optValue(help), "Print this usage statement") + ("count", optValue(count, "N"), "Number of messages to send") + ("size", optValue(count, "N"), "Size of messages") + ("broker,b", optValue(host, "HOST"), "Broker host to connect to") + ("port,p", optValue(port, "PORT"), "Broker port to connect to") + ("username", optValue(username, "USER"), "user name for broker log in.") + ("password", optValue(password, "PASSWORD"), "password for broker log in.") + ("mechanism", optValue(mechanism, "MECH"), "SASL mechanism to use when authenticating.") + ("tcp-nodelay", optValue(tcpNoDelay), "Turn on tcp-nodelay") + ("s,summary", optValue(summary), "Print only average latency."); + } +}; + +uint64_t current_time() +{ + return Duration::FromEpoch(); +} + +class Listener : public MessageListener +{ + private: + Session session; + SubscriptionManager subscriptions; + uint counter; + const uint limit; + std::string queue; + Message request; + double total, min, max; + bool summary; + + public: + Listener(Session& session, uint limit, bool summary); + void start(uint size); + void received(Message& message); +}; + +Listener::Listener(Session& s, uint l, bool summary_) : + session(s), subscriptions(s), counter(0), limit(l), + queue(session.getId().getName()), total(), + min(std::numeric_limits<double>::max()), max(), summary(summary_) +{} + +void Listener::start(uint size) +{ + session.queueDeclare(arg::queue=queue, arg::exclusive=true, arg::autoDelete=true); + request.getDeliveryProperties().setRoutingKey(queue); + subscriptions.subscribe(*this, queue, SubscriptionSettings(FlowControl::unlimited(), ACCEPT_MODE_NONE)); + + request.getDeliveryProperties().setTimestamp(current_time()); + if (size) request.setData(std::string(size, 'X')); + async(session).messageTransfer(arg::content=request); + subscriptions.run(); +} + +void Listener::received(Message& response) +{ + //extract timestamp and compute latency: + uint64_t sentAt = response.getDeliveryProperties().getTimestamp(); + uint64_t receivedAt = current_time(); + + double latency = ((double) (receivedAt - sentAt)) / TIME_MSEC; + if (!summary) cout << "Latency: " << latency << "ms" << endl; + min = std::min(latency, min); + max = std::max(latency, max); + total += latency; + + if (++counter < limit) { + request.getDeliveryProperties().setTimestamp(current_time()); + async(session).messageTransfer(arg::content=request); + } else { + subscriptions.cancel(queue); + if (summary) cout << min << "\t" << max << "\t" << total/limit << endl; + else cout << "min: " << min << " max: " << max << " average: " << total/limit << endl; + } +} + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char** argv) +{ + Args opts; + opts.parse(argc, argv); + + if (opts.help) { + std::cout << opts << std::endl; + return 0; + } + + Connection connection; + try { + connection.open(opts); + Session session = connection.newSession(); + Listener listener(session, opts.count, opts.summary); + listener.start(opts.size); + + connection.close(); + return 0; + } catch(const std::exception& error) { + std::cout << error.what() << std::endl; + } + return 1; +} + + diff --git a/qpid/cpp/src/tests/exception_test.cpp b/qpid/cpp/src/tests/exception_test.cpp new file mode 100644 index 0000000000..3e844b4e58 --- /dev/null +++ b/qpid/cpp/src/tests/exception_test.cpp @@ -0,0 +1,125 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "unit_test.h" +#include "test_tools.h" +#include "BrokerFixture.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/client/MessageListener.h" +#include "qpid/sys/Runnable.h" +#include "qpid/sys/Thread.h" +#include "qpid/framing/reply_exceptions.h" + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(exception_test) + +// FIXME aconway 2008-06-12: need to update our exception handling to +// 0-10 handling and extend this test to provoke all the exceptional +// conditions we know of and verify the correct exception is thrown. + +using namespace std; +using namespace qpid; +using namespace sys; +using namespace client; +using namespace framing; + +using qpid::broker::Broker; +using boost::bind; +using boost::function; + +template <class Ex> +struct Catcher : public Runnable { + function<void ()> f; + bool caught; + Thread thread; + + Catcher(function<void ()> f_) : f(f_), caught(false), thread(this) {} + ~Catcher() { join(); } + + void run() { + try { + ScopedSuppressLogging sl; // Suppress messages for expected errors. + f(); + } + catch(const Ex& e) { + caught=true; + BOOST_MESSAGE(string("Caught expected exception: ")+e.what()+"["+typeid(e).name()+"]"); + } + catch(const std::exception& e) { + BOOST_ERROR(string("Bad exception: ")+e.what()+"["+typeid(e).name()+"] expected: "+typeid(Ex).name()); + } + catch(...) { + BOOST_ERROR(string("Bad exception: unknown")); + } + } + + bool join() { + if (thread) { + thread.join(); + thread=Thread(); + } + return caught; + } +}; + +QPID_AUTO_TEST_CASE(TestSessionBusy) { + SessionFixture f; + try { + ScopedSuppressLogging sl; // Suppress messages for expected errors. + f.connection.newSession(f.session.getId().getName()); + BOOST_FAIL("Expected SessionBusyException for " << f.session.getId().getName()); + } catch (const SessionBusyException&) {} // FIXME aconway 2008-09-22: client is not throwing correct exception. +} + +QPID_AUTO_TEST_CASE(DisconnectedPop) { + SessionFixture fix; + fix.session.queueDeclare(arg::queue="q"); + fix.subs.subscribe(fix.lq, "q"); + Catcher<TransportFailure> pop(bind(&LocalQueue::pop, &fix.lq, sys::TIME_SEC)); + fix.shutdownBroker(); + BOOST_CHECK(pop.join()); +} + +QPID_AUTO_TEST_CASE(DisconnectedListen) { + SessionFixture fix; + struct NullListener : public MessageListener { + void received(Message&) { BOOST_FAIL("Unexpected message"); } + } l; + fix.session.queueDeclare(arg::queue="q"); + fix.subs.subscribe(l, "q"); + + Catcher<TransportFailure> runner(bind(&SubscriptionManager::run, boost::ref(fix.subs))); + fix.shutdownBroker(); + runner.join(); + BOOST_CHECK_THROW(fix.session.queueDeclare(arg::queue="x"), TransportFailure); +} + +QPID_AUTO_TEST_CASE(NoSuchQueueTest) { + SessionFixture fix; + ScopedSuppressLogging sl; // Suppress messages for expected errors. + BOOST_CHECK_THROW(fix.subs.subscribe(fix.lq, "no such queue"), NotFoundException); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/failing-amqp0-10-python-tests b/qpid/cpp/src/tests/failing-amqp0-10-python-tests new file mode 100644 index 0000000000..afa263217f --- /dev/null +++ b/qpid/cpp/src/tests/failing-amqp0-10-python-tests @@ -0,0 +1,32 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +#The following four tests fail the because pure python client excludes +#the node type for queues from the reply-to address, weheras the swigged +#client does not (as that prevents it resolving the node on every send) +qpid.tests.messaging.message.MessageEchoTests.testReplyTo +qpid.tests.messaging.message.MessageEchoTests.testReplyToQueue +qpid.tests.messaging.message.MessageEchoTests.testReplyToQueueSubject +qpid.tests.messaging.message.MessageEchoTests.testProperties + +# The following test fails because the swig client throws an error +# when creating a sender with a node name that is ambiguous and no +# type specified. By contrast the pure python client defaults to the +# queue in this case. +qpid_tests.broker_0_10.new_api.GeneralTests.test_node_disambiguation_2 diff --git a/qpid/cpp/src/tests/failing-amqp1.0-python-tests b/qpid/cpp/src/tests/failing-amqp1.0-python-tests new file mode 100644 index 0000000000..e76d05d7be --- /dev/null +++ b/qpid/cpp/src/tests/failing-amqp1.0-python-tests @@ -0,0 +1,25 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +qpid_tests.broker_0_10.new_api.GeneralTests.test_qpid_3481_acquired_to_alt_exchange_2_consumers +qpid_tests.broker_0_10.new_api.GeneralTests.test_qpid_3481_acquired_to_alt_exchange +qpid_tests.broker_0_10.new_api.GeneralTests.test_nolocal_rerouted +qpid_tests.broker_0_10.new_api.GeneralTests.test_ambiguous_delete_1 +qpid_tests.broker_0_10.new_api.GeneralTests.test_ambiguous_delete_2 +qpid_tests.broker_0_10.new_api.GeneralTests.test_node_disambiguation_2 diff --git a/qpid/cpp/src/tests/fanout_perftest b/qpid/cpp/src/tests/fanout_perftest new file mode 100755 index 0000000000..168994d372 --- /dev/null +++ b/qpid/cpp/src/tests/fanout_perftest @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +exec `dirname $0`/run_perftest 10000 --mode fanout --npubs 16 --nsubs 16 --size 64 diff --git a/qpid/cpp/src/tests/federated_topic_test b/qpid/cpp/src/tests/federated_topic_test new file mode 100755 index 0000000000..2d31f9af5a --- /dev/null +++ b/qpid/cpp/src/tests/federated_topic_test @@ -0,0 +1,128 @@ +#!/bin/sh + +# +# 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. +# + +# Run the topic test on a federated setup + +# Clean up old log files +rm -f subscriber_*.log + +# Defaults values +SUBSCRIBERS=2 +MESSAGES=1000 +BATCHES=1 +VERBOSE=1 + +while getopts "s:m:b:" opt ; do + case $opt in + s) SUBSCRIBERS=$OPTARG ;; + m) MESSAGES=$OPTARG ;; + b) BATCHES=$OPTARG ;; + ?) + echo "Usage: %0 [-s <subscribers>] [-m <messages.] [-b <batches>]" + exit 1 + ;; + esac +done + +source ./test_env.sh + +trap stop_brokers EXIT + +start_broker() { + $QPIDD_EXEC --daemon --port 0 --interface 127.0.0.1 --no-module-dir --no-data-dir --auth no > qpidd.port +} + +start_brokers() { + start_broker + PORT_A=`cat qpidd.port` + start_broker + PORT_B=`cat qpidd.port` + start_broker + PORT_C=`cat qpidd.port` +} + +stop_brokers() { + for p in $PORT_A $PORT_B $PORT_C; do + $QPIDD_EXEC --no-module-dir -q --port $p + done +} + +subscribe() { + #which broker should we connect to? + if (( $1 % 2 )); then + MY_PORT=$PORT_C; + else + MY_PORT=$PORT_A; + fi + + echo Subscriber $1 connecting on $MY_PORT + LOG="subscriber_$1.log" + ./qpid-topic-listener -p $MY_PORT > $LOG 2>&1 && rm -f $LOG +} + +publish() { + ./qpid-topic-publisher --messages $MESSAGES --batches $BATCHES --subscribers $SUBSCRIBERS -p $PORT_A +} + +setup_routes() { + BROKER_A="daffodil:$PORT_A" + BROKER_B="daffodil:$PORT_B" + BROKER_C="daffodil:$PORT_C" + if (($VERBOSE)); then + echo "Establishing routes for topic..." + fi + $QPID_ROUTE_EXEC route add $BROKER_B $BROKER_A amq.topic topic_control B B + $QPID_ROUTE_EXEC route add $BROKER_C $BROKER_B amq.topic topic_control C C + if (($VERBOSE)); then + echo "linked A->B->C" + fi + $QPID_ROUTE_EXEC route add $BROKER_B $BROKER_C amq.topic topic_control B B + $QPID_ROUTE_EXEC route add $BROKER_A $BROKER_B amq.topic topic_control A A + if (($VERBOSE)); then + echo "linked C->B->A" + echo "Establishing routes for response queue..." + fi + + $QPID_ROUTE_EXEC route add $BROKER_B $BROKER_C amq.direct response B B + $QPID_ROUTE_EXEC route add $BROKER_A $BROKER_B amq.direct response A A + if (($VERBOSE)); then + echo "linked C->B->A" + for b in $BROKER_A $BROKER_B $BROKER_C; do + echo "Routes for $b" + $QPID_ROUTE_EXEC route list $b + done + fi +} + +if test -d ${PYTHON_DIR} ; then + start_brokers + if (($VERBOSE)); then + echo "Running federated topic test against brokers on ports $PORT_A $PORT_B $PORT_C" + fi + + for ((i=$SUBSCRIBERS ; i--; )); do + subscribe $i & + done + + setup_routes + + publish || exit 1 +fi diff --git a/qpid/cpp/src/tests/federation.py b/qpid/cpp/src/tests/federation.py new file mode 100755 index 0000000000..f5b62019e5 --- /dev/null +++ b/qpid/cpp/src/tests/federation.py @@ -0,0 +1,2793 @@ +#!/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. +# + +import sys +from qpid.testlib import TestBase010 +from qpid.datatypes import Message +from qpid.queue import Empty +from qpid.util import URL +import qpid.messaging +from time import sleep, time + + +class _FedBroker(object): + """ + A proxy object for a remote broker. Contains connection and management + state. + """ + def __init__(self, host, port, + conn=None, session=None, qmf_broker=None): + self.host = host + self.port = port + self.url = "%s:%d" % (host, port) + self.client_conn = None + self.client_session = None + self.qmf_broker = None + self.qmf_object = None + if conn is not None: + self.client_conn = conn + if session is not None: + self.client_session = session + if qmf_broker is not None: + self.qmf_broker = qmf_broker + + +class FederationTests(TestBase010): + + def remote_host(self): + return self.defines.get("remote-host", "localhost") + + def remote_port(self): + return int(self.defines["remote-port"]) + + def verify_cleanup(self): + attempts = 0 + total = len(self.qmf.getObjects(_class="bridge")) + len(self.qmf.getObjects(_class="link")) + while total > 0: + attempts += 1 + if attempts >= 10: + self.fail("Bridges and links didn't clean up") + return + sleep(1) + total = len(self.qmf.getObjects(_class="bridge")) + len(self.qmf.getObjects(_class="link")) + + def _setup_brokers(self): + ports = [self.remote_port()] + extra = self.defines.get("extra-brokers") + if extra: + for p in extra.split(): + ports.append(int(p)) + + # broker[0] has already been set up. + self._brokers = [_FedBroker(self.broker.host, + self.broker.port, + self.conn, + self.session, + self.qmf_broker)] + self._brokers[0].qmf_object = self.qmf.getObjects(_class="broker")[0] + + # setup remaining brokers + for _p in ports: + _b = _FedBroker(self.remote_host(), _p) + _b.client_conn = self.connect(host=self.remote_host(), port=_p) + _b.client_session = _b.client_conn.session("Fed_client_session_" + str(_p)) + _b.qmf_broker = self.qmf.addBroker(_b.url) + for _bo in self.qmf.getObjects(_class="broker"): + if _bo.getBroker().getUrl() == _b.qmf_broker.getUrl(): + _b.qmf_object = _bo + break + self._brokers.append(_b) + + # add a new-style messaging connection to each broker + for _b in self._brokers: + _b.connection = qpid.messaging.Connection(_b.url) + _b.connection.open() + + def _teardown_brokers(self): + """ Un-does _setup_brokers() + """ + # broker[0] is configured at test setup, so it must remain configured + for _b in self._brokers[1:]: + self.qmf.delBroker(_b.qmf_broker) + if not _b.client_session.error(): + _b.client_session.close(timeout=10) + _b.client_conn.close(timeout=10) + _b.connection.close() + + def test_bridge_create_and_close(self): + self.startQmf(); + qmf = self.qmf + + broker = qmf.getObjects(_class="broker")[0] + result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0, result) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "amq.direct", "amq.direct", "my-key", "", + "", False, False, False, 0, 0) + self.assertEqual(result.status, 0, result) + + bridge = qmf.getObjects(_class="bridge")[0] + result = bridge.close() + self.assertEqual(result.status, 0, result) + + result = link.close() + self.assertEqual(result.status, 0, result) + + self.verify_cleanup() + + def test_pull_from_exchange(self): + """ This test uses an alternative method to manage links and bridges + via the broker object. + """ + session = self.session + + self.startQmf() + qmf = self.qmf + broker = qmf.getObjects(_class="broker")[0] + + # create link + link_args = {"host":self.remote_host(), "port":self.remote_port(), "durable":False, + "authMechanism":"PLAIN", "username":"guest", "password":"guest", + "transport":"tcp"} + result = broker.create("link", "test-link-1", link_args, False) + self.assertEqual(result.status, 0, result) + link = qmf.getObjects(_class="link")[0] + + # create bridge + bridge_args = {"link":"test-link-1", "src":"amq.direct", "dest":"amq.fanout", + "key":"my-key"} + result = broker.create("bridge", "test-bridge-1", bridge_args, False); + self.assertEqual(result.status, 0, result) + bridge = qmf.getObjects(_class="bridge")[0] + + #setup queue to receive messages from local broker + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed1", exchange="amq.fanout") + self.subscribe(queue="fed1", destination="f1") + queue = session.incoming("f1") + sleep(6) + + #send messages to remote broker and confirm it is routed to local broker + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_pull_from_exchange") + + for i in range(1, 11): + dp = r_session.delivery_properties(routing_key="my-key") + r_session.message_transfer(destination="amq.direct", message=Message(dp, "Message %d" % i)) + + for i in range(1, 11): + msg = queue.get(timeout=5) + self.assertEqual("Message %d" % i, msg.body) + try: + extra = queue.get(timeout=1) + self.fail("Got unexpected message in queue: " + extra.body) + except Empty: None + + + result = broker.delete("bridge", "test-bridge-1", {}) + self.assertEqual(result.status, 0, result) + + result = broker.delete("link", "test-link-1", {}) + self.assertEqual(result.status, 0, result) + + self.verify_cleanup() + + def test_push_to_exchange(self): + session = self.session + + self.startQmf() + qmf = self.qmf + broker = qmf.getObjects(_class="broker")[0] + result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0, result) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "amq.direct", "amq.fanout", "my-key", "", "", False, True, False, 0, 0) + self.assertEqual(result.status, 0, result) + + bridge = qmf.getObjects(_class="bridge")[0] + + #setup queue to receive messages from remote broker + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_push_to_exchange") + r_session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + r_session.exchange_bind(queue="fed1", exchange="amq.fanout") + self.subscribe(session=r_session, queue="fed1", destination="f1") + queue = r_session.incoming("f1") + sleep(6) + + #send messages to local broker and confirm it is routed to remote broker + for i in range(1, 11): + dp = session.delivery_properties(routing_key="my-key") + session.message_transfer(destination="amq.direct", message=Message(dp, "Message %d" % i)) + + for i in range(1, 11): + msg = queue.get(timeout=5) + self.assertEqual("Message %d" % i, msg.body) + try: + extra = queue.get(timeout=1) + self.fail("Got unexpected message in queue: " + extra.body) + except Empty: None + + result = bridge.close() + self.assertEqual(result.status, 0, result) + result = link.close() + self.assertEqual(result.status, 0, result) + + self.verify_cleanup() + + def test_pull_from_queue(self): + session = self.session + + #setup queue on remote broker and add some messages + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_pull_from_queue") + r_session.queue_declare(queue="my-bridge-queue", auto_delete=True) + for i in range(1, 6): + dp = r_session.delivery_properties(routing_key="my-bridge-queue") + r_session.message_transfer(message=Message(dp, "Message %d" % i)) + + #setup queue to receive messages from local broker + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed1", exchange="amq.fanout") + self.subscribe(queue="fed1", destination="f1") + queue = session.incoming("f1") + + self.startQmf() + qmf = self.qmf + broker = qmf.getObjects(_class="broker")[0] + result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0, result) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "my-bridge-queue", "amq.fanout", "my-key", "", "", True, False, False, 1, 0) + self.assertEqual(result.status, 0, result) + + bridge = qmf.getObjects(_class="bridge")[0] + sleep(3) + + #add some more messages (i.e. after bridge was created) + for i in range(6, 11): + dp = r_session.delivery_properties(routing_key="my-bridge-queue") + r_session.message_transfer(message=Message(dp, "Message %d" % i)) + + for i in range(1, 11): + try: + msg = queue.get(timeout=5) + self.assertEqual("Message %d" % i, msg.body) + except Empty: + self.fail("Failed to find expected message containing 'Message %d'" % i) + try: + extra = queue.get(timeout=1) + self.fail("Got unexpected message in queue: " + extra.body) + except Empty: None + + result = bridge.close() + self.assertEqual(result.status, 0, result) + result = link.close() + self.assertEqual(result.status, 0, result) + + self.verify_cleanup() + + def test_pull_from_queue_recovery(self): + session = self.session + + #setup queue on remote broker and add some messages + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_pull_from_queue_recovery") + # disable auto-delete otherwise the detach of the fed session may + # delete the queue right after this test re-creates it. + r_session.queue_declare(queue="my-bridge-queue", auto_delete=False) + for i in range(1, 6): + dp = r_session.delivery_properties(routing_key="my-bridge-queue") + r_session.message_transfer(message=Message(dp, "Message %d" % i)) + + #setup queue to receive messages from local broker + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed1", exchange="amq.fanout") + self.subscribe(queue="fed1", destination="f1") + queue = session.incoming("f1") + + self.startQmf() + qmf = self.qmf + broker = qmf.getObjects(_class="broker")[0] + result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0, result) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "my-bridge-queue", "amq.fanout", "my-key", "", "", True, False, False, 1, 0) + self.assertEqual(result.status, 0, result) + + bridge = qmf.getObjects(_class="bridge")[0] + sleep(5) + + #recreate the remote bridge queue to invalidate the bridge session + r_session.queue_delete (queue="my-bridge-queue", if_empty=False, if_unused=False) + r_session.queue_declare(queue="my-bridge-queue", auto_delete=False) + + #add some more messages (i.e. after bridge was created) + for i in range(6, 11): + dp = r_session.delivery_properties(routing_key="my-bridge-queue") + r_session.message_transfer(message=Message(dp, "Message %d" % i)) + + for i in range(1, 11): + try: + msg = queue.get(timeout=5) + self.assertEqual("Message %d" % i, msg.body) + except Empty: + self.fail("Failed to find expected message containing 'Message %d'" % i) + try: + extra = queue.get(timeout=1) + self.fail("Got unexpected message in queue: " + extra.body) + except Empty: None + + result = bridge.close() + self.assertEqual(result.status, 0) + result = link.close() + self.assertEqual(result.status, 0) + self.verify_cleanup() + r_session.queue_delete (queue="my-bridge-queue", if_empty=False, if_unused=False) + + def test_tracing_automatic(self): + remoteUrl = "%s:%d" % (self.remote_host(), self.remote_port()) + self.startQmf() + l_broker = self.qmf_broker + r_broker = self.qmf.addBroker(remoteUrl) + + l_brokerObj = self.qmf.getObjects(_class="broker", _broker=l_broker)[0] + r_brokerObj = self.qmf.getObjects(_class="broker", _broker=r_broker)[0] + + l_res = l_brokerObj.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp") + r_res = r_brokerObj.connect(self.broker.host, self.broker.port, False, "PLAIN", "guest", "guest", "tcp") + + self.assertEqual(l_res.status, 0) + self.assertEqual(r_res.status, 0) + + l_link = self.qmf.getObjects(_class="link", _broker=l_broker)[0] + r_link = self.qmf.getObjects(_class="link", _broker=r_broker)[0] + + l_res = l_link.bridge(False, "amq.direct", "amq.direct", "key", "", "", False, False, False, 0, 0) + r_res = r_link.bridge(False, "amq.direct", "amq.direct", "key", "", "", False, False, False, 0, 0) + + self.assertEqual(l_res.status, 0) + self.assertEqual(r_res.status, 0) + + count = 0 + while l_link.state != "Operational" or r_link.state != "Operational": + count += 1 + if count > 10: + self.fail("Fed links didn't become operational after 10 seconds") + sleep(1) + l_link = self.qmf.getObjects(_class="link", _broker=l_broker)[0] + r_link = self.qmf.getObjects(_class="link", _broker=r_broker)[0] + sleep(3) + + #setup queue to receive messages from local broker + session = self.session + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed1", exchange="amq.direct", binding_key="key") + self.subscribe(queue="fed1", destination="f1") + queue = session.incoming("f1") + + #setup queue on remote broker and add some messages + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_trace") + for i in range(1, 11): + dp = r_session.delivery_properties(routing_key="key") + r_session.message_transfer(destination="amq.direct", message=Message(dp, "Message %d" % i)) + + for i in range(1, 11): + try: + msg = queue.get(timeout=5) + mp = msg.get("message_properties").application_headers + self.assertEqual(mp.__class__, dict) + self.assertEqual(mp['x-qpid.trace'], 'REMOTE') # check that the federation-tag override works + self.assertEqual("Message %d" % i, msg.body) + except Empty: + self.fail("Failed to find expected message containing 'Message %d'" % i) + try: + extra = queue.get(timeout=1) + self.fail("Got unexpected message in queue: " + extra.body) + except Empty: None + + def test_tracing(self): + session = self.session + + self.startQmf() + qmf = self.qmf + broker = qmf.getObjects(_class="broker")[0] + result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "amq.direct", "amq.fanout", "my-key", "my-bridge-id", + "exclude-me,also-exclude-me", False, False, False, 0, 0) + self.assertEqual(result.status, 0) + bridge = qmf.getObjects(_class="bridge")[0] + + #setup queue to receive messages from local broker + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed1", exchange="amq.fanout") + self.subscribe(queue="fed1", destination="f1") + queue = session.incoming("f1") + sleep(6) + + #send messages to remote broker and confirm it is routed to local broker + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_tracing") + + trace = [None, "exclude-me", "a,exclude-me,b", "also-exclude-me,c", "dont-exclude-me"] + body = ["yes", "first-bad", "second-bad", "third-bad", "yes"] + for b, t in zip(body, trace): + headers = {} + if (t): headers["x-qpid.trace"]=t + dp = r_session.delivery_properties(routing_key="my-key", ttl=1000*60*5) + mp = r_session.message_properties(application_headers=headers) + r_session.message_transfer(destination="amq.direct", message=Message(dp, mp, b)) + + for e in ["my-bridge-id", "dont-exclude-me,my-bridge-id"]: + msg = queue.get(timeout=5) + self.assertEqual("yes", msg.body) + self.assertEqual(e, self.getAppHeader(msg, "x-qpid.trace")) + assert(msg.get("delivery_properties").ttl > 0) + assert(msg.get("delivery_properties").ttl < 1000*60*50) + + try: + extra = queue.get(timeout=1) + self.fail("Got unexpected message in queue: " + extra.body) + except Empty: None + + result = bridge.close() + self.assertEqual(result.status, 0) + result = link.close() + self.assertEqual(result.status, 0) + + self.verify_cleanup() + + def test_dynamic_fanout(self): + session = self.session + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_dynamic_fanout") + + session.exchange_declare(exchange="fed.fanout", type="fanout") + r_session.exchange_declare(exchange="fed.fanout", type="fanout") + + self.startQmf() + qmf = self.qmf + broker = qmf.getObjects(_class="broker")[0] + result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "fed.fanout", "fed.fanout", "", "", "", False, False, True, 0, 0) + self.assertEqual(result.status, 0) + bridge = qmf.getObjects(_class="bridge")[0] + sleep(5) + + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed1", exchange="fed.fanout") + self.subscribe(queue="fed1", destination="f1") + queue = session.incoming("f1") + + for i in range(1, 11): + dp = r_session.delivery_properties() + r_session.message_transfer(destination="fed.fanout", message=Message(dp, "Message %d" % i)) + + for i in range(1, 11): + msg = queue.get(timeout=5) + self.assertEqual("Message %d" % i, msg.body) + try: + extra = queue.get(timeout=1) + self.fail("Got unexpected message in queue: " + extra.body) + except Empty: None + + result = bridge.close() + self.assertEqual(result.status, 0) + result = link.close() + self.assertEqual(result.status, 0) + + self.verify_cleanup() + + + def test_dynamic_direct(self): + session = self.session + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_dynamic_direct") + + session.exchange_declare(exchange="fed.direct", type="direct") + r_session.exchange_declare(exchange="fed.direct", type="direct") + + self.startQmf() + qmf = self.qmf + broker = qmf.getObjects(_class="broker")[0] + result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "fed.direct", "fed.direct", "", "", "", False, False, True, 0, 0) + self.assertEqual(result.status, 0) + bridge = qmf.getObjects(_class="bridge")[0] + sleep(5) + + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed1", exchange="fed.direct", binding_key="fd-key") + self.subscribe(queue="fed1", destination="f1") + queue = session.incoming("f1") + + for i in range(1, 11): + dp = r_session.delivery_properties(routing_key="fd-key") + r_session.message_transfer(destination="fed.direct", message=Message(dp, "Message %d" % i)) + + for i in range(1, 11): + msg = queue.get(timeout=5) + self.assertEqual("Message %d" % i, msg.body) + try: + extra = queue.get(timeout=1) + self.fail("Got unexpected message in queue: " + extra.body) + except Empty: None + + result = bridge.close() + self.assertEqual(result.status, 0) + result = link.close() + self.assertEqual(result.status, 0) + + self.verify_cleanup() + + def test_dynamic_topic(self): + session = self.session + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_dynamic_topic") + + session.exchange_declare(exchange="fed.topic", type="topic") + r_session.exchange_declare(exchange="fed.topic", type="topic") + + self.startQmf() + qmf = self.qmf + broker = qmf.getObjects(_class="broker")[0] + result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "fed.topic", "fed.topic", "", "", "", False, False, True, 0, 0) + self.assertEqual(result.status, 0) + bridge = qmf.getObjects(_class="bridge")[0] + sleep(5) + + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed1", exchange="fed.topic", binding_key="ft-key.#") + self.subscribe(queue="fed1", destination="f1") + queue = session.incoming("f1") + + for i in range(1, 11): + dp = r_session.delivery_properties(routing_key="ft-key.one.two") + r_session.message_transfer(destination="fed.topic", message=Message(dp, "Message %d" % i)) + + for i in range(1, 11): + msg = queue.get(timeout=5) + self.assertEqual("Message %d" % i, msg.body) + try: + extra = queue.get(timeout=1) + self.fail("Got unexpected message in queue: " + extra.body) + except Empty: None + + result = bridge.close() + self.assertEqual(result.status, 0) + result = link.close() + self.assertEqual(result.status, 0) + + self.verify_cleanup() + + def test_dynamic_topic_reorigin(self): + session = self.session + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_dynamic_topic_reorigin") + + session.exchange_declare(exchange="fed.topic_reorigin", type="topic") + r_session.exchange_declare(exchange="fed.topic_reorigin", type="topic") + + session.exchange_declare(exchange="fed.topic_reorigin_2", type="topic") + r_session.exchange_declare(exchange="fed.topic_reorigin_2", type="topic") + + self.startQmf() + qmf = self.qmf + broker = qmf.getObjects(_class="broker")[0] + result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + session.queue_declare(queue="fed2", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed2", exchange="fed.topic_reorigin_2", binding_key="ft-key.one.#") + self.subscribe(queue="fed2", destination="f2") + queue2 = session.incoming("f2") + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "fed.topic_reorigin", "fed.topic_reorigin", "", "", "", False, False, True, 0, 0) + self.assertEqual(result.status, 0) + result = link.bridge(False, "fed.topic_reorigin_2", "fed.topic_reorigin_2", "", "", "", False, False, True, 0, 0) + self.assertEqual(result.status, 0) + + bridge = qmf.getObjects(_class="bridge")[0] + bridge2 = qmf.getObjects(_class="bridge")[1] + sleep(5) + + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed1", exchange="fed.topic_reorigin", binding_key="ft-key.#") + self.subscribe(queue="fed1", destination="f1") + queue = session.incoming("f1") + + for i in range(1, 11): + dp = r_session.delivery_properties(routing_key="ft-key.one.two") + r_session.message_transfer(destination="fed.topic_reorigin", message=Message(dp, "Message %d" % i)) + + for i in range(1, 11): + msg = queue.get(timeout=5) + self.assertEqual("Message %d" % i, msg.body) + try: + extra = queue.get(timeout=1) + self.fail("Got unexpected message in queue: " + extra.body) + except Empty: None + + result = bridge.close() + self.assertEqual(result.status, 0) + result = bridge2.close() + self.assertEqual(result.status, 0) + + # extra check: verify we don't leak bridge objects - keep the link + # around and verify the bridge count has gone to zero + + attempts = 0 + bridgeCount = len(qmf.getObjects(_class="bridge")) + while bridgeCount > 0: + attempts += 1 + if attempts >= 5: + self.fail("Bridges didn't clean up") + return + sleep(1) + bridgeCount = len(qmf.getObjects(_class="bridge")) + + result = link.close() + self.assertEqual(result.status, 0) + + self.verify_cleanup() + + def test_dynamic_direct_reorigin(self): + session = self.session + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_dynamic_direct_reorigin") + + session.exchange_declare(exchange="fed.direct_reorigin", type="direct") + r_session.exchange_declare(exchange="fed.direct_reorigin", type="direct") + + session.exchange_declare(exchange="fed.direct_reorigin_2", type="direct") + r_session.exchange_declare(exchange="fed.direct_reorigin_2", type="direct") + + self.startQmf() + qmf = self.qmf + broker = qmf.getObjects(_class="broker")[0] + result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + session.queue_declare(queue="fed2", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed2", exchange="fed.direct_reorigin_2", binding_key="ft-key.two") + self.subscribe(queue="fed2", destination="f2") + queue2 = session.incoming("f2") + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "fed.direct_reorigin", "fed.direct_reorigin", "", "", "", False, False, True, 0, 0) + self.assertEqual(result.status, 0) + result = link.bridge(False, "fed.direct_reorigin_2", "fed.direct_reorigin_2", "", "", "", False, False, True, 0, 0) + self.assertEqual(result.status, 0) + + bridge = qmf.getObjects(_class="bridge")[0] + bridge2 = qmf.getObjects(_class="bridge")[1] + sleep(5) + + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed1", exchange="fed.direct_reorigin", binding_key="ft-key.one") + self.subscribe(queue="fed1", destination="f1") + queue = session.incoming("f1") + + for i in range(1, 11): + dp = r_session.delivery_properties(routing_key="ft-key.one") + r_session.message_transfer(destination="fed.direct_reorigin", message=Message(dp, "Message %d" % i)) + + for i in range(1, 11): + msg = queue.get(timeout=5) + self.assertEqual("Message %d" % i, msg.body) + try: + extra = queue.get(timeout=1) + self.fail("Got unexpected message in queue: " + extra.body) + except Empty: None + + result = bridge.close() + self.assertEqual(result.status, 0) + + # Extra test: don't explicitly close() bridge2. When the link is closed, + # it should clean up bridge2 automagically. verify_cleanup() will detect + # if bridge2 isn't cleaned up and will fail the test. + # + #result = bridge2.close() + #self.assertEqual(result.status, 0) + result = link.close() + self.assertEqual(result.status, 0) + + self.verify_cleanup() + + def test_dynamic_headers_any(self): + self.do_test_dynamic_headers('any') + + def test_dynamic_headers_all(self): + self.do_test_dynamic_headers('all') + + + def do_test_dynamic_headers(self, match_mode): + session = self.session + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_dynamic_headers_%s" % match_mode) + + session.exchange_declare(exchange="fed.headers", type="headers") + r_session.exchange_declare(exchange="fed.headers", type="headers") + + self.startQmf() + qmf = self.qmf + + broker = qmf.getObjects(_class="broker")[0] + result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "fed.headers", "fed.headers", "", "", "", False, False, True, 0, 0) + self.assertEqual(result.status, 0) + bridge = qmf.getObjects(_class="bridge")[0] + sleep(5) + + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed1", exchange="fed.headers", binding_key="key1", arguments={'x-match':match_mode, 'class':'first'}) + self.subscribe(queue="fed1", destination="f1") + queue = session.incoming("f1") + + props = r_session.message_properties(application_headers={'class':'first'}) + for i in range(1, 11): + r_session.message_transfer(destination="fed.headers", message=Message(props, "Message %d" % i)) + + for i in range(1, 11): + msg = queue.get(timeout=5) + content = msg.body + self.assertEqual("Message %d" % i, msg.body) + try: + extra = queue.get(timeout=1) + self.fail("Got unexpected message in queue: " + extra.body) + except Empty: None + + result = bridge.close() + self.assertEqual(result.status, 0) + result = link.close() + self.assertEqual(result.status, 0) + + self.verify_cleanup() + + def test_dynamic_headers_reorigin(self): + session = self.session + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_dynamic_headers_reorigin") + + session.exchange_declare(exchange="fed.headers_reorigin", type="headers") + r_session.exchange_declare(exchange="fed.headers_reorigin", type="headers") + + session.exchange_declare(exchange="fed.headers_reorigin_2", type="headers") + r_session.exchange_declare(exchange="fed.headers_reorigin_2", type="headers") + + self.startQmf() + qmf = self.qmf + broker = qmf.getObjects(_class="broker")[0] + result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + session.queue_declare(queue="fed2", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed2", exchange="fed.headers_reorigin_2", binding_key="key2", arguments={'x-match':'any', 'class':'second'}) + self.subscribe(queue="fed2", destination="f2") + queue2 = session.incoming("f2") + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "fed.headers_reorigin", "fed.headers_reorigin", "", "", "", False, False, True, 0, 0) + self.assertEqual(result.status, 0) + result = link.bridge(False, "fed.headers_reorigin_2", "fed.headers_reorigin_2", "", "", "", False, False, True, 0, 0) + self.assertEqual(result.status, 0) + + bridge = qmf.getObjects(_class="bridge")[0] + bridge2 = qmf.getObjects(_class="bridge")[1] + sleep(5) + + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed1", exchange="fed.headers_reorigin", binding_key="key1", arguments={'x-match':'any', 'class':'first'}) + self.subscribe(queue="fed1", destination="f1") + queue = session.incoming("f1") + + props = r_session.message_properties(application_headers={'class':'first'}) + for i in range(1, 11): + r_session.message_transfer(destination="fed.headers_reorigin", message=Message(props, "Message %d" % i)) + + for i in range(1, 11): + msg = queue.get(timeout=5) + self.assertEqual("Message %d" % i, msg.body) + try: + extra = queue.get(timeout=1) + self.fail("Got unexpected message in queue: " + extra.body) + except Empty: None + + result = bridge.close() + self.assertEqual(result.status, 0) + + # Extra test: don't explicitly close() bridge2. When the link is closed, + # it should clean up bridge2 automagically. verify_cleanup() will detect + # if bridge2 isn't cleaned up and will fail the test. + # + #result = bridge2.close() + #self.assertEqual(result.status, 0) + result = link.close() + self.assertEqual(result.status, 0) + + self.verify_cleanup() + + def test_dynamic_headers_unbind(self): + session = self.session + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_dynamic_headers_unbind") + + session.exchange_declare(exchange="fed.headers_unbind", type="headers") + r_session.exchange_declare(exchange="fed.headers_unbind", type="headers") + + self.startQmf() + qmf = self.qmf + + broker = qmf.getObjects(_class="broker")[0] + result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "fed.headers_unbind", "fed.headers_unbind", "", "", "", False, False, True, 0, 0) + self.assertEqual(result.status, 0) + bridge = qmf.getObjects(_class="bridge")[0] + sleep(5) + + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + queue = qmf.getObjects(_class="queue", name="fed1")[0] + queue.update() + self.assertEqual(queue.bindingCount, 1, + "bindings not accounted for (expected 1, got %d)" % queue.bindingCount) + + session.exchange_bind(queue="fed1", exchange="fed.headers_unbind", binding_key="key1", arguments={'x-match':'any', 'class':'first'}) + queue.update() + self.assertEqual(queue.bindingCount, 2, + "bindings not accounted for (expected 2, got %d)" % queue.bindingCount) + + session.exchange_unbind(queue="fed1", exchange="fed.headers_unbind", binding_key="key1") + queue.update() + self.assertEqual(queue.bindingCount, 1, + "bindings not accounted for (expected 1, got %d)" % queue.bindingCount) + + result = bridge.close() + self.assertEqual(result.status, 0) + result = link.close() + self.assertEqual(result.status, 0) + + self.verify_cleanup() + + + def test_dynamic_headers_xml(self): + session = self.session + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_dynamic_headers_xml") + + session.exchange_declare(exchange="fed.xml", type="xml") + r_session.exchange_declare(exchange="fed.xml", type="xml") + + self.startQmf() + qmf = self.qmf + + broker = qmf.getObjects(_class="broker")[0] + result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "fed.xml", "fed.xml", "", "", "", False, False, True, 0, 0) + + self.assertEqual(result.status, 0) + bridge = qmf.getObjects(_class="bridge")[0] + sleep(5) + + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed1", exchange="fed.xml", binding_key="key1", arguments={'xquery':'true()'}) + self.subscribe(queue="fed1", destination="f1") + queue = session.incoming("f1") + + props = r_session.delivery_properties(routing_key="key1") + for i in range(1, 11): + r_session.message_transfer(destination="fed.xml", message=Message(props, "Message %d" % i)) + + for i in range(1, 11): + msg = queue.get(timeout=5) + content = msg.body + self.assertEqual("Message %d" % i, msg.body) + try: + extra = queue.get(timeout=1) + self.fail("Got unexpected message in queue: " + extra.body) + except Empty: None + + result = bridge.close() + self.assertEqual(result.status, 0) + result = link.close() + self.assertEqual(result.status, 0) + + self.verify_cleanup() + + def test_dynamic_headers_reorigin_xml(self): + session = self.session + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_dynamic_headers_reorigin_xml") + + session.exchange_declare(exchange="fed.xml_reorigin", type="xml") + r_session.exchange_declare(exchange="fed.xml_reorigin", type="xml") + + session.exchange_declare(exchange="fed.xml_reorigin_2", type="xml") + r_session.exchange_declare(exchange="fed.xml_reorigin_2", type="xml") + + self.startQmf() + qmf = self.qmf + broker = qmf.getObjects(_class="broker")[0] + result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + session.queue_declare(queue="fed2", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed2", exchange="fed.xml_reorigin_2", binding_key="key2", arguments={'xquery':'true()'}) + self.subscribe(queue="fed2", destination="f2") + queue2 = session.incoming("f2") + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "fed.xml_reorigin", "fed.xml_reorigin", "", "", "", False, False, True, 0, 0) + + self.assertEqual(result.status, 0) + result = link.bridge(False, "fed.xml_reorigin_2", "fed.xml_reorigin_2", "", "", "", False, False, True, 0, 0) + self.assertEqual(result.status, 0) + + bridge = qmf.getObjects(_class="bridge")[0] + bridge2 = qmf.getObjects(_class="bridge")[1] + sleep(5) + + foo=qmf.getObjects(_class="link") + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed1", exchange="fed.xml_reorigin", binding_key="key1", arguments={'xquery':'true()'}) + self.subscribe(queue="fed1", destination="f1") + queue = session.incoming("f1") + + props = r_session.delivery_properties(routing_key="key1") + for i in range(1, 11): + r_session.message_transfer(destination="fed.xml_reorigin", message=Message(props, "Message %d" % i)) + + for i in range(1, 11): + msg = queue.get(timeout=5) + self.assertEqual("Message %d" % i, msg.body) + try: + extra = queue.get(timeout=1) + self.fail("Got unexpected message in queue: " + extra.body) + except Empty: None + + result = bridge.close() + self.assertEqual(result.status, 0) + + # Extra test: don't explicitly close() bridge2. When the link is closed, + # it should clean up bridge2 automagically. verify_cleanup() will detect + # if bridge2 isn't cleaned up and will fail the test. + # + #result = bridge2.close() + #self.assertEqual(result.status, 0) + result = link.close() + self.assertEqual(result.status, 0) + + self.verify_cleanup() + + def test_dynamic_headers_unbind_xml(self): + session = self.session + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_dynamic_xml_unbind") + + session.exchange_declare(exchange="fed.xml_unbind", type="xml") + r_session.exchange_declare(exchange="fed.xml_unbind", type="xml") + + self.startQmf() + qmf = self.qmf + + broker = qmf.getObjects(_class="broker")[0] + result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "fed.xml_unbind", "fed.xml_unbind", "", "", "", False, False, True, 0, 0) + + self.assertEqual(result.status, 0) + bridge = qmf.getObjects(_class="bridge")[0] + sleep(5) + + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + queue = qmf.getObjects(_class="queue", name="fed1")[0] + queue.update() + self.assertEqual(queue.bindingCount, 1, + "bindings not accounted for (expected 1, got %d)" % queue.bindingCount) + + session.exchange_bind(queue="fed1", exchange="fed.xml_unbind", binding_key="key1", arguments={'xquery':'true()'}) + queue.update() + self.assertEqual(queue.bindingCount, 2, + "bindings not accounted for (expected 2, got %d)" % queue.bindingCount) + + session.exchange_unbind(queue="fed1", exchange="fed.xml_unbind", binding_key="key1") + queue.update() + self.assertEqual(queue.bindingCount, 1, + "bindings not accounted for (expected 1, got %d)" % queue.bindingCount) + + result = bridge.close() + self.assertEqual(result.status, 0) + result = link.close() + self.assertEqual(result.status, 0) + + self.verify_cleanup() + + + def test_dynamic_topic_nodup(self): + """Verify that a message whose routing key matches more than one + binding does not get duplicated to the same queue. + """ + session = self.session + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_dynamic_topic_nodup") + + session.exchange_declare(exchange="fed.topic", type="topic") + r_session.exchange_declare(exchange="fed.topic", type="topic") + + self.startQmf() + qmf = self.qmf + broker = qmf.getObjects(_class="broker")[0] + result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "fed.topic", "fed.topic", "", "", "", False, False, True, 0, 0) + self.assertEqual(result.status, 0) + bridge = qmf.getObjects(_class="bridge")[0] + sleep(5) + + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed1", exchange="fed.topic", binding_key="red.*") + session.exchange_bind(queue="fed1", exchange="fed.topic", binding_key="*.herring") + + self.subscribe(queue="fed1", destination="f1") + queue = session.incoming("f1") + + for i in range(1, 11): + dp = r_session.delivery_properties(routing_key="red.herring") + r_session.message_transfer(destination="fed.topic", message=Message(dp, "Message %d" % i)) + + for i in range(1, 11): + msg = queue.get(timeout=5) + self.assertEqual("Message %d" % i, msg.body) + try: + extra = queue.get(timeout=1) + self.fail("Got unexpected message in queue: " + extra.body) + except Empty: None + + result = bridge.close() + self.assertEqual(result.status, 0) + result = link.close() + self.assertEqual(result.status, 0) + + self.verify_cleanup() + + + def test_dynamic_direct_route_prop(self): + """ Set up a tree of uni-directional routes across the direct exchange. + Bind the same key to the same queues on the leaf nodes. Verify a + message sent with the routing key transverses the tree an arrives at + each leaf. Remove one leaf's queue, and verify that messages still + reach the other leaf. + + Route Topology: + + +---> B2 queue:"test-queue", binding key:"spudboy" + B0 --> B1 --+ + +---> B3 queue:"test-queue", binding key:"spudboy" + """ + session = self.session + + # create the federation + + self.startQmf() + qmf = self.qmf + + self._setup_brokers() + + # create direct exchange on each broker, and retrieve the corresponding + # management object for that exchange + + exchanges=[] + for _b in self._brokers: + _b.client_session.exchange_declare(exchange="fedX.direct", type="direct") + self.assertEqual(_b.client_session.exchange_query(name="fedX.direct").type, + "direct", "exchange_declare failed!") + # pull the exchange out of qmf... + retries = 0 + my_exchange = None + while my_exchange is None: + objs = qmf.getObjects(_broker=_b.qmf_broker, _class="exchange") + for ooo in objs: + if ooo.name == "fedX.direct": + my_exchange = ooo + break + if my_exchange is None: + retries += 1 + self.failIfEqual(retries, 10, + "QMF failed to find new exchange!") + sleep(1) + exchanges.append(my_exchange) + + self.assertEqual(len(exchanges), len(self._brokers), "Exchange creation failed!") + + # connect B0 --> B1 + result = self._brokers[1].qmf_object.connect(self._brokers[0].host, + self._brokers[0].port, + False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + # connect B1 --> B2 + result = self._brokers[2].qmf_object.connect(self._brokers[1].host, + self._brokers[1].port, + False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + # connect B1 --> B3 + result = self._brokers[3].qmf_object.connect(self._brokers[1].host, + self._brokers[1].port, + False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + # for each link, bridge the "fedX.direct" exchanges: + + for _l in qmf.getObjects(_class="link"): + # print("Link=%s:%s %s" % (_l.host, _l.port, str(_l.getBroker()))) + result = _l.bridge(False, # durable + "fedX.direct", # src + "fedX.direct", # dst + "", # key + "", # tag + "", # excludes + False, # srcIsQueue + False, # srcIsLocal + True, # dynamic + 0, # sync + 0) # credit + self.assertEqual(result.status, 0) + + # wait for the inter-broker links to become operational + retries = 0 + operational = False + while not operational: + operational = True + for _l in qmf.getObjects(_class="link"): + #print("Link=%s:%s %s" % (_l.host, _l.port, str(_l.state))) + if _l.state != "Operational": + operational = False + if not operational: + retries += 1 + self.failIfEqual(retries, 10, + "inter-broker links failed to become operational.") + sleep(1) + + # @todo - There is no way to determine when the bridge objects become + # active. Hopefully, this is long enough! + sleep(6) + + # create a queue on B2, bound to "spudboy" + self._brokers[2].client_session.queue_declare(queue="fedX1", exclusive=True, auto_delete=True) + self._brokers[2].client_session.exchange_bind(queue="fedX1", exchange="fedX.direct", binding_key="spudboy") + + # create a queue on B3, bound to "spudboy" + self._brokers[3].client_session.queue_declare(queue="fedX1", exclusive=True, auto_delete=True) + self._brokers[3].client_session.exchange_bind(queue="fedX1", exchange="fedX.direct", binding_key="spudboy") + + # subscribe to messages arriving on B2's queue + self.subscribe(self._brokers[2].client_session, queue="fedX1", destination="f1") + queue_2 = self._brokers[2].client_session.incoming("f1") + + # subscribe to messages arriving on B3's queue + self.subscribe(self._brokers[3].client_session, queue="fedX1", destination="f1") + queue_3 = self._brokers[3].client_session.incoming("f1") + + # wait until the binding key has propagated to each broker (twice at + # broker B1). Work backwards from binding brokers. + + binding_counts = [1, 2, 1, 1] + self.assertEqual(len(binding_counts), len(exchanges), "Update Test!") + for i in range(3,-1,-1): + retries = 0 + exchanges[i].update() + while exchanges[i].bindingCount < binding_counts[i]: + retries += 1 + self.failIfEqual(retries, 10, + "binding failed to propagate to broker %d" + % i) + sleep(3) + exchanges[i].update() + + # send 10 msgs from B0 + for i in range(1, 11): + dp = self._brokers[0].client_session.delivery_properties(routing_key="spudboy") + self._brokers[0].client_session.message_transfer(destination="fedX.direct", message=Message(dp, "Message_drp %d" % i)) + + # wait for 10 messages to be forwarded from B0->B1, + # 10 messages from B1->B2, + # and 10 messages from B1->B3 + retries = 0 + for ex in exchanges: + ex.update() + while (exchanges[0].msgReceives != 10 or exchanges[0].msgRoutes != 10 or + exchanges[1].msgReceives != 10 or exchanges[1].msgRoutes != 20 or + exchanges[2].msgReceives != 10 or exchanges[2].msgRoutes != 10 or + exchanges[3].msgReceives != 10 or exchanges[3].msgRoutes != 10): + retries += 1 + self.failIfEqual(retries, 10, + "federation failed to route msgs %d:%d %d:%d %d:%d %d:%d" + % (exchanges[0].msgReceives, + exchanges[0].msgRoutes, + exchanges[1].msgReceives, + exchanges[1].msgRoutes, + exchanges[2].msgReceives, + exchanges[2].msgRoutes, + exchanges[3].msgReceives, + exchanges[3].msgRoutes)) + sleep(1) + for ex in exchanges: + ex.update() + + # get exactly 10 msgs on B2 and B3 + for i in range(1, 11): + msg = queue_2.get(timeout=5) + self.assertEqual("Message_drp %d" % i, msg.body) + msg = queue_3.get(timeout=5) + self.assertEqual("Message_drp %d" % i, msg.body) + + try: + extra = queue_2.get(timeout=1) + self.fail("Got unexpected message in queue_2: " + extra.body) + except Empty: None + + try: + extra = queue_3.get(timeout=1) + self.fail("Got unexpected message in queue_3: " + extra.body) + except Empty: None + + + # tear down the queue on B2 + self._brokers[2].client_session.exchange_unbind(queue="fedX1", exchange="fedX.direct", binding_key="spudboy") + self._brokers[2].client_session.message_cancel(destination="f1") + self._brokers[2].client_session.queue_delete(queue="fedX1") + + # @todo - restore code when QPID-2499 fixed!! + sleep(6) + # wait for the binding count on B1 to drop from 2 to 1 + retries = 0 + exchanges[1].update() + while exchanges[1].bindingCount != 1: + retries += 1 + self.failIfEqual(retries, 10, + "unbinding failed to propagate to broker B1: %d" + % exchanges[1].bindingCount) + sleep(1) + exchanges[1].update() + + # send 10 msgs from B0 + for i in range(11, 21): + dp = self._brokers[0].client_session.delivery_properties(routing_key="spudboy") + self._brokers[0].client_session.message_transfer(destination="fedX.direct", message=Message(dp, "Message_drp %d" % i)) + + # verify messages are forwarded to B3 only + retries = 0 + for ex in exchanges: + ex.update() + while (exchanges[0].msgReceives != 20 or exchanges[0].msgRoutes != 20 or + exchanges[1].msgReceives != 20 or exchanges[1].msgRoutes != 30 or + exchanges[2].msgReceives != 10 or exchanges[2].msgRoutes != 10 or + exchanges[3].msgReceives != 20 or exchanges[3].msgRoutes != 20): + retries += 1 + self.failIfEqual(retries, 10, + "federation failed to route more msgs %d:%d %d:%d %d:%d %d:%d" + % (exchanges[0].msgReceives, + exchanges[0].msgRoutes, + exchanges[1].msgReceives, + exchanges[1].msgRoutes, + exchanges[2].msgReceives, + exchanges[2].msgRoutes, + exchanges[3].msgReceives, + exchanges[3].msgRoutes)) + sleep(1) + for ex in exchanges: + ex.update() + + # get exactly 10 msgs on B3 only + for i in range(11, 21): + msg = queue_3.get(timeout=5) + self.assertEqual("Message_drp %d" % i, msg.body) + + try: + extra = queue_3.get(timeout=1) + self.fail("Got unexpected message in queue_3: " + extra.body) + except Empty: None + + # cleanup + + self._brokers[3].client_session.exchange_unbind(queue="fedX1", exchange="fedX.direct", binding_key="spudboy") + self._brokers[3].client_session.message_cancel(destination="f1") + self._brokers[3].client_session.queue_delete(queue="fedX1") + + for _b in qmf.getObjects(_class="bridge"): + result = _b.close() + self.assertEqual(result.status, 0) + + for _l in qmf.getObjects(_class="link"): + result = _l.close() + self.assertEqual(result.status, 0) + + for _b in self._brokers: + _b.client_session.exchange_delete(exchange="fedX.direct") + + self._teardown_brokers() + + self.verify_cleanup() + + def test_dynamic_topic_route_prop(self): + """ Set up a tree of uni-directional routes across a topic exchange. + Bind the same key to the same queues on the leaf nodes. Verify a + message sent with the routing key transverses the tree an arrives at + each leaf. Remove one leaf's queue, and verify that messages still + reach the other leaf. + + Route Topology: + + +---> B2 queue:"test-queue", binding key:"spud.*" + B0 --> B1 --+ + +---> B3 queue:"test-queue", binding key:"spud.*" + """ + session = self.session + + # create the federation + + self.startQmf() + qmf = self.qmf + + self._setup_brokers() + + # create exchange on each broker, and retrieve the corresponding + # management object for that exchange + + exchanges=[] + for _b in self._brokers: + _b.client_session.exchange_declare(exchange="fedX.topic", type="topic") + self.assertEqual(_b.client_session.exchange_query(name="fedX.topic").type, + "topic", "exchange_declare failed!") + # pull the exchange out of qmf... + retries = 0 + my_exchange = None + while my_exchange is None: + objs = qmf.getObjects(_broker=_b.qmf_broker, _class="exchange") + for ooo in objs: + if ooo.name == "fedX.topic": + my_exchange = ooo + break + if my_exchange is None: + retries += 1 + self.failIfEqual(retries, 10, + "QMF failed to find new exchange!") + sleep(1) + exchanges.append(my_exchange) + + self.assertEqual(len(exchanges), len(self._brokers), "Exchange creation failed!") + + # connect B0 --> B1 + result = self._brokers[1].qmf_object.connect(self._brokers[0].host, + self._brokers[0].port, + False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + # connect B1 --> B2 + result = self._brokers[2].qmf_object.connect(self._brokers[1].host, + self._brokers[1].port, + False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + # connect B1 --> B3 + result = self._brokers[3].qmf_object.connect(self._brokers[1].host, + self._brokers[1].port, + False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + # for each link, bridge the "fedX.topic" exchanges: + + for _l in qmf.getObjects(_class="link"): + # print("Link=%s:%s %s" % (_l.host, _l.port, str(_l.getBroker()))) + result = _l.bridge(False, # durable + "fedX.topic", # src + "fedX.topic", # dst + "", # key + "", # tag + "", # excludes + False, # srcIsQueue + False, # srcIsLocal + True, # dynamic + 0, # sync + 0) # credit + self.assertEqual(result.status, 0) + + # wait for the inter-broker links to become operational + retries = 0 + operational = False + while not operational: + operational = True + for _l in qmf.getObjects(_class="link"): + #print("Link=%s:%s %s" % (_l.host, _l.port, str(_l.state))) + if _l.state != "Operational": + operational = False + if not operational: + retries += 1 + self.failIfEqual(retries, 10, + "inter-broker links failed to become operational.") + sleep(1) + + # @todo - There is no way to determine when the bridge objects become + # active. + sleep(6) + + # create a queue on B2, bound to "spudboy" + self._brokers[2].client_session.queue_declare(queue="fedX1", exclusive=True, auto_delete=True) + self._brokers[2].client_session.exchange_bind(queue="fedX1", exchange="fedX.topic", binding_key="spud.*") + + # create a queue on B3, bound to "spudboy" + self._brokers[3].client_session.queue_declare(queue="fedX1", exclusive=True, auto_delete=True) + self._brokers[3].client_session.exchange_bind(queue="fedX1", exchange="fedX.topic", binding_key="spud.*") + + # subscribe to messages arriving on B2's queue + self.subscribe(self._brokers[2].client_session, queue="fedX1", destination="f1") + queue_2 = self._brokers[2].client_session.incoming("f1") + + # subscribe to messages arriving on B3's queue + self.subscribe(self._brokers[3].client_session, queue="fedX1", destination="f1") + queue_3 = self._brokers[3].client_session.incoming("f1") + + # wait until the binding key has propagated to each broker (twice at + # broker B1). Work backwards from binding brokers. + + binding_counts = [1, 2, 1, 1] + self.assertEqual(len(binding_counts), len(exchanges), "Update Test!") + for i in range(3,-1,-1): + retries = 0 + exchanges[i].update() + while exchanges[i].bindingCount < binding_counts[i]: + retries += 1 + self.failIfEqual(retries, 10, + "binding failed to propagate to broker %d" + % i) + sleep(3) + exchanges[i].update() + + # send 10 msgs from B0 + for i in range(1, 11): + dp = self._brokers[0].client_session.delivery_properties(routing_key="spud.boy") + self._brokers[0].client_session.message_transfer(destination="fedX.topic", message=Message(dp, "Message_trp %d" % i)) + + # wait for 10 messages to be forwarded from B0->B1, + # 10 messages from B1->B2, + # and 10 messages from B1->B3 + retries = 0 + for ex in exchanges: + ex.update() + while (exchanges[0].msgReceives != 10 or exchanges[0].msgRoutes != 10 or + exchanges[1].msgReceives != 10 or exchanges[1].msgRoutes != 20 or + exchanges[2].msgReceives != 10 or exchanges[2].msgRoutes != 10 or + exchanges[3].msgReceives != 10 or exchanges[3].msgRoutes != 10): + retries += 1 + self.failIfEqual(retries, 10, + "federation failed to route msgs %d:%d %d:%d %d:%d %d:%d" + % (exchanges[0].msgReceives, + exchanges[0].msgRoutes, + exchanges[1].msgReceives, + exchanges[1].msgRoutes, + exchanges[2].msgReceives, + exchanges[2].msgRoutes, + exchanges[3].msgReceives, + exchanges[3].msgRoutes)) + sleep(1) + for ex in exchanges: + ex.update() + + # get exactly 10 msgs on B2 and B3 + for i in range(1, 11): + msg = queue_2.get(timeout=5) + self.assertEqual("Message_trp %d" % i, msg.body) + msg = queue_3.get(timeout=5) + self.assertEqual("Message_trp %d" % i, msg.body) + + try: + extra = queue_2.get(timeout=1) + self.fail("Got unexpected message in queue_2: " + extra.body) + except Empty: None + + try: + extra = queue_3.get(timeout=1) + self.fail("Got unexpected message in queue_3: " + extra.body) + except Empty: None + + # tear down the queue on B2 + self._brokers[2].client_session.exchange_unbind(queue="fedX1", exchange="fedX.topic", binding_key="spud.*") + self._brokers[2].client_session.message_cancel(destination="f1") + self._brokers[2].client_session.queue_delete(queue="fedX1") + + # wait for the binding count on B1 to drop from 2 to 1 + retries = 0 + exchanges[1].update() + while exchanges[1].bindingCount != 1: + retries += 1 + self.failIfEqual(retries, 10, + "unbinding failed to propagate to broker B1: %d" + % exchanges[1].bindingCount) + sleep(1) + exchanges[1].update() + + # send 10 msgs from B0 + for i in range(11, 21): + dp = self._brokers[0].client_session.delivery_properties(routing_key="spud.boy") + self._brokers[0].client_session.message_transfer(destination="fedX.topic", message=Message(dp, "Message_trp %d" % i)) + + # verify messages are forwarded to B3 only + retries = 0 + for ex in exchanges: + ex.update() + while (exchanges[0].msgReceives != 20 or exchanges[0].msgRoutes != 20 or + exchanges[1].msgReceives != 20 or exchanges[1].msgRoutes != 30 or + exchanges[2].msgReceives != 10 or exchanges[2].msgRoutes != 10 or + exchanges[3].msgReceives != 20 or exchanges[3].msgRoutes != 20): + retries += 1 + self.failIfEqual(retries, 10, + "federation failed to route more msgs %d:%d %d:%d %d:%d %d:%d" + % (exchanges[0].msgReceives, + exchanges[0].msgRoutes, + exchanges[1].msgReceives, + exchanges[1].msgRoutes, + exchanges[2].msgReceives, + exchanges[2].msgRoutes, + exchanges[3].msgReceives, + exchanges[3].msgRoutes)) + sleep(1) + for ex in exchanges: + ex.update() + + # get exactly 10 msgs on B3 only + for i in range(11, 21): + msg = queue_3.get(timeout=5) + self.assertEqual("Message_trp %d" % i, msg.body) + + try: + extra = queue_3.get(timeout=1) + self.fail("Got unexpected message in queue_3: " + extra.body) + except Empty: None + + # cleanup + + self._brokers[3].client_session.exchange_unbind(queue="fedX1", exchange="fedX.topic", binding_key="spud.*") + self._brokers[3].client_session.message_cancel(destination="f1") + self._brokers[3].client_session.queue_delete(queue="fedX1") + + for _b in qmf.getObjects(_class="bridge"): + result = _b.close() + self.assertEqual(result.status, 0) + + for _l in qmf.getObjects(_class="link"): + result = _l.close() + self.assertEqual(result.status, 0) + + for _b in self._brokers: + _b.client_session.exchange_delete(exchange="fedX.topic") + + self._teardown_brokers() + + self.verify_cleanup() + + + def test_dynamic_fanout_route_prop(self): + """ Set up a tree of uni-directional routes across a fanout exchange. + Bind the same key to the same queues on the leaf nodes. Verify a + message sent with the routing key transverses the tree an arrives at + each leaf. Remove one leaf's queue, and verify that messages still + reach the other leaf. + + Route Topology: + + +---> B2 queue:"test-queue", binding key:"spud.*" + B0 --> B1 --+ + +---> B3 queue:"test-queue", binding key:"spud.*" + """ + session = self.session + + # create the federation + + self.startQmf() + qmf = self.qmf + + self._setup_brokers() + + # create fanout exchange on each broker, and retrieve the corresponding + # management object for that exchange + + exchanges=[] + for _b in self._brokers: + _b.client_session.exchange_declare(exchange="fedX.fanout", type="fanout") + self.assertEqual(_b.client_session.exchange_query(name="fedX.fanout").type, + "fanout", "exchange_declare failed!") + # pull the exchange out of qmf... + retries = 0 + my_exchange = None + while my_exchange is None: + objs = qmf.getObjects(_broker=_b.qmf_broker, _class="exchange") + for ooo in objs: + if ooo.name == "fedX.fanout": + my_exchange = ooo + break + if my_exchange is None: + retries += 1 + self.failIfEqual(retries, 10, + "QMF failed to find new exchange!") + sleep(1) + exchanges.append(my_exchange) + + self.assertEqual(len(exchanges), len(self._brokers), "Exchange creation failed!") + + # connect B0 --> B1 + result = self._brokers[1].qmf_object.connect(self._brokers[0].host, + self._brokers[0].port, + False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + # connect B1 --> B2 + result = self._brokers[2].qmf_object.connect(self._brokers[1].host, + self._brokers[1].port, + False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + # connect B1 --> B3 + result = self._brokers[3].qmf_object.connect(self._brokers[1].host, + self._brokers[1].port, + False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + # for each link, bridge the "fedX.fanout" exchanges: + + for _l in qmf.getObjects(_class="link"): + # print("Link=%s:%s %s" % (_l.host, _l.port, str(_l.getBroker()))) + result = _l.bridge(False, # durable + "fedX.fanout", # src + "fedX.fanout", # dst + "", # key + "", # tag + "", # excludes + False, # srcIsQueue + False, # srcIsLocal + True, # dynamic + 0, # sync + 0) # credit + self.assertEqual(result.status, 0) + + # wait for the inter-broker links to become operational + retries = 0 + operational = False + while not operational: + operational = True + for _l in qmf.getObjects(_class="link"): + # print("Link=%s:%s %s" % (_l.host, _l.port, str(_l.state))) + if _l.state != "Operational": + operational = False + if not operational: + retries += 1 + self.failIfEqual(retries, 10, + "inter-broker links failed to become operational.") + sleep(1) + + # @todo - There is no way to determine when the bridge objects become + # active. + sleep(6) + + # create a queue on B2, bound to the exchange + self._brokers[2].client_session.queue_declare(queue="fedX1", exclusive=True, auto_delete=True) + self._brokers[2].client_session.exchange_bind(queue="fedX1", exchange="fedX.fanout") + + # create a queue on B3, bound to the exchange + self._brokers[3].client_session.queue_declare(queue="fedX1", exclusive=True, auto_delete=True) + self._brokers[3].client_session.exchange_bind(queue="fedX1", exchange="fedX.fanout") + + # subscribe to messages arriving on B2's queue + self.subscribe(self._brokers[2].client_session, queue="fedX1", destination="f1") + queue_2 = self._brokers[2].client_session.incoming("f1") + + # subscribe to messages arriving on B3's queue + self.subscribe(self._brokers[3].client_session, queue="fedX1", destination="f1") + queue_3 = self._brokers[3].client_session.incoming("f1") + + # wait until the binding key has propagated to each broker (twice at + # broker B1). Work backwards from binding brokers. + + binding_counts = [1, 2, 1, 1] + self.assertEqual(len(binding_counts), len(exchanges), "Update Test!") + for i in range(3,-1,-1): + retries = 0 + exchanges[i].update() + while exchanges[i].bindingCount < binding_counts[i]: + retries += 1 + self.failIfEqual(retries, 10, + "binding failed to propagate to broker %d" + % i) + sleep(3) + exchanges[i].update() + + # send 10 msgs from B0 + for i in range(1, 11): + dp = self._brokers[0].client_session.delivery_properties() + self._brokers[0].client_session.message_transfer(destination="fedX.fanout", message=Message(dp, "Message_frp %d" % i)) + + # wait for 10 messages to be forwarded from B0->B1, + # 10 messages from B1->B2, + # and 10 messages from B1->B3 + retries = 0 + for ex in exchanges: + ex.update() + while (exchanges[0].msgReceives != 10 or exchanges[0].msgRoutes != 10 or + exchanges[1].msgReceives != 10 or exchanges[1].msgRoutes != 20 or + exchanges[2].msgReceives != 10 or exchanges[2].msgRoutes != 10 or + exchanges[3].msgReceives != 10 or exchanges[3].msgRoutes != 10): + retries += 1 + self.failIfEqual(retries, 10, + "federation failed to route msgs %d:%d %d:%d %d:%d %d:%d" + % (exchanges[0].msgReceives, + exchanges[0].msgRoutes, + exchanges[1].msgReceives, + exchanges[1].msgRoutes, + exchanges[2].msgReceives, + exchanges[2].msgRoutes, + exchanges[3].msgReceives, + exchanges[3].msgRoutes)) + sleep(1) + for ex in exchanges: + ex.update() + + # get exactly 10 msgs on B2 and B3 + for i in range(1, 11): + msg = queue_2.get(timeout=5) + self.assertEqual("Message_frp %d" % i, msg.body) + msg = queue_3.get(timeout=5) + self.assertEqual("Message_frp %d" % i, msg.body) + + try: + extra = queue_2.get(timeout=1) + self.fail("Got unexpected message in queue_2: " + extra.body) + except Empty: None + + try: + extra = queue_3.get(timeout=1) + self.fail("Got unexpected message in queue_3: " + extra.body) + except Empty: None + + # tear down the queue on B2 + self._brokers[2].client_session.exchange_unbind(queue="fedX1", exchange="fedX.fanout") + self._brokers[2].client_session.message_cancel(destination="f1") + self._brokers[2].client_session.queue_delete(queue="fedX1") + + # wait for the binding count on B1 to drop from 2 to 1 + retries = 0 + exchanges[1].update() + while exchanges[1].bindingCount != 1: + retries += 1 + self.failIfEqual(retries, 10, + "unbinding failed to propagate to broker B1: %d" + % exchanges[1].bindingCount) + sleep(1) + exchanges[1].update() + + # send 10 msgs from B0 + for i in range(11, 21): + dp = self._brokers[0].client_session.delivery_properties() + self._brokers[0].client_session.message_transfer(destination="fedX.fanout", message=Message(dp, "Message_frp %d" % i)) + + # verify messages are forwarded to B3 only + retries = 0 + for ex in exchanges: + ex.update() + while (exchanges[0].msgReceives != 20 or exchanges[0].msgRoutes != 20 or + exchanges[1].msgReceives != 20 or exchanges[1].msgRoutes != 30 or + exchanges[2].msgReceives != 10 or exchanges[2].msgRoutes != 10 or + exchanges[3].msgReceives != 20 or exchanges[3].msgRoutes != 20): + retries += 1 + self.failIfEqual(retries, 10, + "federation failed to route more msgs %d:%d %d:%d %d:%d %d:%d" + % (exchanges[0].msgReceives, + exchanges[0].msgRoutes, + exchanges[1].msgReceives, + exchanges[1].msgRoutes, + exchanges[2].msgReceives, + exchanges[2].msgRoutes, + exchanges[3].msgReceives, + exchanges[3].msgRoutes)) + sleep(1) + for ex in exchanges: + ex.update() + + # get exactly 10 msgs on B3 only + for i in range(11, 21): + msg = queue_3.get(timeout=5) + self.assertEqual("Message_frp %d" % i, msg.body) + + try: + extra = queue_3.get(timeout=1) + self.fail("Got unexpected message in queue_3: " + extra.body) + except Empty: None + + # cleanup + + self._brokers[3].client_session.exchange_unbind(queue="fedX1", exchange="fedX.fanout") + self._brokers[3].client_session.message_cancel(destination="f1") + self._brokers[3].client_session.queue_delete(queue="fedX1") + + for _b in qmf.getObjects(_class="bridge"): + result = _b.close() + self.assertEqual(result.status, 0) + + for _l in qmf.getObjects(_class="link"): + result = _l.close() + self.assertEqual(result.status, 0) + + for _b in self._brokers: + _b.client_session.exchange_delete(exchange="fedX.fanout") + + self._teardown_brokers() + + self.verify_cleanup() + + + def getProperty(self, msg, name): + for h in msg.headers: + if hasattr(h, name): return getattr(h, name) + return None + + def getAppHeader(self, msg, name): + headers = self.getProperty(msg, "application_headers") + if headers: + return headers[name] + return None + + def test_dynamic_topic_bounce(self): + """ Bounce the connection between federated Topic Exchanges. + """ + class Params: + def exchange_type(self): return "topic" + def bind_queue(self, ssn, qname, ename): + ssn.exchange_bind(queue=qname, exchange=ename, + binding_key="spud.*") + def unbind_queue(self, ssn, qname, ename): + ssn.exchange_unbind(queue=qname, exchange=ename, binding_key="spud.*") + def delivery_properties(self, ssn): + return ssn.delivery_properties(routing_key="spud.boy") + + self.generic_dynamic_bounce_test(Params()) + + def test_dynamic_direct_bounce(self): + """ Bounce the connection between federated Direct Exchanges. + """ + class Params: + def exchange_type(self): return "direct" + def bind_queue(self, ssn, qname, ename): + ssn.exchange_bind(queue=qname, exchange=ename, binding_key="spud") + def unbind_queue(self, ssn, qname, ename): + ssn.exchange_unbind(queue=qname, exchange=ename, binding_key="spud") + def delivery_properties(self, ssn): + return ssn.delivery_properties(routing_key="spud") + self.generic_dynamic_bounce_test(Params()) + + def test_dynamic_fanout_bounce(self): + """ Bounce the connection between federated Fanout Exchanges. + """ + class Params: + def exchange_type(self): return "fanout" + def bind_queue(self, ssn, qname, ename): + ssn.exchange_bind(queue=qname, exchange=ename) + def unbind_queue(self, ssn, qname, ename): + ssn.exchange_unbind(queue=qname, exchange=ename) + def delivery_properties(self, ssn): + return ssn.delivery_properties(routing_key="spud") + self.generic_dynamic_bounce_test(Params()) + + def test_dynamic_headers_bounce(self): + """ Bounce the connection between federated Headers Exchanges. + """ + class Params: + def exchange_type(self): return "headers" + def bind_queue(self, ssn, qname, ename): + ssn.exchange_bind(queue=qname, exchange=ename, + binding_key="spud", arguments={'x-match':'any', 'class':'first'}) + def unbind_queue(self, ssn, qname, ename): + ssn.exchange_unbind(queue=qname, exchange=ename, binding_key="spud") + def delivery_properties(self, ssn): + return ssn.message_properties(application_headers={'class':'first'}) + ## @todo KAG - re-enable once federation bugs with headers exchanges + ## are fixed. + #self.generic_dynamic_bounce_test(Params()) + return + + + def generic_dynamic_bounce_test(self, params): + """ Verify that a federated broker can maintain a binding to a local + queue using the same key as a remote binding. Destroy and reconnect + the federation link, and verify routes are restored correctly. + See QPID-3170. + Topology: + + Queue1 <---"Key"---B0<==[Federated Exchange]==>B1---"Key"--->Queue2 + """ + session = self.session + + # create the federation + + self.startQmf() + qmf = self.qmf + + self._setup_brokers() + + # create exchange on each broker, and retrieve the corresponding + # management object for that exchange + + exchanges=[] + for _b in self._brokers[0:2]: + _b.client_session.exchange_declare(exchange="fedX", type=params.exchange_type()) + self.assertEqual(_b.client_session.exchange_query(name="fedX").type, + params.exchange_type(), "exchange_declare failed!") + # pull the exchange out of qmf... + retries = 0 + my_exchange = None + timeout = time() + 10 + while my_exchange is None and time() <= timeout: + objs = qmf.getObjects(_broker=_b.qmf_broker, _class="exchange") + for ooo in objs: + if ooo.name == "fedX": + my_exchange = ooo + break + if my_exchange is None: + self.fail("QMF failed to find new exchange!") + exchanges.append(my_exchange) + + # + # on each broker, create a local queue bound to the exchange with the + # same key value. + # + + self._brokers[0].client_session.queue_declare(queue="fedX1", exclusive=True, auto_delete=True) + params.bind_queue(self._brokers[0].client_session, "fedX1", "fedX") + self.subscribe(self._brokers[0].client_session, queue="fedX1", destination="f1") + queue_0 = self._brokers[0].client_session.incoming("f1") + + self._brokers[1].client_session.queue_declare(queue="fedX1", exclusive=True, auto_delete=True) + params.bind_queue(self._brokers[1].client_session, "fedX1", "fedX") + self.subscribe(self._brokers[1].client_session, queue="fedX1", destination="f1") + queue_1 = self._brokers[1].client_session.incoming("f1") + + # now federate the two brokers + + # connect B0 --> B1 + result = self._brokers[1].qmf_object.connect(self._brokers[0].host, + self._brokers[0].port, + False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + # connect B1 --> B0 + result = self._brokers[0].qmf_object.connect(self._brokers[1].host, + self._brokers[1].port, + False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + # for each link, bridge the "fedX" exchanges: + + for _l in qmf.getObjects(_class="link"): + # print("Link=%s:%s %s" % (_l.host, _l.port, str(_l.getBroker()))) + result = _l.bridge(False, # durable + "fedX", # src + "fedX", # dst + "", # key + "", # tag + "", # excludes + False, # srcIsQueue + False, # srcIsLocal + True, # dynamic + 0, # sync + 0) # credit + self.assertEqual(result.status, 0) + + # wait for all the inter-broker links to become operational + operational = False + timeout = time() + 10 + while not operational and time() <= timeout: + operational = True + for _l in qmf.getObjects(_class="link"): + #print("Link=%s:%s %s" % (_l.host, _l.port, str(_l.state))) + if _l.state != "Operational": + operational = False + self.failUnless(operational, "inter-broker links failed to become operational.") + + # @todo - There is no way to determine when the bridge objects become + # active. + + # wait until the binding key has propagated to each broker - each + # broker should see 2 bindings (1 local, 1 remote) + + binding_counts = [2, 2] + self.assertEqual(len(binding_counts), len(exchanges), "Update Test!") + for i in range(2): + exchanges[i].update() + timeout = time() + 10 + while exchanges[i].bindingCount < binding_counts[i] and time() <= timeout: + exchanges[i].update() + self.failUnless(exchanges[i].bindingCount == binding_counts[i]) + + # send 10 msgs to B0 + for i in range(1, 11): + # dp = self._brokers[0].client_session.delivery_properties(routing_key=params.routing_key()) + dp = params.delivery_properties(self._brokers[0].client_session) + self._brokers[0].client_session.message_transfer(destination="fedX", message=Message(dp, "Message_trp %d" % i)) + + # get exactly 10 msgs on B0's local queue and B1's queue + for i in range(1, 11): + try: + msg = queue_0.get(timeout=5) + self.assertEqual("Message_trp %d" % i, msg.body) + msg = queue_1.get(timeout=5) + self.assertEqual("Message_trp %d" % i, msg.body) + except Empty: + self.fail("Only got %d msgs - expected 10" % i) + try: + extra = queue_0.get(timeout=1) + self.fail("Got unexpected message in queue_0: " + extra.body) + except Empty: None + + try: + extra = queue_1.get(timeout=1) + self.fail("Got unexpected message in queue_1: " + extra.body) + except Empty: None + + # + # Tear down the bridges between the two exchanges, then wait + # for the bindings to be cleaned up + # + + for _b in qmf.getObjects(_class="bridge"): + result = _b.close() + self.assertEqual(result.status, 0) + + binding_counts = [1, 1] + self.assertEqual(len(binding_counts), len(exchanges), "Update Test!") + for i in range(2): + exchanges[i].update() + timeout = time() + 10 + while exchanges[i].bindingCount != binding_counts[i] and time() <= timeout: + exchanges[i].update() + self.failUnless(exchanges[i].bindingCount == binding_counts[i]) + + # + # restore the bridges between the two exchanges, and wait for the + # bindings to propagate. + # + + for _l in qmf.getObjects(_class="link"): + # print("Link=%s:%s %s" % (_l.host, _l.port, str(_l.getBroker()))) + result = _l.bridge(False, # durable + "fedX", # src + "fedX", # dst + "", # key + "", # tag + "", # excludes + False, # srcIsQueue + False, # srcIsLocal + True, # dynamic + 0, # sync + 0) # credit + self.assertEqual(result.status, 0) + + binding_counts = [2, 2] + self.assertEqual(len(binding_counts), len(exchanges), "Update Test!") + for i in range(2): + exchanges[i].update() + timeout = time() + 10 + while exchanges[i].bindingCount != binding_counts[i] and time() <= timeout: + exchanges[i].update() + self.failUnless(exchanges[i].bindingCount == binding_counts[i]) + + # + # verify traffic flows correctly + # + + for i in range(1, 11): + #dp = self._brokers[1].client_session.delivery_properties(routing_key=params.routing_key()) + dp = params.delivery_properties(self._brokers[1].client_session) + self._brokers[1].client_session.message_transfer(destination="fedX", message=Message(dp, "Message_trp %d" % i)) + + # get exactly 10 msgs on B0's queue and B1's queue + for i in range(1, 11): + try: + msg = queue_0.get(timeout=5) + self.assertEqual("Message_trp %d" % i, msg.body) + msg = queue_1.get(timeout=5) + self.assertEqual("Message_trp %d" % i, msg.body) + except Empty: + self.fail("Only got %d msgs - expected 10" % i) + try: + extra = queue_0.get(timeout=1) + self.fail("Got unexpected message in queue_0: " + extra.body) + except Empty: None + + try: + extra = queue_1.get(timeout=1) + self.fail("Got unexpected message in queue_1: " + extra.body) + except Empty: None + + + # + # cleanup + # + params.unbind_queue(self._brokers[0].client_session, "fedX1", "fedX") + self._brokers[0].client_session.message_cancel(destination="f1") + self._brokers[0].client_session.queue_delete(queue="fedX1") + + params.unbind_queue(self._brokers[1].client_session, "fedX1", "fedX") + self._brokers[1].client_session.message_cancel(destination="f1") + self._brokers[1].client_session.queue_delete(queue="fedX1") + + for _b in qmf.getObjects(_class="bridge"): + result = _b.close() + self.assertEqual(result.status, 0) + + for _l in qmf.getObjects(_class="link"): + result = _l.close() + self.assertEqual(result.status, 0) + + for _b in self._brokers[0:2]: + _b.client_session.exchange_delete(exchange="fedX") + + self._teardown_brokers() + + self.verify_cleanup() + + + def test_multilink_direct(self): + """ Verify that two distinct links can be created between federated + brokers. + """ + self.startQmf() + qmf = self.qmf + self._setup_brokers() + src_broker = self._brokers[0] + dst_broker = self._brokers[1] + + # create a direct exchange on each broker + for _b in [src_broker, dst_broker]: + _b.client_session.exchange_declare(exchange="fedX.direct", type="direct") + self.assertEqual(_b.client_session.exchange_query(name="fedX.direct").type, + "direct", "exchange_declare failed!") + + # create destination queues + for _q in [("HiQ", "high"), ("MedQ", "medium"), ("LoQ", "low")]: + dst_broker.client_session.queue_declare(queue=_q[0], auto_delete=True) + dst_broker.client_session.exchange_bind(queue=_q[0], exchange="fedX.direct", binding_key=_q[1]) + + # create two connections, one for high priority traffic + for _q in ["HiPri", "Traffic"]: + result = dst_broker.qmf_object.create("link", _q, + {"host":src_broker.host, + "port":src_broker.port}, + False) + self.assertEqual(result.status, 0); + + links = qmf.getObjects(_broker=dst_broker.qmf_broker, _class="link") + for _l in links: + if _l.name == "HiPri": + hi_link = _l + elif _l.name == "Traffic": + data_link = _l + else: + self.fail("Unexpected Link found: " + _l.name) + + # now create a route for messages sent with key "high" to use the + # hi_link + result = dst_broker.qmf_object.create("bridge", "HiPriBridge", + {"link":hi_link.name, + "src":"fedX.direct", + "dest":"fedX.direct", + "key":"high"}, False) + self.assertEqual(result.status, 0); + + + # create routes for the "medium" and "low" links to use the normal + # data_link + for _b in [("MediumBridge", "medium"), ("LowBridge", "low")]: + result = dst_broker.qmf_object.create("bridge", _b[0], + {"link":data_link.name, + "src":"fedX.direct", + "dest":"fedX.direct", + "key":_b[1]}, False) + self.assertEqual(result.status, 0); + + # now wait for the links to become operational + for _l in [hi_link, data_link]: + expire_time = time() + 30 + while _l.state != "Operational" and time() < expire_time: + _l.update() + self.assertEqual(_l.state, "Operational", "Link failed to become operational") + + # verify each link uses a different connection + self.assertNotEqual(hi_link.connectionRef, data_link.connectionRef, + "Different links using the same connection") + + hi_conn = qmf.getObjects(_broker=dst_broker.qmf_broker, + _objectId=hi_link.connectionRef)[0] + data_conn = qmf.getObjects(_broker=dst_broker.qmf_broker, + _objectId=data_link.connectionRef)[0] + + + # send hi data, verify only goes over hi link + + r_ssn = dst_broker.connection.session() + hi_receiver = r_ssn.receiver("HiQ"); + med_receiver = r_ssn.receiver("MedQ"); + low_receiver = r_ssn.receiver("LoQ"); + + for _c in [hi_conn, data_conn]: + _c.update() + self.assertEqual(_c.msgsToClient, 0, "Unexpected messages received") + + s_ssn = src_broker.connection.session() + hi_sender = s_ssn.sender("fedX.direct/high") + med_sender = s_ssn.sender("fedX.direct/medium") + low_sender = s_ssn.sender("fedX.direct/low") + + try: + hi_sender.send(qpid.messaging.Message(content="hi priority")) + msg = hi_receiver.fetch(timeout=10) + r_ssn.acknowledge() + self.assertEqual(msg.content, "hi priority"); + except: + self.fail("Hi Pri message failure") + + hi_conn.update() + data_conn.update() + self.assertEqual(hi_conn.msgsToClient, 1, "Expected 1 hi pri message") + self.assertEqual(data_conn.msgsToClient, 0, "Expected 0 data messages") + + # send low and medium, verify it does not go over hi link + + try: + med_sender.send(qpid.messaging.Message(content="medium priority")) + msg = med_receiver.fetch(timeout=10) + r_ssn.acknowledge() + self.assertEqual(msg.content, "medium priority"); + except: + self.fail("Medium Pri message failure") + + hi_conn.update() + data_conn.update() + self.assertEqual(hi_conn.msgsToClient, 1, "Expected 1 hi pri message") + self.assertEqual(data_conn.msgsToClient, 1, "Expected 1 data message") + + try: + low_sender.send(qpid.messaging.Message(content="low priority")) + msg = low_receiver.fetch(timeout=10) + r_ssn.acknowledge() + self.assertEqual(msg.content, "low priority"); + except: + self.fail("Low Pri message failure") + + hi_conn.update() + data_conn.update() + self.assertEqual(hi_conn.msgsToClient, 1, "Expected 1 hi pri message") + self.assertEqual(data_conn.msgsToClient, 2, "Expected 2 data message") + + # cleanup + + for _b in qmf.getObjects(_broker=dst_broker.qmf_broker,_class="bridge"): + result = _b.close() + self.assertEqual(result.status, 0) + + for _l in qmf.getObjects(_broker=dst_broker.qmf_broker,_class="link"): + result = _l.close() + self.assertEqual(result.status, 0) + + for _q in [("HiQ", "high"), ("MedQ", "medium"), ("LoQ", "low")]: + dst_broker.client_session.exchange_unbind(queue=_q[0], exchange="fedX.direct", binding_key=_q[1]) + dst_broker.client_session.queue_delete(queue=_q[0]) + + for _b in [src_broker, dst_broker]: + _b.client_session.exchange_delete(exchange="fedX.direct") + + self._teardown_brokers() + + self.verify_cleanup() + + + def test_multilink_shared_queue(self): + """ Verify that two distinct links can be created between federated + brokers. + """ + self.startQmf() + qmf = self.qmf + self._setup_brokers() + src_broker = self._brokers[0] + dst_broker = self._brokers[1] + + # create a topic exchange on the destination broker + dst_broker.client_session.exchange_declare(exchange="fedX.topic", type="topic") + self.assertEqual(dst_broker.client_session.exchange_query(name="fedX.topic").type, + "topic", "exchange_declare failed!") + + # create a destination queue + dst_broker.client_session.queue_declare(queue="destQ", auto_delete=True) + dst_broker.client_session.exchange_bind(queue="destQ", exchange="fedX.topic", binding_key="srcQ") + + # create a single source queue + src_broker.client_session.queue_declare(queue="srcQ", auto_delete=True) + + # create two connections + for _q in ["Link1", "Link2"]: + result = dst_broker.qmf_object.create("link", _q, + {"host":src_broker.host, + "port":src_broker.port}, + False) + self.assertEqual(result.status, 0); + + links = qmf.getObjects(_broker=dst_broker.qmf_broker, _class="link") + self.assertEqual(len(links), 2) + + # now create two "parallel" queue routes from the source queue to the + # destination exchange. + result = dst_broker.qmf_object.create("bridge", "Bridge1", + {"link":"Link1", + "src":"srcQ", + "dest":"fedX.topic", + "srcIsQueue": True}, + False) + self.assertEqual(result.status, 0); + result = dst_broker.qmf_object.create("bridge", "Bridge2", + {"link":"Link2", + "src":"srcQ", + "dest":"fedX.topic", + "srcIsQueue": True}, + False) + self.assertEqual(result.status, 0); + + + # now wait for the links to become operational + for _l in links: + expire_time = time() + 30 + while _l.state != "Operational" and time() < expire_time: + _l.update() + self.assertEqual(_l.state, "Operational", "Link failed to become operational") + + # verify each link uses a different connection + self.assertNotEqual(links[0].connectionRef, links[1].connectionRef, + "Different links using the same connection") + + conn1 = qmf.getObjects(_broker=dst_broker.qmf_broker, + _objectId=links[0].connectionRef)[0] + conn2 = qmf.getObjects(_broker=dst_broker.qmf_broker, + _objectId=links[1].connectionRef)[0] + + # verify messages sent to the queue are pulled by each connection + + r_ssn = dst_broker.connection.session() + receiver = r_ssn.receiver("destQ"); + + for _c in [conn1, conn2]: + _c.update() + self.assertEqual(_c.msgsToClient, 0, "Unexpected messages received") + + s_ssn = src_broker.connection.session() + sender = s_ssn.sender("srcQ") + + try: + for x in range(5): + sender.send(qpid.messaging.Message(content="hello")) + for x in range(5): + msg = receiver.fetch(timeout=10) + self.assertEqual(msg.content, "hello"); + r_ssn.acknowledge() + except: + self.fail("Message failure") + + # expect messages to be split over each connection. + conn1.update() + conn2.update() + self.assertNotEqual(conn1.msgsToClient, 0, "No messages sent") + self.assertNotEqual(conn2.msgsToClient, 0, "No messages sent") + self.assertEqual(conn2.msgsToClient + conn1.msgsToClient, 5, + "Expected 5 messages total") + + for _b in qmf.getObjects(_broker=dst_broker.qmf_broker,_class="bridge"): + result = _b.close() + self.assertEqual(result.status, 0) + + for _l in qmf.getObjects(_broker=dst_broker.qmf_broker,_class="link"): + result = _l.close() + self.assertEqual(result.status, 0) + + dst_broker.client_session.exchange_unbind(queue="destQ", exchange="fedX.topic", binding_key="srcQ") + dst_broker.client_session.exchange_delete(exchange="fedX.topic") + + self._teardown_brokers() + + self.verify_cleanup() + + + def test_dynamic_direct_shared_queue(self): + """ + Route Topology: + + +<--- B1 + B0 <---+<--- B2 + +<--- B3 + """ + session = self.session + + # create the federation + + self.startQmf() + qmf = self.qmf + + self._setup_brokers() + + # create direct exchange on each broker, and retrieve the corresponding + # management object for that exchange + + exchanges=[] + for _b in self._brokers: + _b.client_session.exchange_declare(exchange="fedX.direct", type="direct") + self.assertEqual(_b.client_session.exchange_query(name="fedX.direct").type, + "direct", "exchange_declare failed!") + # pull the exchange out of qmf... + retries = 0 + my_exchange = None + while my_exchange is None: + objs = qmf.getObjects(_broker=_b.qmf_broker, _class="exchange") + for ooo in objs: + if ooo.name == "fedX.direct": + my_exchange = ooo + break + if my_exchange is None: + retries += 1 + self.failIfEqual(retries, 10, + "QMF failed to find new exchange!") + sleep(1) + exchanges.append(my_exchange) + + self.assertEqual(len(exchanges), len(self._brokers), "Exchange creation failed!") + + # Create 2 links per each source broker (1,2,3) to the downstream + # broker 0: + for _b in range(1,4): + for _l in ["dynamic", "queue"]: + result = self._brokers[0].qmf_object.create( "link", + "Link-%d-%s" % (_b, _l), + {"host":self._brokers[_b].host, + "port":self._brokers[_b].port}, False) + self.assertEqual(result.status, 0) + + # create queue on source brokers for use by the dynamic route + self._brokers[_b].client_session.queue_declare(queue="fedSrcQ", exclusive=False, auto_delete=True) + + for _l in range(1,4): + # for each dynamic link, create a dynamic bridge for the "fedX.direct" + # exchanges, using the fedSrcQ on each upstream source broker + result = self._brokers[0].qmf_object.create("bridge", + "Bridge-%d-dynamic" % _l, + {"link":"Link-%d-dynamic" % _l, + "src":"fedX.direct", + "dest":"fedX.direct", + "dynamic":True, + "queue":"fedSrcQ"}, False) + self.assertEqual(result.status, 0) + + # create a queue route that shares the queue used by the dynamic route + result = self._brokers[0].qmf_object.create("bridge", + "Bridge-%d-queue" % _l, + {"link":"Link-%d-queue" % _l, + "src":"fedSrcQ", + "dest":"fedX.direct", + "srcIsQueue":True}, False) + self.assertEqual(result.status, 0) + + + # wait for the inter-broker links to become operational + retries = 0 + operational = False + while not operational: + operational = True + for _l in qmf.getObjects(_class="link"): + #print("Link=%s:%s %s" % (_l.host, _l.port, str(_l.state))) + if _l.state != "Operational": + operational = False + if not operational: + retries += 1 + self.failIfEqual(retries, 10, + "inter-broker links failed to become operational.") + sleep(1) + + # @todo - There is no way to determine when the bridge objects become + # active. Hopefully, this is long enough! + sleep(6) + + # create a queue on B0, bound to "spudboy" + self._brokers[0].client_session.queue_declare(queue="DestQ", exclusive=True, auto_delete=True) + self._brokers[0].client_session.exchange_bind(queue="DestQ", exchange="fedX.direct", binding_key="spudboy") + + # subscribe to messages arriving on B2's queue + self.subscribe(self._brokers[0].client_session, queue="DestQ", destination="f1") + queue = self._brokers[0].client_session.incoming("f1") + + # wait until the binding key has propagated to each broker + + binding_counts = [1, 1, 1, 1] + self.assertEqual(len(binding_counts), len(exchanges), "Update Test!") + for i in range(3,-1,-1): + retries = 0 + exchanges[i].update() + while exchanges[i].bindingCount < binding_counts[i]: + retries += 1 + self.failIfEqual(retries, 10, + "binding failed to propagate to broker %d" + % i) + sleep(3) + exchanges[i].update() + + for _b in range(1,4): + # send 3 msgs from each source broker + for i in range(3): + dp = self._brokers[_b].client_session.delivery_properties(routing_key="spudboy") + self._brokers[_b].client_session.message_transfer(destination="fedX.direct", message=Message(dp, "Message_drp %d" % i)) + + # get exactly 9 (3 per broker) on B0 + for i in range(9): + msg = queue.get(timeout=5) + + try: + extra = queue.get(timeout=1) + self.fail("Got unexpected message in queue: " + extra.body) + except Empty: None + + # verify that messages went across every link + for _l in qmf.getObjects(_broker=self._brokers[0].qmf_broker, + _class="link"): + for _c in qmf.getObjects(_broker=self._brokers[0].qmf_broker, + _objectId=_l.connectionRef): + self.assertNotEqual(_c.msgsToClient, 0, "Messages did not pass over link as expected.") + + # cleanup + + self._brokers[0].client_session.exchange_unbind(queue="DestQ", exchange="fedX.direct", binding_key="spudboy") + self._brokers[0].client_session.message_cancel(destination="f1") + self._brokers[0].client_session.queue_delete(queue="DestQ") + + for _b in qmf.getObjects(_class="bridge"): + result = _b.close() + self.assertEqual(result.status, 0) + + for _l in qmf.getObjects(_class="link"): + result = _l.close() + self.assertEqual(result.status, 0) + + for _b in self._brokers: + _b.client_session.exchange_delete(exchange="fedX.direct") + + self._teardown_brokers() + + self.verify_cleanup() + + def test_dynamic_bounce_unbinds_named_queue(self): + """ Verify that a propagated binding is removed when the connection is + bounced + """ + session = self.session + + # create the federation + + self.startQmf() + qmf = self.qmf + + self._setup_brokers() + + # create exchange on each broker, and retrieve the corresponding + # management object for that exchange + + exchanges=[] + for _b in self._brokers[0:2]: + _b.client_session.exchange_declare(exchange="fedX", type="direct") + self.assertEqual(_b.client_session.exchange_query(name="fedX").type, + "direct", "exchange_declare failed!") + # pull the exchange out of qmf... + retries = 0 + my_exchange = None + timeout = time() + 10 + while my_exchange is None and time() <= timeout: + objs = qmf.getObjects(_broker=_b.qmf_broker, _class="exchange") + for ooo in objs: + if ooo.name == "fedX": + my_exchange = ooo + break + if my_exchange is None: + self.fail("QMF failed to find new exchange!") + exchanges.append(my_exchange) + + # on the destination broker, create a binding for propagation + self._brokers[0].client_session.queue_declare(queue="fedDstQ") + self._brokers[0].client_session.exchange_bind(queue="fedDstQ", exchange="fedX", binding_key="spud") + + # on the source broker, create a bridge queue + self._brokers[1].client_session.queue_declare(queue="fedSrcQ") + + # connect B1 --> B0 + result = self._brokers[0].qmf_object.create( "link", + "Link-dynamic", + {"host":self._brokers[1].host, + "port":self._brokers[1].port}, False) + self.assertEqual(result.status, 0) + + # bridge the "fedX" exchange: + result = self._brokers[0].qmf_object.create("bridge", + "Bridge-dynamic", + {"link":"Link-dynamic", + "src":"fedX", + "dest":"fedX", + "dynamic":True, + "queue":"fedSrcQ"}, False) + self.assertEqual(result.status, 0) + + # wait for the inter-broker links to become operational + operational = False + timeout = time() + 10 + while not operational and time() <= timeout: + operational = True + for _l in qmf.getObjects(_class="link"): + #print("Link=%s:%s %s" % (_l.host, _l.port, str(_l.state))) + if _l.state != "Operational": + operational = False + self.failUnless(operational, "inter-broker links failed to become operational.") + + # wait until the binding key has propagated to the src broker + exchanges[1].update() + timeout = time() + 10 + while exchanges[1].bindingCount < 1 and time() <= timeout: + exchanges[1].update() + self.failUnless(exchanges[1].bindingCount == 1) + + # + # Tear down the bridges between the two exchanges, then wait + # for the bindings to be cleaned up + # + for _b in qmf.getObjects(_class="bridge"): + result = _b.close() + self.assertEqual(result.status, 0) + exchanges[1].update() + timeout = time() + 10 + while exchanges[1].bindingCount != 0 and time() <= timeout: + exchanges[1].update() + self.failUnless(exchanges[1].bindingCount == 0) + + self._brokers[1].client_session.queue_delete(queue="fedSrcQ") + + for _b in qmf.getObjects(_class="bridge"): + result = _b.close() + self.assertEqual(result.status, 0) + + for _l in qmf.getObjects(_class="link"): + result = _l.close() + self.assertEqual(result.status, 0) + + for _b in self._brokers[0:2]: + _b.client_session.exchange_delete(exchange="fedX") + + self._teardown_brokers() + + self.verify_cleanup() + + def test_credit(self): + """ Test a federation link configured to use explict acks and a credit + limit + """ + session = self.session + + # setup queue on remote broker and add some messages + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_credit") + r_session.queue_declare(queue="my-bridge-queue", auto_delete=True) + + #setup queue to receive messages from local broker + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + session.exchange_bind(queue="fed1", exchange="amq.fanout") + self.subscribe(queue="fed1", destination="f1") + queue = session.incoming("f1") + + self.startQmf() + qmf = self.qmf + broker = qmf.getObjects(_class="broker")[0] + result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0, result) + + link = qmf.getObjects(_class="link")[0] + + # now wait for Link to go operational + retries = 0 + operational = False + while not operational: + link.update() + if link.state == "Operational": + operational = True; + if not operational: + retries += 1 + self.failIfEqual(retries, 10, + "inter-broker links failed to become operational.") + sleep(1) + + # create the subscription + result = link.bridge(False, "my-bridge-queue", "amq.fanout", "my-key", + "", "", True, False, False, + 3, # explicit ack, with sync every 3 msgs + 7) # msg credit + self.assertEqual(result.status, 0, result) + bridge = qmf.getObjects(_class="bridge")[0] + + # generate enough traffic to trigger flow control and syncs + for i in range(1000): + dp = r_session.delivery_properties(routing_key="my-bridge-queue") + r_session.message_transfer(message=Message(dp, "Message %d" % i)) + + for i in range(1000): + try: + msg = queue.get(timeout=5) + self.assertEqual("Message %d" % i, msg.body) + except Empty: + self.fail("Failed to find expected message containing 'Message %d'" % i) + try: + extra = queue.get(timeout=1) + self.fail("Got unexpected message in queue: " + extra.body) + except Empty: None + + result = bridge.close() + self.assertEqual(result.status, 0, result) + result = link.close() + self.assertEqual(result.status, 0, result) + + r_session.close() + r_conn.close() + + self.verify_cleanup() + diff --git a/qpid/cpp/src/tests/federation_sys.py b/qpid/cpp/src/tests/federation_sys.py new file mode 100755 index 0000000000..be9613bb9f --- /dev/null +++ b/qpid/cpp/src/tests/federation_sys.py @@ -0,0 +1,977 @@ +#!/usr/bin/env python +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from inspect import stack +from qpid import messaging +from qpid.messaging import Message +from qpid.messaging.exceptions import Empty +from qpid.testlib import TestBase010 +from random import randint +from sys import stdout +from time import sleep + + +class Enum(object): + def __init__(self, **entries): + self.__dict__.update(entries) + def __repr__(self): + args = ['%s=%s' % (k, repr(v)) for (k,v) in vars(self).items()] + return 'Enum(%s)' % ', '.join(args) + + +class QmfTestBase010(TestBase010): + + _brokers = [] + _links = [] + _bridges = [] + _alt_exch_ops = Enum(none=0, create=1, delete=2) + + class _Broker(object): + """ + This broker proxy object holds the Qmf proxy to a broker of known address as well as the QMF broker + object, connection and sessions to the broker. + """ + def __init__(self, url): + self.url = url # format: "host:port" + url_parts = url.split(':') + self.host = url_parts[0] + self.port = int(url_parts[1]) + self.qmf_broker = None + self.connection = messaging.Connection.establish(self.url) + self.sessions = [] + def __str__(self): + return "_Broker %s:%s (%d open sessions)" % (self.host, self.port, len(self.sessions)) + def destroy(self, qmf = None): + if qmf is not None: + qmf.delBroker(self.qmf_broker.getBroker()) + for session in self.sessions: + try: # Session may have been closed by broker error + session.close() + except Exception, e: print "WARNING: %s: Unable to close session %s (%s): %s %s" % (self, session, hex(id(session)), type(e), e) + try: # Connection may have been closed by broker error + self.connection.close() + except Exception, e: print "WARNING: %s: Unable to close connection %s (%s): %s %s" % (self, self.connection, hex(id(self.connection)), type(e), e) + def session(self, name, transactional_flag = False): + session = self.connection.session(name, transactional_flag) + self.sessions.append(session) + return session + + def setUp(self): + """ + Called one before each test starts + """ + TestBase010.setUp(self) + self.startQmf(); + + def tearDown(self): + """ + Called once after each test competes. Close all Qmf objects (bridges, links and brokers) + """ + while len(self._bridges): + self._bridges.pop().close() + while len(self._links): + self._links.pop().close() + while len(self._brokers): + b = self._brokers.pop() + if len(self._brokers) <= 1: + b.destroy(None) + else: + b.destroy(self.qmf) + TestBase010.tearDown(self) + self.qmf.close() + + #--- General test utility functions + + def _get_name(self): + """ + Return the name of method which called this method stripped of "test_" prefix. Used for naming + queues and exchanges on a per-test basis. + """ + return stack()[1][3][5:] + + def _get_broker_port(self, key): + """ + Get the port of a broker defined in the environment using -D<key>=portno + """ + return int(self.defines[key]) + + def _get_send_address(self, exch_name, queue_name): + """ + Get an address to which to send messages based on the exchange name and queue name, but taking into account + that the exchange name may be "" (the default exchange), in whcih case the format changes slightly. + """ + if len(exch_name) == 0: # Default exchange + return queue_name + return "%s/%s" % (exch_name, queue_name) + + def _get_broker(self, broker_port_key): + """ + Read the port numbers for pre-started brokers from the environment using keys, then find or create and return + the Qmf broker proxy for the appropriate broker + """ + port = self._get_broker_port(broker_port_key) + return self._find_create_broker("localhost:%s" % port) + ################ + def _get_msg_subject(self, topic_key): + """ + Return an appropriate subject for sending a message to a known topic. Return None if there is no topic. + """ + if len(topic_key) == 0: return None + if "*" in topic_key: return topic_key.replace("*", "test") + if "#" in topic_key: return topic_key.replace("#", "multipart.test") + return topic_key + + def _send_msgs(self, session_name, broker, addr, msg_count, msg_content = "Message_%03d", topic_key = "", + msg_durable_flag = False, enq_txn_size = 0): + """ + Send messages to a broker using address addr + """ + send_session = broker.session(session_name, transactional_flag = enq_txn_size > 0) + sender = send_session.sender(addr) + txn_cnt = 0 + for i in range(0, msg_count): + sender.send(Message(msg_content % (i + 1), subject = self._get_msg_subject(topic_key), durable = msg_durable_flag)) + if enq_txn_size > 0: + txn_cnt += 1 + if txn_cnt >= enq_txn_size: + send_session.commit() + txn_cnt = 0 + if enq_txn_size > 0 and txn_cnt > 0: + send_session.commit() + sender.close() + send_session.close() + + def _receive_msgs(self, session_name, broker, addr, msg_count, msg_content = "Message_%03d", deq_txn_size = 0, + timeout = 0): + """ + Receive messages from a broker + """ + receive_session = broker.session(session_name, transactional_flag = deq_txn_size > 0) + receiver = receive_session.receiver(addr) + txn_cnt = 0 + for i in range(0, msg_count): + try: + msg = receiver.fetch(timeout = timeout) + if deq_txn_size > 0: + txn_cnt += 1 + if txn_cnt >= deq_txn_size: + receive_session.commit() + txn_cnt = 0 + receive_session.acknowledge() + except Empty: + if deq_txn_size > 0: receive_session.rollback() + receiver.close() + receive_session.close() + if i == 0: + self.fail("Broker %s queue \"%s\" is empty" % (broker.qmf_broker.getBroker().getUrl(), addr)) + else: + self.fail("Unable to receive message %d from broker %s queue \"%s\"" % (i, broker.qmf_broker.getBroker().getUrl(), addr)) + if msg.content != msg_content % (i + 1): + receiver.close() + receive_session.close() + self.fail("Unexpected message \"%s\", was expecting \"%s\"." % (msg.content, msg_content % (i + 1))) + try: + msg = receiver.fetch(timeout = 0) + if deq_txn_size > 0: receive_session.rollback() + receiver.close() + receive_session.close() + self.fail("Extra message \"%s\" found on broker %s address \"%s\"" % (msg.content, broker.qmf_broker.getBroker().getUrl(), addr)) + except Empty: + pass + if deq_txn_size > 0 and txn_cnt > 0: + receive_session.commit() + receiver.close() + receive_session.close() + + #--- QMF-specific utility functions + + def _get_qmf_property(self, props, key): + """ + Get the value of a named property key kj from a property list [(k0, v0), (k1, v1), ... (kn, vn)]. + """ + for k,v in props: + if k.name == key: + return v + return None + + def _check_qmf_return(self, method_result): + """ + Check the result of a Qmf-defined method call + """ + self.assertTrue(method_result.status == 0, method_result.text) + + def _check_optional_qmf_property(self, qmf_broker, type, qmf_object, key, expected_val, obj_ref_flag): + """ + Optional Qmf properties don't show up in the properties list when they are not specified. Checks for + these property types involve searching the properties list and making sure it is present or not as + expected. + """ + val = self._get_qmf_property(qmf_object.getProperties(), key) + if val is None: + if len(expected_val) > 0: + self.fail("%s %s exists, but has does not have %s property. Expected value: \"%s\"" % + (type, qmf_object.name, key, expected_val)) + else: + if len(expected_val) > 0: + if obj_ref_flag: + obj = self.qmf.getObjects(_objectId = val, _broker = qmf_broker.getBroker()) + self.assertEqual(len(obj), 1, "More than one object with the same objectId: %s" % obj) + val = obj[0].name + self.assertEqual(val, expected_val, "%s %s exists, but has incorrect %s property. Found \"%s\", expected \"%s\"" % + (type, qmf_object.name, key, val, expected_val)) + else: + self.fail("%s %s exists, but has an unexpected %s property \"%s\" set." % (type, qmf_object.name, key, val)) + + #--- Find/create Qmf broker objects + + def _find_qmf_broker(self, url): + """ + Find the Qmf broker object for the given broker URL. The broker must have been previously added to Qmf through + addBroker() + """ + for b in self.qmf.getObjects(_class="broker"): + if b.getBroker().getUrl() == url: + return b + return None + + def _find_create_broker(self, url): + """ + Find a running broker through Qmf. If it does not exist, add it (assuming the broker is already running). + """ + broker = self._Broker(url) + self._brokers.append(broker) + if self.qmf is not None: + qmf_broker = self._find_qmf_broker(broker.url) + if qmf_broker is None: + self.qmf.addBroker(broker.url) + broker.qmf_broker = self._find_qmf_broker(broker.url) + else: + broker.qmf_broker = qmf_broker + return broker + + #--- Find/create/delete exchanges + + def _find_qmf_exchange(self, qmf_broker, name, type, alternate, durable, auto_delete): + """ + Find Qmf exchange object + """ + for e in self.qmf.getObjects(_class="exchange", _broker = qmf_broker.getBroker()): + if e.name == name: + if len(name) == 0 or (len(name) >= 4 and name[:4] == "amq."): return e # skip checks for special exchanges + self.assertEqual(e.type, type, + "Exchange \"%s\" exists, but is of unexpected type %s; expected type %s." % + (name, e.type, type)) + self._check_optional_qmf_property(qmf_broker, "Exchange", e, "altExchange", alternate, True) + self.assertEqual(e.durable, durable, + "Exchange \"%s\" exists, but has incorrect durability. Found durable=%s, expected durable=%s" % + (name, e.durable, durable)) + self.assertEqual(e.autoDelete, auto_delete, + "Exchange \"%s\" exists, but has incorrect auto-delete property. Found %s, expected %s" % + (name, e.autoDelete, auto_delete)) + return e + return None + + def _find_create_qmf_exchange(self, qmf_broker, name, type, alternate, durable, auto_delete, args): + """ + Find Qmf exchange object if exchange exists, create exchange and return its Qmf object if not + """ + e = self._find_qmf_exchange(qmf_broker, name, type, alternate, durable, auto_delete) + if e is not None: return e + # Does not exist, so create it + props = dict({"exchange-type": type, "type": type, "durable": durable, "auto-delete": auto_delete, "alternate-exchange": alternate}, **args) + self._check_qmf_return(qmf_broker.create(type="exchange", name=name, properties=props, strict=True)) + e = self._find_qmf_exchange(qmf_broker, name, type, alternate, durable, auto_delete) + self.assertNotEqual(e, None, "Creation of exchange %s on broker %s failed" % (name, qmf_broker.getBroker().getUrl())) + return e + + def _find_delete_qmf_exchange(self, qmf_broker, name, type, alternate, durable, auto_delete): + """ + Find and delete Qmf exchange object if it exists + """ + e = self._find_qmf_exchange(qmf_broker, name, type, alternate, durable, auto_delete) + if e is not None and not auto_delete: + self._check_qmf_return(qmf_broker.delete(type="exchange", name=name, options={})) + + #--- Find/create/delete queues + + def _find_qmf_queue(self, qmf_broker, name, alternate_exchange, durable, exclusive, auto_delete): + """ + Find a Qmf queue object + """ + for q in self.qmf.getObjects(_class="queue", _broker = qmf_broker.getBroker()): + if q.name == name: + self._check_optional_qmf_property(qmf_broker, "Queue", q, "altExchange", alternate_exchange, True) + self.assertEqual(q.durable, durable, + "Queue \"%s\" exists, but has incorrect durable property. Found %s, expected %s" % + (name, q.durable, durable)) + self.assertEqual(q.exclusive, exclusive, + "Queue \"%s\" exists, but has incorrect exclusive property. Found %s, expected %s" % + (name, q.exclusive, exclusive)) + self.assertEqual(q.autoDelete, auto_delete, + "Queue \"%s\" exists, but has incorrect auto-delete property. Found %s, expected %s" % + (name, q.autoDelete, auto_delete)) + return q + return None + + def _find_create_qmf_queue(self, qmf_broker, name, alternate_exchange, durable, exclusive, auto_delete, args): + """ + Find Qmf queue object if queue exists, create queue and return its Qmf object if not + """ + q = self._find_qmf_queue(qmf_broker, name, alternate_exchange, durable, exclusive, auto_delete) + if q is not None: return q + # Queue does not exist, so create it + props = dict({"durable": durable, "auto-delete": auto_delete, "exclusive": exclusive, "alternate-exchange": alternate_exchange}, **args) + self._check_qmf_return(qmf_broker.create(type="queue", name=name, properties=props, strict=True)) + q = self._find_qmf_queue(qmf_broker, name, alternate_exchange, durable, exclusive, auto_delete) + self.assertNotEqual(q, None, "Creation of queue %s on broker %s failed" % (name, qmf_broker.getBroker().getUrl())) + return q + + def _find_delete_qmf_queue(self, qmf_broker, name, alternate_exchange, durable, exclusive, auto_delete, args): + """ + Find and delete Qmf queue object if it exists + """ + q = self._find_qmf_queue(qmf_broker, name, alternate_exchange, durable, exclusive, auto_delete) + if q is not None and not auto_delete: + self._check_qmf_return(qmf_broker.delete(type="queue", name=name, options={})) + + #--- Find/create/delete bindings (between an exchange and a queue) + + def _find_qmf_binding(self, qmf_broker, qmf_exchange, qmf_queue, binding_key, binding_args): + """ + Find a Qmf binding object + """ + for b in self.qmf.getObjects(_class="binding", _broker = qmf_broker.getBroker()): + if b.exchangeRef == qmf_exchange.getObjectId() and b.queueRef == qmf_queue.getObjectId(): + if qmf_exchange.type != "fanout": # Fanout ignores the binding key, and always returns "" as the key + self.assertEqual(b.bindingKey, binding_key, + "Binding between exchange %s and queue %s exists, but has mismatching binding key: Found %s, expected %s." % + (qmf_exchange.name, qmf_queue.name, b.bindingKey, binding_key)) + self.assertEqual(b.arguments, binding_args, + "Binding between exchange %s and queue %s exists, but has mismatching arguments: Found %s, expected %s" % + (qmf_exchange.name, qmf_queue.name, b.arguments, binding_args)) + return b + return None + + def _find_create_qmf_binding(self, qmf_broker, qmf_exchange, qmf_queue, binding_key, binding_args): + """ + Find Qmf binding object if it exists, create binding and return its Qmf object if not + """ + b = self._find_qmf_binding(qmf_broker, qmf_exchange, qmf_queue, binding_key, binding_args) + if b is not None: return b + # Does not exist, so create it + self._check_qmf_return(qmf_broker.create(type="binding", name="%s/%s/%s" % (qmf_exchange.name, qmf_queue.name, binding_key), properties=binding_args, strict=True)) + b = self._find_qmf_binding(qmf_broker, qmf_exchange, qmf_queue, binding_key, binding_args) + self.assertNotEqual(b, None, "Creation of binding between exchange %s and queue %s with key %s failed" % + (qmf_exchange.name, qmf_queue.name, binding_key)) + return b + + def _find_delete_qmf_binding(self, qmf_broker, qmf_exchange, qmf_queue, binding_key, binding_args): + """ + Find and delete Qmf binding object if it exists + """ + b = self._find_qmf_binding(qmf_broker, qmf_exchange, qmf_queue, binding_key, binding_args) + if b is not None: + if len(qmf_exchange.name) > 0: # not default exchange + self._check_qmf_return(qmf_broker.delete(type="binding", name="%s/%s/%s" % (qmf_exchange.name, qmf_queue.name, binding_key), options={})) + + #--- Find/create a link + + def _find_qmf_link(self, qmf_from_broker_proxy, host, port): + """ + Find a Qmf link object + """ + for l in self.qmf.getObjects(_class="link", _broker=qmf_from_broker_proxy): + if l.host == host and l.port == port: + return l + return None + + def _find_create_qmf_link(self, qmf_from_broker, qmf_to_broker_proxy, link_durable_flag, auth_mechanism, user_id, + password, transport, pause_interval, link_ready_timeout): + """ + Find a Qmf link object if it exists, create it and return its Qmf link object if not + """ + to_broker_host = qmf_to_broker_proxy.host + to_broker_port = qmf_to_broker_proxy.port + l = self._find_qmf_link(qmf_from_broker.getBroker(), to_broker_host, to_broker_port) + if l is not None: return l + # Does not exist, so create it + self._check_qmf_return(qmf_from_broker.connect(to_broker_host, to_broker_port, link_durable_flag, auth_mechanism, user_id, password, transport)) + l = self._find_qmf_link(qmf_from_broker.getBroker(), to_broker_host, to_broker_port) + self.assertNotEqual(l, None, "Creation of link from broker %s to broker %s failed" % + (qmf_from_broker.getBroker().getUrl(), qmf_to_broker_proxy.getUrl())) + self._wait_for_link(l, pause_interval, link_ready_timeout) + return l + + def _wait_for_link(self, link, pause_interval, link_ready_timeout): + """ + Wait for link to become active (state=Operational) + """ + tot_time = 0 + link.update() + while link.state != "Operational" and tot_time < link_ready_timeout: + sleep(pause_interval) + tot_time += pause_interval + link.update() + self.assertEqual(link.state, "Operational", "Timeout: Link not operational, state=%s" % link.state) + + #--- Find/create a bridge + + def _find_qmf_bridge(self, qmf_broker_proxy, qmf_link, source, destination, key): + """ + Find a Qmf link object + """ + for b in self.qmf.getObjects(_class="bridge", _broker=qmf_broker_proxy): + if b.linkRef == qmf_link.getObjectId() and b.src == source and b.dest == destination and b.key == key: + return b + return None + + def _find_create_qmf_bridge(self, qmf_broker_proxy, qmf_link, queue_name, exch_name, topic_key, + queue_route_type_flag, bridge_durable_flag): + """ + Find a Qmf bridge object if it exists, create it and return its Qmf object if not + """ + if queue_route_type_flag: + src = queue_name + dest = exch_name + key = "" + else: + src = exch_name + dest = exch_name + if len(topic_key) > 0: + key = topic_key + else: + key = queue_name + b = self._find_qmf_bridge(qmf_broker_proxy, qmf_link, src, dest, key) + if b is not None: + return b + # Does not exist, so create it + self._check_qmf_return(qmf_link.bridge(bridge_durable_flag, src, dest, key, "", "", queue_route_type_flag, False, False, 1, 0)) + b = self._find_qmf_bridge(qmf_broker_proxy, qmf_link, src, dest, key) + self.assertNotEqual(b, None, "Bridge creation failed: src=%s dest=%s key=%s" % (src, dest, key)) + return b + + def _wait_for_bridge(self, bridge, src_broker, dest_broker, exch_name, queue_name, topic_key, pause_interval, + bridge_ready_timeout): + """ + Wait for bridge to become active by sending messages over the bridge at 1 sec intervals until they are + observed at the destination. + """ + tot_time = 0 + active = False + send_session = src_broker.session("tx") + sender = send_session.sender(self._get_send_address(exch_name, queue_name)) + src_receive_session = src_broker.session("src_rx") + src_receiver = src_receive_session.receiver(queue_name) + dest_receive_session = dest_broker.session("dest_rx") + dest_receiver = dest_receive_session.receiver(queue_name) + while not active and tot_time < bridge_ready_timeout: + sender.send(Message("xyz123", subject = self._get_msg_subject(topic_key))) + try: + src_receiver.fetch(timeout = 0) + src_receive_session.acknowledge() + # Keep receiving msgs, as several may have accumulated + while True: + dest_receiver.fetch(timeout = 0) + dest_receive_session.acknowledge() + sleep(1) + active = True + except Empty: + sleep(pause_interval) + tot_time += pause_interval + dest_receiver.close() + dest_receive_session.close() + src_receiver.close() + src_receive_session.close() + sender.close() + send_session.close() + self.assertTrue(active, "Bridge failed to become active after %ds: %s" % (bridge_ready_timeout, bridge)) + + #--- Find/create/delete utility functions + + def _create_and_bind(self, qmf_broker, exchange_args, queue_args, binding_args): + """ + Create a binding between a named exchange and queue on a broker + """ + e = self._find_create_qmf_exchange(qmf_broker, **exchange_args) + q = self._find_create_qmf_queue(qmf_broker, **queue_args) + return self._find_create_qmf_binding(qmf_broker, e, q, **binding_args) + + def _check_alt_exchange(self, qmf_broker, alt_exch_name, alt_exch_type, alt_exch_op): + """ + Check for existence of alternate exchange. Return the Qmf exchange proxy object for the alternate exchange + """ + if len(alt_exch_name) == 0: return None + if alt_exch_op == _alt_exch_ops.create: + return self._find_create_qmf_exchange(qmf_broker=qmf_broker, name=alt_exch_name, type=alt_exch_type, + alternate="", durable=False, auto_delete=False, args={}) + if alt_exch_op == _alt_exch_ops.delete: + return self._find_delete_qmf_exchange(qmf_broker=qmf_broker, name=alt_exch_name, type=alt_exch_type, + alternate="", durable=False, auto_delete=False) + return self._find_qmf_exchange(qmf_broker=qmf_broker, name=alt_exchange_name, type=alt_exchange_type, + alternate="", durable=False, auto_delete=False) + + def _delete_queue_binding(self, qmf_broker, exchange_args, queue_args, binding_args): + """ + Delete a queue and the binding between it and the exchange + """ + e = self._find_qmf_exchange(qmf_broker, exchange_args["name"], exchange_args["type"], exchange_args["alternate"], exchange_args["durable"], exchange_args["auto_delete"]) + q = self._find_qmf_queue(qmf_broker, queue_args["name"], queue_args["alternate_exchange"], queue_args["durable"], queue_args["exclusive"], queue_args["auto_delete"]) + self._find_delete_qmf_binding(qmf_broker, e, q, **binding_args) + self._find_delete_qmf_queue(qmf_broker, **queue_args) + + def _create_route(self, queue_route_type_flag, src_broker, dest_broker, exch_name, queue_name, topic_key, + link_durable_flag, bridge_durable_flag, auth_mechanism, user_id, password, transport, + pause_interval = 1, link_ready_timeout = 20, bridge_ready_timeout = 20): + """ + Create a route from a source broker to a destination broker + """ + l = self._find_create_qmf_link(dest_broker.qmf_broker, src_broker.qmf_broker.getBroker(), link_durable_flag, + auth_mechanism, user_id, password, transport, pause_interval, link_ready_timeout) + self._links.append(l) + b = self._find_create_qmf_bridge(dest_broker.qmf_broker.getBroker(), l, queue_name, exch_name, topic_key, + queue_route_type_flag, bridge_durable_flag) + self._bridges.append(b) + self._wait_for_bridge(b, src_broker, dest_broker, exch_name, queue_name, topic_key, pause_interval, bridge_ready_timeout) + + # Parameterized test - entry point for tests + + def _do_test(self, + test_name, # Name of test + exch_name = "amq.direct", # Remote exchange name + exch_type = "direct", # Remote exchange type + exch_alt_exch = "", # Remote exchange alternate exchange + exch_alt_exch_type = "direct", # Remote exchange alternate exchange type + exch_durable_flag = False, # Remote exchange durability + exch_auto_delete_flag = False, # Remote exchange auto-delete property + exch_x_args = {}, # Remote exchange args + queue_alt_exch = "", # Remote queue alternate exchange + queue_alt_exch_type = "direct", # Remote queue alternate exchange type + queue_durable_flag = False, # Remote queue durability + queue_exclusive_flag = False, # Remote queue exclusive property + queue_auto_delete_flag = False, # Remote queue auto-delete property + queue_x_args = {}, # Remote queue args + binding_durable_flag = False, # Remote binding durability + binding_x_args = {}, # Remote binding args + topic_key = "", # Binding key For remote topic exchanges only + msg_count = 10, # Number of messages to send + msg_durable_flag = False, # Message durability + link_durable_flag = False, # Route link durability + bridge_durable_flag = False, # Route bridge durability + queue_route_type_flag = False, # Route type: false = bridge route, true = queue route + enq_txn_size = 0, # Enqueue transaction size, 0 = no transactions + deq_txn_size = 0, # Dequeue transaction size, 0 = no transactions + alt_exch_op = _alt_exch_ops.create,# Op on alt exch [create (ensure present), delete (ensure not present), none (neither create nor delete)] + auth_mechanism = "", # Authorization mechanism for linked broker + user_id = "", # User ID for authorization on linked broker + password = "", # Password for authorization on linked broker + transport = "tcp" # Transport for route to linked broker + ): + """ + Parameterized federation test. Sets up a federated link between a source broker and a destination broker and + checks that messages correctly pass over the link to the destination. Where appropriate (non-queue-routes), also + checks for the presence of messages on the source broker. + + In these tests, the concept is to create a LOCAL broker, then create a link to a REMOTE broker using federation. + In other words, the messages sent to the LOCAL broker will be replicated on the REMOTE broker, and tests are + performed on the REMOTE broker to check that the required messages are present. In the case of regular routes, + the LOCAL broker will also retain the messages, and a similar test is performed on this broker. + + TODO: There are several items to improve here: + 1. _do_test() is rather general. Rather create a version for each exchange type and test the exchange/queue + interaction in more detail based on the exchange type + 2. Add a headers and an xml exchange type + 3. Restructure the tests to start and stop brokers directly rather than relying on previously + started brokers. Then persistence can be checked by stopping and restarting the brokers. In particular, + test the persistence of links and bridges, both of which take a persistence flag. + 4. Test the behavior of the alternate exchanges when messages are sourced through a link. Also check behavior + when the alternate exchange is not present or is deleted after the reference is made. + 5. Test special queue types (eg LVQ) + """ + local_broker = self._get_broker("local-port") + remote_broker = self._get_broker("remote-port") + + # Check alternate exchanges exist (and create them if not) on both local and remote brokers + self._check_alt_exchange(local_broker.qmf_broker, exch_alt_exch, exch_alt_exch_type, alt_exch_op) + self._check_alt_exchange(local_broker.qmf_broker, queue_alt_exch, queue_alt_exch_type, alt_exch_op) + self._check_alt_exchange(remote_broker.qmf_broker, exch_alt_exch, exch_alt_exch_type, alt_exch_op) + self._check_alt_exchange(remote_broker.qmf_broker, queue_alt_exch, queue_alt_exch_type, alt_exch_op) + + queue_name = "queue_%s" % test_name + exchange_args = {"name": exch_name, "type": exch_type, "alternate": exch_alt_exch, + "durable": exch_durable_flag, "auto_delete": exch_auto_delete_flag, "args": exch_x_args} + queue_args = {"name": queue_name, "alternate_exchange": queue_alt_exch, "durable": queue_durable_flag, + "exclusive": queue_exclusive_flag, "auto_delete": queue_auto_delete_flag, "args": queue_x_args} + binding_args = {"binding_args": binding_x_args} + if exch_type == "topic": + self.assertTrue(len(topic_key) > 0, "Topic exchange selected, but no topic key was set.") + binding_args["binding_key"] = topic_key + elif exch_type == "direct": + binding_args["binding_key"] = queue_name + else: + binding_args["binding_key"] = "" + self._create_and_bind(qmf_broker=local_broker.qmf_broker, exchange_args=exchange_args, queue_args=queue_args, binding_args=binding_args) + self._create_and_bind(qmf_broker=remote_broker.qmf_broker, exchange_args=exchange_args, queue_args=queue_args, binding_args=binding_args) + self._create_route(queue_route_type_flag, local_broker, remote_broker, exch_name, queue_name, topic_key, + link_durable_flag, bridge_durable_flag, auth_mechanism, user_id, password, transport) + + self._send_msgs("send_session", local_broker, addr = self._get_send_address(exch_name, queue_name), + msg_count = msg_count, topic_key = topic_key, msg_durable_flag = msg_durable_flag, enq_txn_size = enq_txn_size) + if not queue_route_type_flag: + self._receive_msgs("local_receive_session", local_broker, addr = queue_name, msg_count = msg_count, deq_txn_size = deq_txn_size) + self._receive_msgs("remote_receive_session", remote_broker, addr = queue_name, msg_count = msg_count, deq_txn_size = deq_txn_size, timeout = 5) + + # Clean up + self._delete_queue_binding(qmf_broker=local_broker.qmf_broker, exchange_args=exchange_args, queue_args=queue_args, binding_args=binding_args) + self._delete_queue_binding(qmf_broker=remote_broker.qmf_broker, exchange_args=exchange_args, queue_args=queue_args, binding_args=binding_args) + +class A_ShortTests(QmfTestBase010): + + def test_route_defaultExch(self): + self._do_test(self._get_name()) + + def test_queueRoute_defaultExch(self): + self._do_test(self._get_name(), queue_route_type_flag=True) + + +class A_LongTests(QmfTestBase010): + + def test_route_amqDirectExch(self): + self._do_test(self._get_name(), exch_name="amq.direct") + + def test_queueRoute_amqDirectExch(self): + self._do_test(self._get_name(), exch_name="amq.direct", queue_route_type_flag=True) + + + def test_route_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange") + + def test_queueRoute_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_route_type_flag=True) + + + def test_route_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout") + + def test_queueRoute_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_route_type_flag=True) + + + def test_route_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#") + + def test_queueRoute_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_route_type_flag=True) + + +class B_ShortTransactionTests(QmfTestBase010): + + def test_txEnq01_route_defaultExch(self): + self._do_test(self._get_name(), enq_txn_size=1) + + def test_txEnq01_queueRoute_defaultExch(self): + self._do_test(self._get_name(), queue_route_type_flag=True, enq_txn_size=1) + + def test_txEnq01_txDeq01_route_defaultExch(self): + self._do_test(self._get_name(), enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_queueRoute_defaultExch(self): + self._do_test(self._get_name(), queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1) + + +class B_LongTransactionTests(QmfTestBase010): + + def test_txEnq10_route_defaultExch(self): + self._do_test(self._get_name(), enq_txn_size=10, msg_count = 103) + + def test_txEnq10_queueRoute_defaultExch(self): + self._do_test(self._get_name(), queue_route_type_flag=True, enq_txn_size=10, msg_count = 103) + + + + + def test_txEnq01_route_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", enq_txn_size=1) + + def test_txEnq01_queueRoute_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_route_type_flag=True, enq_txn_size=1) + + def test_txEnq10_route_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", enq_txn_size=10, msg_count = 103) + + def test_txEnq10_queueRoute_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_route_type_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq01_txDeq01_route_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_queueRoute_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1) + + + def test_txEnq01_route_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", enq_txn_size=1) + + def test_txEnq01_queueRoute_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_route_type_flag=True, enq_txn_size=1) + + def test_txEnq10_route_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", enq_txn_size=10, msg_count = 103) + + def test_txEnq10_queueRoute_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_route_type_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq01_txDeq01_route_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_queueRoute_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1) + + + def test_txEnq01_route_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", enq_txn_size=1) + + def test_txEnq01_queueRoute_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_route_type_flag=True, enq_txn_size=1) + + def test_txEnq10_route_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", enq_txn_size=10, msg_count = 103) + + def test_txEnq10_queueRoute_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_route_type_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq01_txDeq01_route_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_queueRoute_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1) + + +class E_ShortPersistenceTests(QmfTestBase010): + + def test_route_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True) + + def test_route_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True) + + def test_queueRoute_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, queue_route_type_flag=True) + + def test_queueRoute_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True) + + +class E_LongPersistenceTests(QmfTestBase010): + + + def test_route_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True) + + def test_route_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True) + + def test_queueRoute_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, queue_route_type_flag=True) + + def test_queueRoute_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True) + + + def test_route_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True) + + def test_route_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True) + + def test_queueRoute_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, queue_route_type_flag=True) + + def test_queueRoute_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True) + + + def test_route_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True) + + def test_route_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True) + + def test_queueRoute_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, queue_route_type_flag=True) + + def test_queueRoute_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True) + + +class F_ShortPersistenceTransactionTests(QmfTestBase010): + + def test_txEnq01_route_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, enq_txn_size=1) + + def test_txEnq01_route_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1) + + def test_txEnq01_queueRoute_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1) + + def test_txEnq01_queueRoute_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1) + + def test_txEnq01_txDeq01_route_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_route_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_queueRoute_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_queueRoute_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1) + + +class F_LongPersistenceTransactionTests(QmfTestBase010): + + def test_txEnq10_route_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq10_route_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq10_queueRoute_durQueue_defaultExch(self): + self._do_test(self._get_name(), queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq10_queueRoute_durMsg_durQueue_defaultExch(self): + self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103) + + + + + def test_txEnq01_route_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, enq_txn_size=1) + + def test_txEnq01_route_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1) + + def test_txEnq01_queueRoute_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1) + + def test_txEnq01_queueRoute_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1) + + def test_txEnq10_route_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq10_route_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq10_queueRoute_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq10_queueRoute_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq01_txDeq01_route_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_route_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_queueRoute_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_queueRoute_durMsg_durQueue_directExch(self): + self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1) + + + def test_txEnq01_route_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, enq_txn_size=1) + + def test_txEnq01_route_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1) + + def test_txEnq01_queueRoute_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1) + + def test_txEnq01_queueRoute_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1) + + def test_txEnq10_route_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq10_route_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq10_queueRoute_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq10_queueRoute_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq01_txDeq01_route_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_route_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_queueRoute_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_queueRoute_durMsg_durQueue_fanoutExch(self): + self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1) + + + def test_txEnq01_route_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, enq_txn_size=1) + + def test_txEnq01_route_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1) + + def test_txEnq01_queueRoute_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1) + + def test_txEnq01_queueRoute_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1) + + def test_txEnq10_route_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq10_route_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq10_queueRoute_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq10_queueRoute_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103) + + def test_txEnq01_txDeq01_route_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_route_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_queueRoute_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1) + + def test_txEnq01_txDeq01_queueRoute_durMsg_durQueue_topicExch(self): + self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1) + + diff --git a/qpid/cpp/src/tests/find_prog.ps1 b/qpid/cpp/src/tests/find_prog.ps1 new file mode 100644 index 0000000000..5c482debbf --- /dev/null +++ b/qpid/cpp/src/tests/find_prog.ps1 @@ -0,0 +1,36 @@ +#
+# 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.
+#
+
+# Locate the subdirectory where the specified program resides; the program
+# must have a directory and a file name, even if the directory is .
+param(
+ [string] $prog # program to look for somewhere below cwd
+)
+
+$dir = Split-Path $prog
+$exe = Split-Path $prog -leaf
+$sub = ""
+$subs = "Debug","Release","MinSizeRel","RelWithDebInfo"
+foreach ($try in $subs) {
+ $prog = "$dir\$try\$exe"
+ if (Test-Path $prog) {
+ $sub = $try
+ break
+ }
+}
diff --git a/qpid/cpp/src/tests/ha_test.py b/qpid/cpp/src/tests/ha_test.py new file mode 100755 index 0000000000..82ca808cb1 --- /dev/null +++ b/qpid/cpp/src/tests/ha_test.py @@ -0,0 +1,403 @@ +#!/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. +# + +import os, signal, sys, time, imp, re, subprocess, glob, random, logging, shutil, math, unittest, random +import traceback +from brokertest import * +from threading import Thread, Lock, Condition +from logging import getLogger, WARN, ERROR, DEBUG, INFO +from qpidtoollibs import BrokerAgent +from qpid.harness import Skipped + +log = getLogger(__name__) + +class LogLevel: + """ + Temporarily change the log settings on the root logger. + Used to suppress expected WARN messages from the python client. + """ + def __init__(self, level): + self.save_level = getLogger().getEffectiveLevel() + getLogger().setLevel(level) + + def restore(self): + getLogger().setLevel(self.save_level) + +class QmfAgent(object): + """Access to a QMF broker agent.""" + def __init__(self, address, **kwargs): + self._connection = qm.Connection.establish( + address, client_properties={"qpid.ha-admin":1}, **kwargs) + self._agent = BrokerAgent(self._connection) + + def queues(self): + return [q.values['name'] for q in self._agent.getAllQueues()] + + def repsub_queue(self, sub): + """If QMF subscription sub is a replicating subscription return + the name of the replicated queue, else return None""" + session = self.getSession(sub.sessionRef) + if not session: return None + m = re.search("qpid.ha-q:(.*)\.", session.name) + return m and m.group(1) + + def repsub_queues(self): + """Return queue names for all replicating subscriptions""" + return filter(None, [self.repsub_queue(s) for s in self.getAllSubscriptions()]) + + def tx_queues(self): + """Return names of all tx-queues""" + return [q for q in self.queues() if q.startswith("qpid.ha-tx")] + + def __getattr__(self, name): + a = getattr(self._agent, name) + return a + +class Credentials(object): + """SASL credentials: username, password, and mechanism""" + def __init__(self, username, password, mechanism): + (self.username, self.password, self.mechanism) = (username, password, mechanism) + + def __str__(self): return "Credentials%s"%(self.tuple(),) + + def tuple(self): return (self.username, self.password, self.mechanism) + + def add_user(self, url): return "%s/%s@%s"%(self.username, self.password, url) + +class HaPort: + """Many HA tests need to allocate a broker port dynamically and then kill + and restart a broker on that same port multiple times. qpidd --port=0 only + ensures the port for the initial broker process, subsequent brokers re-using + the same port may fail with "address already in use". + + HaPort binds and listens to the port and returns a file descriptor to pass + to qpidd --socket-fd. It holds on to the port untill the end of the test so + the broker can restart multiple times. + """ + + def __init__(self, test, port=0): + """Bind and listen to port. port=0 allocates a port dynamically. + self.port is the allocated port, self.fileno is the file descriptor for + qpid --socket-fd.""" + + self.test = test + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket.bind(("", port)) + self.socket.listen(5) + self.port = self.socket.getsockname()[1] + self.fileno = self.socket.fileno() + self.stopped = False + test.teardown_add(self) # Stop during test.tearDown + + def teardown(self): # Called in tearDown + if not self.stopped: + self.stopped = True + self.socket.shutdown(socket.SHUT_RDWR) + self.socket.close() + + def __str__(self): return "HaPort<port:%s, fileno:%s>"%(self.port, self.fileno) + + +class HaBroker(Broker): + """Start a broker with HA enabled + @param client_cred: (user, password, mechanism) for admin clients started by the HaBroker. + """ + + heartbeat=5 + + def __init__(self, test, ha_port=None, args=[], brokers_url=None, ha_cluster=True, + ha_replicate="all", client_credentials=None, **kwargs): + assert BrokerTest.ha_lib, "Cannot locate HA plug-in" + ha_port = ha_port or HaPort(test) + args = copy(args) + args += ["--load-module", BrokerTest.ha_lib, + # Non-standard settings for faster tests. + "--link-maintenance-interval=0.1", + "--ha-cluster=%s"%ha_cluster] + # Add default --log-enable arguments unless args already has --log arguments. + if not env_has_log_config() and not [l for l in args if l.startswith("--log")]: + args += ["--log-enable=info+", "--log-enable=debug+:ha::"] + if not [h for h in args if h.startswith("--link-heartbeat-interval")]: + args += ["--link-heartbeat-interval=%s"%(HaBroker.heartbeat)] + + if ha_replicate is not None: + args += [ "--ha-replicate=%s"%ha_replicate ] + if brokers_url: args += [ "--ha-brokers-url", brokers_url ] + # Set up default ACL + acl=os.path.join(os.getcwd(), "unrestricted.acl") + if not os.path.exists(acl): + aclf=file(acl,"w") + aclf.write(""" +acl allow all all + """) + aclf.close() + if not "--acl-file" in args: + args += [ "--acl-file", acl, ] + args += ["--socket-fd=%s"%ha_port.fileno, "--listen-disable=tcp"] + self._agent = None + self.client_credentials = client_credentials + self.ha_port = ha_port + Broker.__init__(self, test, args, port=ha_port.port, **kwargs) + + # Do some static setup to locate the qpid-config and qpid-ha tools. + @property + def qpid_ha_script(self): + if not hasattr(self, "_qpid_ha_script"): + qpid_ha_exec = os.getenv("QPID_HA_EXEC") + if not qpid_ha_exec or not os.path.isfile(qpid_ha_exec): + raise Skipped("qpid-ha not available") + self._qpid_ha_script = import_script(qpid_ha_exec) + return self._qpid_ha_script + + def __repr__(self): return "<HaBroker:%s:%d>"%(self.log, self.port()) + + def qpid_ha(self, args): + if not self.qpid_ha_script: + raise Skipped("qpid-ha not available") + try: + cred = self.client_credentials + url = self.host_port() + if cred: + url =cred.add_user(url) + args = args + ["--sasl-mechanism", cred.mechanism] + self.qpid_ha_script.main_except(["", "-b", url]+args) + except Exception, e: + raise Exception("Error in qpid_ha -b %s %s: %s"%(url, args,e)) + + def promote(self): self.ready(); self.qpid_ha(["promote", "--cluster-manager"]) + def replicate(self, from_broker, queue): self.qpid_ha(["replicate", from_broker, queue]) + @property + def agent(self): + if not self._agent: + cred = self.client_credentials + if cred: + self._agent = QmfAgent(cred.add_user(self.host_port()), sasl_mechanisms=cred.mechanism) + else: + self._agent = QmfAgent(self.host_port()) + return self._agent + + def qmf(self): + hb = self.agent.getHaBroker() + hb.update() + return hb + + def ha_status(self): return self.qmf().status + + def wait_status(self, status, timeout=10): + + def try_get_status(): + self._status = "<unknown>" + try: + self._status = self.ha_status() + except qm.ConnectionError, e: + # Record the error but don't raise, the broker may not be up yet. + self._status = "%s: %s" % (type(e).__name__, e) + return self._status == status; + assert retry(try_get_status, timeout=timeout), "%s expected=%r, actual=%r"%( + self, status, self._status) + + def wait_queue(self, queue, timeout=10, msg="wait_queue"): + """ Wait for queue to be visible via QMF""" + agent = self.agent + assert retry(lambda: agent.getQueue(queue) is not None, timeout=timeout), \ + "%s queue %s not present" % (msg, queue) + + def wait_no_queue(self, queue, timeout=10, msg="wait_no_queue"): + """ Wait for queue to be invisible via QMF""" + agent = self.agent + assert retry(lambda: agent.getQueue(queue) is None, timeout=timeout), "%s: queue %s still present"%(msg,queue) + + def qpid_config(self, args): + qpid_config_exec = os.getenv("QPID_CONFIG_EXEC") + if not qpid_config_exec or not os.path.isfile(qpid_config_exec): + raise Skipped("qpid-config not available") + assert subprocess.call( + [qpid_config_exec, "--broker", self.host_port()]+args, stdout=1, stderr=subprocess.STDOUT + ) == 0, "qpid-config failed" + + def config_replicate(self, from_broker, queue): + self.qpid_config(["add", "queue", "--start-replica", from_broker, queue]) + + def config_declare(self, queue, replication): + self.qpid_config(["add", "queue", queue, "--replicate", replication]) + + def connect_admin(self, **kwargs): + cred = self.client_credentials + if cred: + return Broker.connect( + self, client_properties={"qpid.ha-admin":1}, + username=cred.username, password=cred.password, sasl_mechanisms=cred.mechanism, + **kwargs) + else: + return Broker.connect(self, client_properties={"qpid.ha-admin":1}, **kwargs) + + def wait_address(self, address): + """Wait for address to become valid on the broker.""" + c = self.connect_admin() + try: wait_address(c, address) + finally: c.close() + + wait_backup = wait_address + + def browse(self, queue, timeout=0, transform=lambda m: m.content): + c = self.connect_admin() + try: + return browse(c.session(), queue, timeout, transform) + finally: c.close() + + def assert_browse_backup(self, queue, expected, **kwargs): + """Combines wait_backup and assert_browse_retry.""" + c = self.connect_admin() + try: + wait_address(c, queue) + assert_browse_retry(c.session(), queue, expected, **kwargs) + finally: c.close() + + assert_browse = assert_browse_backup + + def assert_connect_fail(self): + try: + self.connect() + self.test.fail("Expected qm.ConnectionError") + except qm.ConnectionError: pass + + def try_connect(self): + try: return self.connect() + except qm.ConnectionError: return None + + def ready(self, *args, **kwargs): + if not 'client_properties' in kwargs: kwargs['client_properties'] = {} + kwargs['client_properties']['qpid.ha-admin'] = True + return Broker.ready(self, *args, **kwargs) + + def kill(self, final=True): + if final: self.ha_port.teardown() + self._agent = None + return Broker.kill(self) + + +class HaCluster(object): + _cluster_count = 0 + + def __init__(self, test, n, promote=True, wait=True, args=[], s_args=[], **kwargs): + """Start a cluster of n brokers. + + @test: The test being run + @n: start n brokers + @promote: promote self[0] to primary + @wait: wait for primary active and backups ready. Ignored if promote=False + @args: args for all brokers in the cluster. + @s_args: args for specific brokers: s_args[i] for broker i. + """ + self.test = test + self.args = copy(args) + self.s_args = copy(s_args) + self.kwargs = kwargs + self._ports = [HaPort(test) for i in xrange(n)] + self._set_url() + self._brokers = [] + self.id = HaCluster._cluster_count + self.broker_id = 0 + HaCluster._cluster_count += 1 + for i in xrange(n): self.start() + if promote: + self[0].promote() + if wait: + self[0].wait_status("active") + for b in self[1:]: b.wait_status("ready") + + def next_name(self): + name="cluster%s-%s"%(self.id, self.broker_id) + self.broker_id += 1 + return name + + def _ha_broker(self, i, name): + args = self.args + if i < len(self.s_args): args += self.s_args[i] + ha_port = self._ports[i] + b = HaBroker(ha_port.test, ha_port, brokers_url=self.url, name=name, + args=args, **self.kwargs) + b.ready(timeout=10) + return b + + def start(self): + """Start a new broker in the cluster""" + i = len(self) + assert i <= len(self._ports) + if i == len(self._ports): # Adding new broker after cluster init + self._ports.append(HaPort(self.test)) + self._set_url() + b = self._ha_broker(i, self.next_name()) + self._brokers.append(b) + return b + + def _set_url(self): + self.url = ",".join("127.0.0.1:%s"%(p.port) for p in self._ports) + + def connect(self, i, **kwargs): + """Connect with reconnect_urls""" + c = self[i].connect(reconnect=True, reconnect_urls=self.url.split(","), **kwargs) + self.test.teardown_add(c) # Clean up + return c + + def kill(self, i, promote_next=True, final=True): + """Kill broker i, promote broker i+1""" + self[i].kill(final=final) + if promote_next: self[(i+1) % len(self)].promote() + + def restart(self, i): + """Start a broker with the same port, name and data directory. It will get + a separate log file: foo.n.log""" + if self._ports[i].stopped: raise Exception("Restart after final kill: %s"%(self)) + b = self._brokers[i] + self._brokers[i] = self._ha_broker(i, b.name) + self._brokers[i].ready() + + def bounce(self, i, promote_next=True): + """Stop and restart a broker in a cluster.""" + if (len(self) == 1): + self.kill(i, promote_next=False, final=False) + self.restart(i) + self[i].ready() + if promote_next: self[i].promote() + else: + self.kill(i, promote_next, final=False) + self.restart(i) + + # Behave like a list of brokers. + def __len__(self): return len(self._brokers) + def __getitem__(self,index): return self._brokers[index] + def __iter__(self): return self._brokers.__iter__() + + +def wait_address(connection, address): + """Wait for an address to become valid.""" + assert retry(lambda: valid_address(connection, address)), "Timed out waiting for address %s"%(address) + +def valid_address(connection, address): + """Test if an address is valid""" + try: + s = connection.session().receiver(address) + s.session.close() + return True + except qm.NotFound: + return False + + diff --git a/qpid/cpp/src/tests/ha_test_max_queues.cpp b/qpid/cpp/src/tests/ha_test_max_queues.cpp new file mode 100644 index 0000000000..fcce4c3151 --- /dev/null +++ b/qpid/cpp/src/tests/ha_test_max_queues.cpp @@ -0,0 +1,67 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include <qpid/client/Connection.h> +#include <qpid/client/Session.h> +#include <qpid/client/AsyncSession.h> +#include <qpid/Url.h> +#include <qpid/framing/reply_exceptions.h> +#include <sstream> + +using namespace qpid::client; +using namespace std; + +int main(int argc, char** argv) { + if (argc != 2) { + cerr << "Expecing URL of broker as argument" << endl; + exit(1); + } + try { + // We need to create a large number of queues quickly, so we + // use the old API for it's asynchronous commands. + // The qpid::messaging API does not allow async queue creation. + // + Connection c; + c.open(qpid::Url(argv[1])); + AsyncSession s = async(c.newSession()); + // Generate too many queues, make sure we get an exception. + for (uint64_t i = 0; i < 100000; ++i) { + ostringstream os; + os << "q" << i; + string q = os.str(); + s.queueDeclare(q, arg::sync=false); + if (i && i % 1000 == 0) { + s.sync(); // Check for exceptions. + cout << "Declared " << q << endl; + } + } + cout << "Expected resource-limit-exceeded exception" << endl; + return 1; + } + catch (const qpid::framing::ResourceLimitExceededException& e) { + cout << "Resource limit exceeded: " << e.what() << endl; + return 0; + } + catch (const std::exception& e) { + cout << "Error: " << e.what() << endl; + return 1; + } +} diff --git a/qpid/cpp/src/tests/ha_tests.py b/qpid/cpp/src/tests/ha_tests.py new file mode 100755 index 0000000000..2ee2e291e2 --- /dev/null +++ b/qpid/cpp/src/tests/ha_tests.py @@ -0,0 +1,1634 @@ +#!/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. +# + +import os, signal, sys, time, imp, re, subprocess, glob, random, logging, shutil, math, unittest +import traceback +from qpid.datatypes import uuid4, UUID +from brokertest import * +from ha_test import * +from threading import Thread, Lock, Condition +from logging import getLogger, WARN, ERROR, DEBUG, INFO +from qpidtoollibs import BrokerAgent, EventHelper + +log = getLogger(__name__) + +class HaBrokerTest(BrokerTest): + """Base class for HA broker tests""" + +class ReplicationTests(HaBrokerTest): + """Correctness tests for HA replication.""" + + def test_replication(self): + """Test basic replication of configuration and messages before and + after backup has connected""" + + def setup(prefix, primary): + """Create config, send messages on the primary p""" + a = primary.agent + + def queue(name, replicate): + a.addQueue(name, options={'qpid.replicate':replicate}) + return name + + def exchange(name, replicate, bindq, key): + a.addExchange("fanout", name, options={'qpid.replicate':replicate}) + a.bind(name, bindq, key) + return name + + # Test replication of messages + p = primary.connect().session() + s = p.sender(queue(prefix+"q1", "all")) + for m in ["a", "b", "1"]: s.send(qm.Message(m)) + # Test replication of dequeue + self.assertEqual(p.receiver(prefix+"q1").fetch(timeout=0).content, "a") + p.acknowledge() + + p.sender(queue(prefix+"q2", "configuration")).send(qm.Message("2")) + p.sender(queue(prefix+"q3", "none")).send(qm.Message("3")) + p.sender(exchange(prefix+"e1", "all", prefix+"q1", "key1")).send(qm.Message("4")) + p.sender(exchange(prefix+"e2", "configuration", prefix+"q2", "key2")).send(qm.Message("5")) + # Test unbind + p.sender(queue(prefix+"q4", "all")).send(qm.Message("6")) + s3 = p.sender(exchange(prefix+"e4", "all", prefix+"q4", "key4")) + s3.send(qm.Message("7")) + a.unbind(prefix+"e4", prefix+"q4", "key4") + p.sender(prefix+"e4").send(qm.Message("drop1")) # Should be dropped + + # Test replication of deletes + queue(prefix+"dq", "all") + exchange(prefix+"de", "all", prefix+"dq", "") + a.delQueue(prefix+"dq") + a.delExchange(prefix+"de") + + # Need a marker so we can wait till sync is done. + queue(prefix+"x", "configuration") + + def verify(b, prefix, p): + """Verify setup was replicated to backup b""" + # Wait for configuration to replicate. + wait_address(b.connection, prefix+"x"); + self.assert_browse_retry(b, prefix+"q1", ["b", "1", "4"]) + + self.assertEqual(p.receiver(prefix+"q1").fetch(timeout=0).content, "b") + p.acknowledge() + self.assert_browse_retry(b, prefix+"q1", ["1", "4"]) + + self.assert_browse_retry(b, prefix+"q2", []) # configuration only + assert not valid_address(b.connection, prefix+"q3") + + # Verify exchange with replicate=all + b.sender(prefix+"e1/key1").send(qm.Message(prefix+"e1")) + self.assert_browse_retry(b, prefix+"q1", ["1", "4", prefix+"e1"]) + + # Verify exchange with replicate=configuration + b.sender(prefix+"e2/key2").send(qm.Message(prefix+"e2")) + self.assert_browse_retry(b, prefix+"q2", [prefix+"e2"]) + + b.sender(prefix+"e4/key4").send(qm.Message("drop2")) # Verify unbind. + self.assert_browse_retry(b, prefix+"q4", ["6","7"]) + + # Verify deletes + assert not valid_address(b.connection, prefix+"dq") + assert not valid_address(b.connection, prefix+"de") + + l = LogLevel(ERROR) # Hide expected WARNING log messages from failover. + try: + cluster = HaCluster(self, 2) + primary = cluster[0] + backup = cluster[1] + + # Send messages before re-starting the backup, test catch-up replication. + cluster.kill(1, promote_next=False, final=False) + setup("1", primary) + cluster.restart(1) + + # Send messages after re-starting the backup, to test steady-state replication. + setup("2", primary) + + p = primary.connect().session() + + # Verify the data on the backup + b = backup.connect_admin().session() + verify(b, "1", p) + verify(b, "2", p) + # Test a series of messages, enqueue all then dequeue all. + primary.agent.addQueue("foo") + s = p.sender("foo") + wait_address(b.connection, "foo") + msgs = [str(i) for i in range(10)] + for m in msgs: s.send(qm.Message(m)) + self.assert_browse_retry(p, "foo", msgs) + self.assert_browse_retry(b, "foo", msgs) + r = p.receiver("foo") + for m in msgs: self.assertEqual(m, r.fetch(timeout=0).content) + p.acknowledge() + self.assert_browse_retry(p, "foo", []) + self.assert_browse_retry(b, "foo", []) + + # Another series, this time verify each dequeue individually. + for m in msgs: s.send(qm.Message(m)) + self.assert_browse_retry(p, "foo", msgs) + self.assert_browse_retry(b, "foo", msgs) + for i in range(len(msgs)): + self.assertEqual(msgs[i], r.fetch(timeout=0).content) + p.acknowledge() + self.assert_browse_retry(p, "foo", msgs[i+1:]) + self.assert_browse_retry(b, "foo", msgs[i+1:]) + finally: l.restore() + + def test_sync(self): + primary = HaBroker(self, name="primary") + primary.promote() + p = primary.connect().session() + s = p.sender("q;{create:always}") + for m in [str(i) for i in range(0,10)]: s.send(m) + s.sync() + backup1 = HaBroker(self, name="backup1", brokers_url=primary.host_port()) + for m in [str(i) for i in range(10,20)]: s.send(m) + s.sync() + backup2 = HaBroker(self, name="backup2", brokers_url=primary.host_port()) + for m in [str(i) for i in range(20,30)]: s.send(m) + s.sync() + + msgs = [str(i) for i in range(30)] + b1 = backup1.connect_admin().session() + backup1.assert_browse_backup("q", msgs) + backup2.assert_browse_backup("q", msgs) + + def test_send_receive(self): + """Verify sequence numbers of messages sent by qpid-send""" + l = LogLevel(ERROR) # Hide expected WARNING log messages from failover. + try: + brokers = HaCluster(self, 3) + sender = self.popen( + ["qpid-send", + "--broker", brokers[0].host_port(), + "--address", "q;{create:always}", + "--messages=1000", + "--content-string=x", + "--connection-options={%s}"%self.protocol_option() + ]) + receiver = self.popen( + ["qpid-receive", + "--broker", brokers[0].host_port(), + "--address", "q;{create:always}", + "--messages=990", + "--timeout=10", + "--connection-options={%s}"%self.protocol_option() + ]) + self.assertEqual(sender.wait(), 0) + self.assertEqual(receiver.wait(), 0) + expect = [long(i) for i in range(991, 1001)] + sn = lambda m: m.properties["sn"] + brokers[1].assert_browse_backup("q", expect, transform=sn) + brokers[2].assert_browse_backup("q", expect, transform=sn) + finally: l.restore() + + def test_failover_python(self): + """Verify that backups rejects connections and that fail-over works in python client""" + l = LogLevel(ERROR) # Hide expected WARNING log messages from failover. + try: + primary = HaBroker(self, name="primary") + primary.promote() + backup = HaBroker(self, name="backup", brokers_url=primary.host_port()) + # Check that backup rejects normal connections + try: + backup.connect().session() + self.fail("Expected connection to backup to fail") + except qm.ConnectionError: pass + # Check that admin connections are allowed to backup. + backup.connect_admin().close() + + # Test discovery: should connect to primary after reject by backup + c = backup.connect(reconnect_urls=[primary.host_port(), backup.host_port()], + reconnect=True) + s = c.session() + sender = s.sender("q;{create:always}") + sender.send("foo", sync=True) + s.sync() + primary.kill() + assert retry(lambda: not is_running(primary.pid)) + backup.promote() + sender.send("bar") + self.assert_browse_retry(s, "q", ["foo", "bar"]) + c.close() + finally: l.restore() + + + def test_heartbeat_python(self): + """Verify that a python client with a heartbeat specified disconnects + from a stalled broker and does not hang indefinitely.""" + + broker = Broker(self) + broker_addr = broker.host_port() + + # Case 1: Connect before stalling the broker, use the connection after stalling. + c = qm.Connection(broker_addr, heartbeat=1) + c.open() + os.kill(broker.pid, signal.SIGSTOP) # Stall the broker + + def make_sender(): c.session().sender("foo") + self.assertRaises(qm.ConnectionError, make_sender) + + # Case 2: Connect to a stalled broker + c = qm.Connection(broker_addr, heartbeat=1) + self.assertRaises(qm.ConnectionError, c.open) + + # Case 3: Re-connect to a stalled broker. + broker2 = Broker(self) + c = qm.Connection(broker2.host_port(), heartbeat=1, reconnect_limit=1, + reconnect=True, reconnect_urls=[broker_addr], + reconnect_log=False) # Hide expected warnings + c.open() + broker2.kill() # Cause re-connection to broker + self.assertRaises(qm.ConnectionError, make_sender) + + def test_failover_cpp(self): + """Verify that failover works in the C++ client.""" + cluster = HaCluster(self, 2) + cluster[0].connect().session().sender("q;{create:always}") + cluster[1].wait_backup("q") + # FIXME aconway 2014-02-21: using 0-10, there is a failover problem with 1.0 + sender = NumberedSender(cluster[0], url=cluster.url, queue="q", + connection_options="reconnect:true,protocol:'amqp0-10'") + receiver = NumberedReceiver(cluster[0], url=cluster.url, queue="q", + connection_options="reconnect:true,protocol:'amqp0-10'") + receiver.start() + sender.start() + assert retry(lambda: receiver.received > 10) # Wait for some messages to get thru + cluster.kill(0) + n = receiver.received + assert retry(lambda: receiver.received > n + 10) # Verify we are still going + sender.stop() + receiver.stop() + + def test_backup_failover(self): + """Verify that a backup broker fails over and recovers queue state""" + brokers = HaCluster(self, 3) + brokers[0].connect().session().sender("q;{create:always}").send("a") + brokers.kill(0) + brokers[1].connect().session().sender("q").send("b") + brokers[2].assert_browse_backup("q", ["a","b"]) + s = brokers[1].connect().session() + self.assertEqual("a", s.receiver("q").fetch().content) + s.acknowledge() + brokers[2].assert_browse_backup("q", ["b"]) + + def test_empty_backup_failover(self): + """Verify that a new primary becomes active with no queues. + Regression test for QPID-5430""" + brokers = HaCluster(self, 3) + brokers.kill(0) + brokers[1].wait_status("active") + + def test_qpid_config_replication(self): + """Set up replication via qpid-config""" + brokers = HaCluster(self,2) + brokers[0].config_declare("q","all") + brokers[0].connect().session().sender("q").send("foo") + brokers[1].assert_browse_backup("q", ["foo"]) + + def test_standalone_queue_replica(self): + """Test replication of individual queues outside of cluster mode""" + primary = HaBroker(self, name="primary", ha_cluster=False, + args=["--ha-queue-replication=yes"]); + pc = primary.connect() + ps = pc.session().sender("q;{create:always}") + pr = pc.session().receiver("q;{create:always}") + backup = HaBroker(self, name="backup", ha_cluster=False, + args=["--ha-queue-replication=yes"]) + bs = backup.connect().session() + br = bs.receiver("q;{create:always}") + + def srange(*args): return [str(i) for i in xrange(*args)] + + for m in srange(3): ps.send(m) + # Set up replication with qpid-ha + backup.replicate(primary.host_port(), "q") + backup.assert_browse_backup("q", srange(3)) + for m in srange(3,6): ps.send(str(m)) + backup.assert_browse_backup("q", srange(6)) + self.assertEqual("0", pr.fetch().content) + pr.session.acknowledge() + backup.assert_browse_backup("q", srange(1,6)) + + # Set up replication with qpid-config + ps2 = pc.session().sender("q2;{create:always}") + backup.config_replicate(primary.host_port(), "q2"); + ps2.send("x") + backup.assert_browse_backup("q2", ["x"]) + + + def test_standalone_queue_replica_failover(self): + """Test individual queue replication from a cluster to a standalone + backup broker, verify it fails over.""" + l = LogLevel(ERROR) # Hide expected WARNING log messages from failover. + try: + cluster = HaCluster(self, 2) + primary = cluster[0] + pc = cluster.connect(0) + ps = pc.session().sender("q;{create:always}") + pr = pc.session().receiver("q;{create:always}") + backup = HaBroker(self, name="backup", ha_cluster=False, + args=["--ha-queue-replication=yes"]) + br = backup.connect().session().receiver("q;{create:always}") + backup.replicate(cluster.url, "q") + ps.send("a") + ps.sync() + backup.assert_browse_backup("q", ["a"]) + cluster.bounce(0) + backup.assert_browse_backup("q", ["a"]) + ps.send("b") + backup.assert_browse_backup("q", ["a", "b"]) + cluster[0].wait_status("ready") + cluster.bounce(1) + # FIXME aconway 2014-02-20: pr does not fail over with 1.0/swig + if qm == qpid_messaging: + print "WARNING: Skipping SWIG client failover bug" + return + self.assertEqual("a", pr.fetch().content) + pr.session.acknowledge() + backup.assert_browse_backup("q", ["b"]) + pc.close() + br.close() + finally: l.restore() + + def test_lvq(self): + """Verify that we replicate to an LVQ correctly""" + cluster = HaCluster(self, 2) + s = cluster[0].connect().session().sender("lvq; {create:always, node:{x-declare:{arguments:{'qpid.last_value_queue_key':lvq-key}}}}") + + def send(key,value,expect): + s.send(qm.Message(content=value,properties={"lvq-key":key})) + cluster[1].assert_browse_backup("lvq", expect) + + send("a", "a-1", ["a-1"]) + send("b", "b-1", ["a-1", "b-1"]) + send("a", "a-2", ["b-1", "a-2"]) + send("a", "a-3", ["b-1", "a-3"]) + send("c", "c-1", ["b-1", "a-3", "c-1"]) + send("c", "c-2", ["b-1", "a-3", "c-2"]) + send("b", "b-2", ["a-3", "c-2", "b-2"]) + send("c", "c-3", ["a-3", "b-2", "c-3"]) + send("d", "d-1", ["a-3", "b-2", "c-3", "d-1"]) + + def test_ring(self): + """Test replication with the ring queue policy""" + """Verify that we replicate to an LVQ correctly""" + cluster = HaCluster(self, 2) + s = cluster[0].connect().session().sender("q; {create:always, node:{x-declare:{arguments:{'qpid.policy_type':ring, 'qpid.max_count':5}}}}") + for i in range(10): s.send(qm.Message(str(i))) + cluster[1].assert_browse_backup("q", [str(i) for i in range(5,10)]) + + def test_reject(self): + """Test replication with the reject queue policy""" + cluster = HaCluster(self, 2) + primary, backup = cluster + s = primary.connect().session().sender("q; {create:always, node:{x-declare:{arguments:{'qpid.policy_type':reject, 'qpid.max_count':5}}}}") + try: + for i in range(10): s.send(qm.Message(str(i)), sync=False) + except qm.LinkError: pass + backup.assert_browse_backup("q", [str(i) for i in range(0,5)]) + try: s.session.connection.close() + except: pass # Expect exception from broken session + + def test_priority(self): + """Verify priority queues replicate correctly""" + cluster = HaCluster(self, 2) + session = cluster[0].connect().session() + s = session.sender("priority-queue; {create:always, node:{x-declare:{arguments:{'qpid.priorities':10}}}}") + priorities = [8,9,5,1,2,2,3,4,9,7,8,9,9,2] + for p in priorities: s.send(qm.Message(priority=p)) + # Can't use browse_backup as browser sees messages in delivery order not priority. + cluster[1].wait_backup("priority-queue") + r = cluster[1].connect_admin().session().receiver("priority-queue") + received = [r.fetch().priority for i in priorities] + self.assertEqual(sorted(priorities, reverse=True), received) + + def test_priority_fairshare(self): + """Verify priority queues replicate correctly""" + cluster = HaCluster(self, 2) + primary, backup = cluster + session = primary.connect().session() + levels = 8 + priorities = [4,5,3,7,8,8,2,8,2,8,8,16,6,6,6,6,6,6,8,3,5,8,3,5,5,3,3,8,8,3,7,3,7,7,7,8,8,8,2,3] + limits={7:0,6:4,5:3,4:2,3:2,2:2,1:2} + limit_policy = ",".join(["'qpid.fairshare':5"] + ["'qpid.fairshare-%s':%s"%(i[0],i[1]) for i in limits.iteritems()]) + s = session.sender("priority-queue; {create:always, node:{x-declare:{arguments:{'qpid.priorities':%s, %s}}}}"%(levels,limit_policy)) + messages = [qm.Message(content=str(uuid4()), priority = p) for p in priorities] + for m in messages: s.send(m) + backup.wait_backup(s.target) + r = backup.connect_admin().session().receiver("priority-queue") + received = [r.fetch().content for i in priorities] + sort = sorted(messages, key=lambda m: priority_level(m.priority, levels), reverse=True) + fair = [m.content for m in fairshare(sort, lambda l: limits.get(l,0), levels)] + self.assertEqual(received, fair) + + def test_priority_ring(self): + cluster = HaCluster(self, 2) + primary, backup = cluster + s = primary.connect().session().sender("q; {create:always, node:{x-declare:{arguments:{'qpid.policy_type':ring, 'qpid.max_count':5, 'qpid.priorities':10}}}}") + priorities = [8,9,5,1,2,2,3,4,9,7,8,9,9,2] + for p in priorities: s.send(qm.Message(priority=p)) + expect = sorted(priorities,reverse=True)[0:5] + primary.assert_browse("q", expect, transform=lambda m: m.priority) + backup.assert_browse_backup("q", expect, transform=lambda m: m.priority) + + def test_backup_acquired(self): + """Verify that acquired messages are backed up, for all queue types.""" + class Test: + def __init__(self, queue, arguments, expect): + self.queue = queue + self.address = "%s;{create:always,node:{x-declare:{arguments:{%s}}}}"%( + self.queue, ",".join(arguments)) + self.expect = [str(i) for i in expect] + + def send(self, connection): + """Send messages, then acquire one but don't acknowledge""" + s = connection.session() + for m in range(10): s.sender(self.address).send(str(m)) + s.receiver(self.address).fetch() + + def verify(self, brokertest, backup): + backup.assert_browse_backup(self.queue, self.expect, msg=self.queue) + + tests = [ + Test("plain",[],range(10)), + Test("ring", ["'qpid.policy_type':ring", "'qpid.max_count':5"], range(5,10)), + Test("priority",["'qpid.priorities':10"], range(10)), + Test("fairshare", ["'qpid.priorities':10,'qpid.fairshare':5"], range(10)), + Test("lvq", ["'qpid.last_value_queue_key':lvq-key"], [9]) + ] + + cluster = HaCluster(self, 3) + cluster.kill(2, final=False) # restart after messages are sent to test catch-up + + c = cluster[0].connect() + for t in tests: t.send(c) # Send messages, leave one unacknowledged. + + cluster.restart(2) + cluster[2].wait_status("ready") + + # Verify acquired message was replicated + for t in tests: t.verify(self, cluster[1]) + for t in tests: t.verify(self, cluster[2]) + + def test_replicate_default(self): + """Make sure we don't replicate if ha-replicate is unspecified or none""" + cluster1 = HaCluster(self, 2, ha_replicate=None) + cluster1[1].wait_status("ready") + c1 = cluster1[0].connect().session().sender("q;{create:always}") + cluster2 = HaCluster(self, 2, ha_replicate="none") + cluster2[1].wait_status("ready") + cluster2[0].connect().session().sender("q;{create:always}") + time.sleep(.1) # Give replication a chance. + # Expect queues not to be found + self.assertRaises(qm.NotFound, cluster1[1].connect_admin().session().receiver, "q") + self.assertRaises(qm.NotFound, cluster2[1].connect_admin().session().receiver, "q") + + def test_replicate_binding(self): + """Verify that binding replication can be disabled""" + cluster = HaCluster(self, 2) + primary, backup = cluster[0], cluster[1] + ps = primary.connect().session() + a = primary.agent + a.addExchange("fanout", "ex") + a.addQueue("q") + a.bind("ex", "q", options={'qpid.replicate':'none'}) + backup.wait_backup("q") + + primary.kill() + assert retry(lambda: not is_running(primary.pid)) # Wait for primary to die + backup.promote() + bs = backup.connect_admin().session() + bs.sender("ex").send(qm.Message("msg")) + self.assert_browse_retry(bs, "q", []) + + def test_invalid_replication(self): + """Verify that we reject an attempt to declare a queue with invalid replication value.""" + cluster = HaCluster(self, 1, ha_replicate="all") + self.assertRaises(Exception, cluster[0].connect().session().sender, + "q;{create:always, node:{x-declare:{arguments:{'qpid.replicate':XXinvalidXX}}}}") + + def test_exclusive_queue(self): + """Ensure that we can back-up exclusive queues, i.e. the replicating + subscriptions are exempt from the exclusivity""" + cluster = HaCluster(self, 2) + def test(addr): + c = cluster[0].connect() + q = addr.split(";")[0] + r = c.session().receiver(addr) + self.assertRaises(qm.LinkError, c.session().receiver, addr) + s = c.session().sender(q).send(q) + cluster[1].assert_browse_backup(q, [q]) + test("excl_queue;{create:always, node:{x-declare:{exclusive:True}}}") + if qm == qpid.messaging: # FIXME aconway 2014-02-20: swig client no exclusive subscribe + test("excl_sub;{create:always, link:{x-subscribe:{exclusive:True}}}"); + + def test_auto_delete_exclusive(self): + """Verify that we ignore auto-delete, exclusive, non-auto-delete-timeout queues""" + cluster = HaCluster(self, 2) + s0 = cluster[0].connect().session() + s0.receiver("exad;{create:always,node:{x-declare:{exclusive:True,auto-delete:True}}}") + s0.receiver("ex;{create:always,node:{x-declare:{exclusive:True}}}") + ad = s0.receiver("ad;{create:always,node:{x-declare:{auto-delete:True}}}") + s0.receiver("time;{create:always,node:{x-declare:{exclusive:True,auto-delete:True,arguments:{'qpid.auto_delete_timeout':1}}}}") + s0.receiver("q;{create:always}") + + s1 = cluster[1].connect_admin().session() + cluster[1].wait_backup("q") + assert not valid_address(s1.connection, "exad") + assert valid_address(s1.connection, "ex") + assert valid_address(s1.connection, "ad") + assert valid_address(s1.connection, "time") + + # Verify that auto-delete queues are not kept alive by + # replicating subscriptions + ad.close() + s0.sync() + assert not valid_address(s0.connection, "ad") + + def test_broker_info(self): + """Check that broker information is correctly published via management""" + cluster = HaCluster(self, 3) + + def ha_broker(broker): + ha_broker = broker.agent.getHaBroker(); + ha_broker.update() + return ha_broker + + for broker in cluster: # Make sure HA system-id matches broker's + self.assertEqual(ha_broker(broker).systemId, UUID(broker.agent.getBroker().systemRef)) + + # Check that all brokers have the same membership as the cluster + def check_ids(broker): + cluster_ids = set([ ha_broker(b).systemId for b in cluster]) + broker_ids = set([m["system-id"] for m in ha_broker(broker).members]) + assert retry(lambda: cluster_ids == broker_ids, 1), "%s != %s on %s"%(cluster_ids, broker_ids, broker) + + for broker in cluster: check_ids(broker) + + # Add a new broker, check it is updated everywhere + b = cluster.start() + for broker in cluster: check_ids(broker) + + def test_auth(self): + """Verify that authentication does not interfere with replication.""" + # TODO aconway 2012-07-09: generate test sasl config portably for cmake + sasl_config=os.path.join(self.rootdir, "sasl_config") + if not os.path.exists(sasl_config): + print "WARNING: Skipping test, SASL test configuration %s not found."%sasl_config + return + acl=os.path.join(os.getcwd(), "policy.acl") + aclf=file(acl,"w") + # Minimum set of privileges required for the HA user. + aclf.write(""" +# HA user +acl allow zag@QPID access queue +acl allow zag@QPID create queue +acl allow zag@QPID consume queue +acl allow zag@QPID delete queue +acl allow zag@QPID access exchange +acl allow zag@QPID create exchange +acl allow zag@QPID bind exchange +acl allow zag@QPID publish exchange +acl allow zag@QPID delete exchange +acl allow zag@QPID access method +acl allow zag@QPID create link +acl allow zag@QPID access query +# Normal user +acl allow zig@QPID all all +acl deny all all + """) + aclf.close() + cluster = HaCluster( + self, 2, + args=["--auth", "yes", "--sasl-config", sasl_config, + "--acl-file", acl, + "--ha-username=zag", "--ha-password=zag", "--ha-mechanism=PLAIN" + ], + client_credentials=Credentials("zag", "zag", "PLAIN")) + c = cluster[0].connect(username="zig", password="zig") + s0 = c.session(); + a = cluster[0].agent + a.addQueue("q") + a.addExchange("fanout", "ex") + a.bind("ex", "q", "") + s0.sender("ex").send("foo"); + + # Transactions should be done over the tx_protocol + c = cluster[0].connect(protocol=self.tx_protocol, username="zig", password="zig") + s1 = c.session(transactional=True) + s1.sender("ex").send("foo-tx"); + cluster[1].assert_browse_backup("q", ["foo"]) + s1.commit() + cluster[1].assert_browse_backup("q", ["foo", "foo-tx"]) + + def test_alternate_exchange(self): + """Verify that alternate-exchange on exchanges and queues is propagated + to new members of a cluster. """ + cluster = HaCluster(self, 2) + s = cluster[0].connect().session() + # altex exchange: acts as alternate exchange + a = cluster[0].agent + a.addExchange("fanout", "altex") + # altq queue bound to altex, collect re-routed messages. + a.addQueue("altq") + a.bind("altex", "altq", "") + # ex exchange with alternate-exchange altex and no queues bound + a.addExchange("direct", "ex", {"alternate-exchange":"altex"}) + # create queue q with alternate-exchange altex + a.addQueue("q", {"alternate-exchange":"altex"}) + # create a bunch of exchanges to ensure we don't clean up prematurely if the + # response comes in multiple fragments. + for i in xrange(200): s.sender("ex.%s;{create:always,node:{type:topic}}"%i) + + def verify(broker): + c = broker.connect() + s = c.session() + # Verify unmatched message goes to ex's alternate. + s.sender("ex").send("foo") + altq = s.receiver("altq") + self.assertEqual("foo", altq.fetch(timeout=0).content) + s.acknowledge() + # Verify rejected message goes to q's alternate. + s.sender("q").send("bar") + msg = s.receiver("q").fetch(timeout=0) + self.assertEqual("bar", msg.content) + s.acknowledge(msg, qm.Disposition(qm.REJECTED)) # Reject the message + self.assertEqual("bar", altq.fetch(timeout=0).content) + s.acknowledge() + s.sync() # Make sure backups are caught-up. + c.close() + + # Sanity check: alternate exchanges on original broker + verify(cluster[0]) + a = cluster[0].agent + # Altex is in use as an alternate exchange, we should get an exception + self.assertRaises(Exception, a.delExchange, "altex") + # Check backup that was connected during setup. + def wait(broker): + broker.wait_status("ready") + for a in ["q", "ex", "altq", "altex"]: + broker.wait_backup(a) + wait(cluster[1]) + cluster.bounce(0) + verify(cluster[1]) + + # Check a newly started backup. + cluster.start() + wait(cluster[2]) + cluster.bounce(1) + verify(cluster[2]) + + # Check that alt-exchange in-use count is replicated + a = cluster[2].agent + self.assertRaises(Exception, a.delExchange, "altex") + a.delQueue("q") + self.assertRaises(Exception, a.delExchange, "altex") + a.delExchange("ex") + a.delExchange("altex") + + def test_priority_reroute(self): + """Regression test for QPID-4262, rerouting messages from a priority queue + to itself causes a crash""" + cluster = HaCluster(self, 2) + primary = cluster[0] + session = primary.connect().session() + a = primary.agent + a.addQueue("pq", {'qpid.priorities':10}) + a.bind("amq.fanout", "pq") + s = session.sender("pq") + for m in xrange(100): s.send(qm.Message(str(m), priority=m%10)) + pq = QmfAgent(primary.host_port()).getQueue("pq") + pq.reroute(request=0, useAltExchange=False, exchange="amq.fanout") + # Verify that consuming is in priority order + expect = [str(10*i+p) for p in xrange(9,-1,-1) for i in xrange(0,10) ] + actual = [m.content for m in primary.get_messages("pq", 100)] + self.assertEqual(expect, actual) + + def test_delete_missing_response(self): + """Check that a backup correctly deletes leftover queues and exchanges that are + missing from the initial reponse set.""" + # This test is a bit contrived, we set up the situation on backup brokers + # and then promote one. + cluster = HaCluster(self, 2, promote=False) + + # cluster[0] Will be the primary + s = cluster[0].connect_admin().session() + s.sender("q1;{create:always}") + s.sender("e1;{create:always, node:{type:topic}}") + + # cluster[1] will be the backup, has extra queues/exchanges + xdecl = "x-declare:{arguments:{'qpid.replicate':'all'}}" + node = "node:{%s}"%(xdecl) + s = cluster[1].connect_admin().session() + s.sender("q1;{create:always, %s}"%(node)) + s.sender("q2;{create:always, %s}"%(node)) + s.sender("e1;{create:always, node:{type:topic, %s}}"%(xdecl)) + s.sender("e2;{create:always, node:{type:topic, %s}}"%(xdecl)) + for a in ["q1", "q2", "e1", "e2"]: cluster[1].wait_backup(a) + + cluster[0].promote() + # Verify the backup deletes the surplus queue and exchange + cluster[1].wait_status("ready") + s = cluster[1].connect_admin().session() + self.assertRaises(qm.NotFound, s.receiver, ("q2")); + self.assertRaises(qm.NotFound, s.receiver, ("e2")); + + + def test_delete_qpid_4285(self): + """Regression test for QPID-4285: on deleting a queue it gets stuck in a + partially deleted state and causes replication errors.""" + cluster = HaCluster(self,2) + s = cluster[0].connect().session() + s.receiver("q;{create:always}") + cluster[1].wait_backup("q") + cluster.kill(0) # Make the backup take over. + s = cluster[1].connect().session() + cluster[1].agent.delQueue("q") # Delete q on new primary + self.assertRaises(qm.NotFound, s.receiver, "q") + assert not cluster[1].agent.getQueue("q") # Should not be in QMF + + def test_auto_delete_failover(self): + """Test auto-delete queues. Verify that: + - queues auto-deleted on the primary are deleted on the backup. + - auto-delete queues with/without timeout are deleted after a failover correctly + - auto-delete queues never used (subscribe to) to are not deleted + - messages are correctly routed to the alternate exchange. + """ + cluster = HaCluster(self, 3) + s = cluster[0].connect().session() + a = cluster[0].agent + + def setup(q, timeout=None): + # Create alternate exchange, auto-delete queue and queue bound to alt. ex. + a.addExchange("fanout", q+"-altex") + args = {"auto-delete":True, "alternate-exchange":q+"-altex"} + if timeout is not None: args['qpid.auto_delete_timeout'] = timeout + a.addQueue(q, args) + a.addQueue(q+"-altq") + a.bind("%s-altex"%q, "%s-altq"%q) + + for args in [["q1"],["q2",0],["q3",1],["q4"],["q5"]]: setup(*args) + receivers = [] + for i in xrange(1,5): # Don't use q5 + q = "q%s"%i + receivers.append(s.receiver(q)) # Subscribe + qs = s.sender(q); qs.send(q); qs.close() # Send q name as message + + receivers[3].close() # Trigger auto-delete for q4 + for b in cluster[1:3]: b.wait_no_queue("q4") # Verify deleted on backups + + cluster[0].kill(final=False) # Kill primary + cluster[2].promote() + cluster.restart(0) + cluster[2].wait_queue("q3") # Not yet auto-deleted, 1 sec timeout. + for b in cluster: + for q in ["q%s"%i for i in xrange(1,5)]: + b.wait_no_queue(q,timeout=2, msg=str(b)) # auto-deleted + b.assert_browse_backup("%s-altq"%q, [q]) # Routed to alternate + cluster[2].wait_queue("q5") # Not auto-deleted, never subscribed + cluster[2].connect().session().receiver("q5").close() + cluster[2].wait_no_queue("q5") + + def test_auto_delete_close(self): + """Verify auto-delete queues are deleted on backup if auto-deleted + on primary""" + cluster=HaCluster(self, 2) + + # Create altex to use as alternate exchange, with altq bound to it + a = cluster[0].agent + a.addExchange("fanout", "altex") + a.addQueue("altq", {"auto-delete":True}) + a.bind("altex", "altq") + + p = cluster[0].connect().session() + r = p.receiver("adq1;{create:always,node:{x-declare:{auto-delete:True,alternate-exchange:'altex'}}}") + s = p.sender("adq1") + for m in ["aa","bb","cc"]: s.send(m) + s.close() + cluster[1].wait_queue("adq1") + r.close() # trigger auto-delete of adq1 + cluster[1].wait_no_queue("adq1") + cluster[1].assert_browse_backup("altq", ["aa","bb","cc"]) + + def test_expired(self): + """Regression test for QPID-4379: HA does not properly handle expired messages""" + # Race between messages expiring and HA replicating consumer. + cluster = HaCluster(self, 2) + s = cluster[0].connect().session().sender("q;{create:always}", capacity=2) + def send_ttl_messages(): + for i in xrange(100): s.send(qm.Message(str(i), ttl=0.001)) + send_ttl_messages() + cluster.start() + send_ttl_messages() + + def test_missed_recreate(self): + """If a queue or exchange is destroyed and one with the same name re-created + while a backup is disconnected, the backup should also delete/recreate + the object when it re-connects""" + cluster = HaCluster(self, 3) + sn = cluster[0].connect().session() + # Create a queue with messages + s = sn.sender("qq;{create:always}") + msgs = [str(i) for i in xrange(3)] + for m in msgs: s.send(m) + cluster[1].assert_browse_backup("qq", msgs) + cluster[2].assert_browse_backup("qq", msgs) + # Set up an exchange with a binding. + a = cluster[0].agent + a.addExchange("fanout", "xx") + a.addQueue("xxq") + a.bind("xx", "xxq", "xxq") + cluster[1].wait_address("xx") + self.assertEqual(cluster[1].agent.getExchange("xx").values["bindingCount"], 1) + cluster[2].wait_address("xx") + self.assertEqual(cluster[2].agent.getExchange("xx").values["bindingCount"], 1) + + # Simulate the race by re-creating the objects before promoting the new primary + cluster.kill(0, promote_next=False) + xdecl = "x-declare:{arguments:{'qpid.replicate':'all'}}" + node = "node:{%s}"%(xdecl) + sn = cluster[1].connect_admin().session() + a = cluster[1].agent + a.delQueue("qq", if_empty=False) + s = sn.sender("qq;{create:always, %s}"%(node)) + s.send("foo") + a.delExchange("xx") + sn.sender("xx;{create:always,node:{type:topic,%s}}"%(xdecl)) + cluster[1].promote() + cluster[1].wait_status("active") + # Verify we are not still using the old objects on cluster[2] + cluster[2].assert_browse_backup("qq", ["foo"]) + cluster[2].wait_address("xx") + self.assertEqual(cluster[2].agent.getExchange("xx").values["bindingCount"], 0) + + def test_resource_limit_bug(self): + """QPID-5666 Regression test: Incorrect resource limit exception for queue creation.""" + cluster = HaCluster(self, 3) + qs = ["q%s"%i for i in xrange(10)] + a = cluster[0].agent + a.addQueue("q") + cluster[1].wait_backup("q") + cluster.kill(0) + cluster[1].promote() + cluster[1].wait_status("active") + a = cluster[1].agent + a.delQueue("q") + a.addQueue("q") + +def fairshare(msgs, limit, levels): + """ + Generator to return prioritised messages in expected order for a given fairshare limit + """ + count = 0 + last_priority = None + postponed = [] + while msgs or postponed: + if not msgs: + msgs = postponed + count = 0 + last_priority = None + postponed = [ ] + msg = msgs.pop(0) + if last_priority and priority_level(msg.priority, levels) == last_priority: + count += 1 + else: + last_priority = priority_level(msg.priority, levels) + count = 1 + l = limit(last_priority) + if (l and count > l): + postponed.append(msg) + else: + yield msg + return + +def priority_level(value, levels): + """ + Method to determine which of a distinct number of priority levels + a given value falls into. + """ + offset = 5-math.ceil(levels/2.0) + return min(max(value - offset, 0), levels-1) + +class LongTests(HaBrokerTest): + """Tests that can run for a long time if -DDURATION=<minutes> is set""" + + def duration(self): + d = self.config.defines.get("DURATION") + if d: return float(d)*60 + else: return 3 # Default is to be quick + + def test_failover_send_receive(self): + """Test failover with continuous send-receive""" + brokers = HaCluster(self, 3) + + # Start sender and receiver threads + n = 10 + senders = [ + NumberedSender( + brokers[0], url=brokers.url,max_depth=50, + queue="test%s"%(i), args=["--capacity=10"]) for i in xrange(n)] + + receivers = [ + NumberedReceiver( + brokers[0], url=brokers.url, sender=senders[i], + queue="test%s"%(i), args=["--capacity=10"]) for i in xrange(n)] + + for r in receivers: r.start() + for s in senders: s.start() + + def wait_passed(r, n): + """Wait for receiver r to pass n""" + def check(): + r.check() # Verify no exceptions + return r.received > n + 100 + assert retry(check), "Stalled %s waiting for %s, sent %s"%( + r.queue, n, [s for s in senders if s.queue==r.queue][0].sent) + + for r in receivers: wait_passed(r, 0) + + # Kill and restart brokers in a cycle: + endtime = time.time() + self.duration() + i = 0 + primary = 0 + try: + try: + while time.time() < endtime or i < 3: # At least 3 iterations + # Precondition: All 3 brokers running, + # primary = index of promoted primary + # one or two backups are running, + for s in senders: s.sender.assert_running() + for r in receivers: r.receiver.assert_running() + checkpoint = [ r.received+10 for r in receivers ] + victim = random.choice([0,1,2,primary]) # Give the primary a better chance. + if victim == primary: + # Don't kill primary till it is active and the next + # backup is ready, otherwise we can lose messages. + brokers[victim].wait_status("active") + next = (victim+1)%3 + brokers[next].wait_status("ready") + brokers.bounce(victim) # Next one is promoted + primary = next + else: + brokers.bounce(victim, promote_next=False) + + # Make sure we are not stalled + map(wait_passed, receivers, checkpoint) + # Run another checkpoint to ensure things work in this configuration + checkpoint = [ r.received+10 for r in receivers ] + map(wait_passed, receivers, checkpoint) + i += 1 + except: + traceback.print_exc() + raise + finally: + for s in senders: s.stop() + for r in receivers: r.stop() + dead = filter(lambda b: not b.is_running(), brokers) + if dead: raise Exception("Brokers not running: %s"%dead) + + def test_tx_send_receive(self): + brokers = HaCluster(self, 3) + sender = self.popen( + ["qpid-send", + "--broker", brokers[0].host_port(), + "--address", "q;{create:always}", + "--messages=1000", + "--tx=10", + "--connection-options={protocol:%s}" % self.tx_protocol + ]) + receiver = self.popen( + ["qpid-receive", + "--broker", brokers[0].host_port(), + "--address", "q;{create:always}", + "--messages=990", + "--timeout=10", + "--tx=10", + "--connection-options={protocol:%s}" % self.tx_protocol + ]) + self.assertEqual(sender.wait(), 0) + self.assertEqual(receiver.wait(), 0) + expect = [long(i) for i in range(991, 1001)] + sn = lambda m: m.properties["sn"] + brokers[0].assert_browse("q", expect, transform=sn) + brokers[1].assert_browse_backup("q", expect, transform=sn) + brokers[2].assert_browse_backup("q", expect, transform=sn) + + + def test_qmf_order(self): + """QPID 4402: HA QMF events can be out of order. + This test mimics the test described in the JIRA. Two threads repeatedly + declare the same auto-delete queue and close their connection. + """ + broker = Broker(self) + class Receiver(Thread): + def __init__(self, qname): + Thread.__init__(self) + self.qname = qname + self.stopped = False + + def run(self): + while not self.stopped: + self.connection = broker.connect() + try: + self.connection.session().receiver( + self.qname+";{create:always,node:{x-declare:{auto-delete:True}}}") + except qm.NotFound: pass # Can occur occasionally, not an error. + try: self.connection.close() + except: pass + + class QmfObject(object): + """Track existance of an object and validate QMF events""" + def __init__(self, type_name, name_field, name): + self.type_name, self.name_field, self.name = type_name, name_field, name + self.exists = False + + def qmf_event(self, event): + content = event.content[0] + event_type = content['_schema_id']['_class_name'] + values = content['_values'] + if event_type == self.type_name+"Declare" and values[self.name_field] == self.name: + disp = values['disp'] + log.debug("Event %s: disp=%s exists=%s"%( + event_type, values['disp'], self.exists)) + if self.exists: assert values['disp'] == 'existing' + else: assert values['disp'] == 'created' + self.exists = True + elif event_type == self.type_name+"Delete" and values[self.name_field] == self.name: + log.debug("Event %s: exists=%s"%(event_type, self.exists)) + assert self.exists + self.exists = False + + # Verify order of QMF events. + helper = EventHelper() + r = broker.connect().session().receiver(helper.eventAddress()) + threads = [Receiver("qq"), Receiver("qq")] + for t in threads: t.start() + queue = QmfObject("queue", "qName", "qq") + finish = time.time() + self.duration() + try: + while time.time() < finish: + queue.qmf_event(r.fetch()) + finally: + for t in threads: t.stopped = True; t.join() + + def test_max_queues(self): + """Verify that we behave properly if we try to exceed the max number + of replicated queues - currently limited by the max number of channels + in the replication link""" + # This test is very slow (3 mins), skip it unless duration() > 1 minute. + if self.duration() < 60: return + # This test is written in C++ for speed, it takes a long time + # to create 64k queues in python. See ha_test_max_queues.cpp. + cluster = HaCluster(self, 2) + test = self.popen(["ha_test_max_queues", cluster[0].host_port()]) + self.assertEqual(test.wait(), 0) + +class RecoveryTests(HaBrokerTest): + """Tests for recovery after a failure.""" + + def test_queue_hold(self): + """Verify that the broker holds queues without sufficient backup, + i.e. does not complete messages sent to those queues.""" + + l = LogLevel(ERROR) # Hide expected WARNING log messages from failover. + try: + # We don't want backups to time out for this test, set long timeout. + cluster = HaCluster(self, 4, args=["--ha-backup-timeout=120"]); + # Wait for the primary to be ready + cluster[0].wait_status("active") + for b in cluster[1:4]: b.wait_status("ready") + # Create a queue before the failure. + # FIXME aconway 2014-02-20: SWIG client doesn't respect sync=False + s1 = cluster.connect(0, native=True).session().sender("q1;{create:always}") + for b in cluster: b.wait_backup("q1") + for i in xrange(10): s1.send(str(i), timeout=0.1) + + # Kill primary and 2 backups + cluster[3].wait_status("ready") + for i in [0,1,2]: cluster.kill(i, promote_next=False, final=False) + cluster[3].promote() # New primary, backups will be 1 and 2 + cluster[3].wait_status("recovering") + + def assertSyncTimeout(s): + self.assertRaises(qpid.messaging.Timeout, s.sync, timeout=.01) + + # Create a queue after the failure + # FIXME aconway 2014-02-20: SWIG client doesn't respect sync=False + s2 = cluster.connect(3, native=True).session().sender("q2;{create:always}") + + # Verify that messages sent are not completed + for i in xrange(10,20): + s1.send(str(i), sync=False, timeout=0.1); + s2.send(str(i), sync=False, timeout=0.1) + + assertSyncTimeout(s1) + self.assertEqual(s1.unsettled(), 10) + assertSyncTimeout(s2) + self.assertEqual(s2.unsettled(), 10) + + # Verify we can receive even if sending is on hold: + cluster[3].assert_browse("q1", [str(i) for i in range(10)]) + + # Restart backups, verify queues are released only when both backups are up + cluster.restart(1) + assertSyncTimeout(s1) + self.assertEqual(s1.unsettled(), 10) + assertSyncTimeout(s2) + self.assertEqual(s2.unsettled(), 10) + cluster.restart(2) + cluster.restart(0) + + # Verify everything is up to date and active + def settled(sender): sender.sync(timeout=1); return sender.unsettled() == 0; + assert retry(lambda: settled(s1)), "Unsetttled=%s"%(s1.unsettled()) + assert retry(lambda: settled(s2)), "Unsetttled=%s"%(s2.unsettled()) + cluster[1].assert_browse_backup("q1", [str(i) for i in range(10)+range(10,20)]) + cluster[1].assert_browse_backup("q2", [str(i) for i in range(10,20)]) + cluster[3].wait_status("active"), + s1.session.connection.close() + s2.session.connection.close() + finally: l.restore() + + def test_expected_backup_timeout(self): + """Verify that we time-out expected backups and release held queues + after a configured interval. Verify backup is demoted to catch-up, + but can still rejoin. + """ + cluster = HaCluster(self, 3, args=["--ha-backup-timeout=0.5"]); + for i in [0,1]: cluster.kill(i, False) + cluster[2].promote() # New primary, expected backup will be 1 + # Should not go active till the expected backup connects or times out. + cluster[2].wait_status("recovering") + # Messages should be held till expected backup times out + ss = cluster[2].connect().session() + s = ss.sender("q;{create:always}") + s.send("foo", sync=False) + self.assertEqual(s.unsettled(), 1) # Verify message not settled immediately. + s.sync(timeout=1) # And settled after timeout. + cluster[2].wait_status("active") + + def test_join_ready_cluster(self): + """If we join a cluster where the primary is dead, the new primary is + not yet promoted and there are ready backups then we should refuse + promotion so that one of the ready backups can be chosen.""" + cluster = HaCluster(self, 2) + cluster[0].wait_status("active") + cluster[1].wait_status("ready") + cluster.bounce(0, promote_next=False) + self.assertRaises(Exception, cluster[0].promote) + os.kill(cluster[1].pid, signal.SIGSTOP) # Test for timeout if unresponsive. + cluster.bounce(0, promote_next=False) + cluster[0].promote() + + def test_stalled_backup(self): + """Make sure that a stalled backup broker does not stall the primary""" + cluster = HaCluster(self, 3, args=["--link-heartbeat-interval=1"]) + os.kill(cluster[1].pid, signal.SIGSTOP) + s = cluster[0].connect().session() + s.sender("q;{create:always}").send("x") + self.assertEqual("x", s.receiver("q").fetch(0).content) + +class StoreTests(HaBrokerTest): + """Test for HA with persistence.""" + + def check_skip(self): + if not BrokerTest.store_lib: + print "WARNING: skipping HA+store tests, no store lib found." + return not BrokerTest.store_lib + + def test_store_recovery(self): + """Verify basic store and recover functionality""" + if self.check_skip(): return + cluster = HaCluster(self, 1) + sn = cluster[0].connect().session() + # Create queue qq, exchange exx and binding between them + s = sn.sender("qq;{create:always,node:{durable:true}}") + sk = sn.sender("exx/k;{create:always,node:{type:topic, durable:true, x-declare:{type:'direct'}}}") + cluster[0].agent.bind("exx", "qq", "k") + for m in ["foo", "bar", "baz"]: s.send(qm.Message(m, durable=True)) + r = cluster[0].connect().session().receiver("qq") + self.assertEqual(r.fetch().content, "foo") + r.session.acknowledge() + # Sending this message is a hack to flush the dequeue operation on qq. + s.send(qm.Message("flush", durable=True)) + + def verify(broker, x_count): + sn = broker.connect().session() + assert_browse(sn, "qq", [ "bar", "baz", "flush" ]+ (x_count)*["x"]) + sn.sender("exx/k").send(qm.Message("x", durable=True)) + assert_browse(sn, "qq", [ "bar", "baz", "flush" ]+ (x_count+1)*["x"]) + + verify(cluster[0], 0) # Sanity check + cluster.bounce(0) + cluster[0].wait_status("active") + verify(cluster[0], 1) # Loaded from store + cluster.start() + cluster[1].wait_status("ready") + cluster.kill(0) + cluster[1].wait_status("active") + verify(cluster[1], 2) + cluster.bounce(1, promote_next=False) + cluster[1].promote() + cluster[1].wait_status("active") + verify(cluster[1], 3) + + def test_catchup_store(self): + """Verify that a backup erases queue data from store recovery before + doing catch-up from the primary.""" + if self.check_skip(): return + cluster = HaCluster(self, 2) + sn = cluster[0].connect(heartbeat=HaBroker.heartbeat).session() + s1 = sn.sender("q1;{create:always,node:{durable:true}}") + for m in ["foo","bar"]: s1.send(qm.Message(m, durable=True)) + s2 = sn.sender("q2;{create:always,node:{durable:true}}") + sk2 = sn.sender("ex/k2;{create:always,node:{type:topic, durable:true, x-declare:{type:'direct'}}}") + cluster[0].agent.bind("ex", "q2", "k2") + sk2.send(qm.Message("hello", durable=True)) + # Wait for backup to catch up. + cluster[1].assert_browse_backup("q1", ["foo","bar"]) + cluster[1].assert_browse_backup("q2", ["hello"]) + # Make changes that the backup doesn't see + cluster.kill(1, promote_next=False, final=False) + r1 = cluster[0].connect(heartbeat=HaBroker.heartbeat).session().receiver("q1") + for m in ["foo", "bar"]: self.assertEqual(r1.fetch().content, m) + r1.session.acknowledge() + for m in ["x","y","z"]: s1.send(qm.Message(m, durable=True)) + cluster[0].agent.unbind("ex", "q2", "k2") + cluster[0].agent.bind("ex", "q1", "k1") + # Restart both brokers from store to get inconsistent sequence numbering. + cluster.bounce(0, promote_next=False) + cluster[0].promote() + cluster[0].wait_status("active") + cluster.restart(1) + cluster[1].wait_status("ready") + + # Verify state + cluster[0].assert_browse("q1", ["x","y","z"]) + cluster[1].assert_browse_backup("q1", ["x","y","z"]) + + sn = cluster[0].connect(heartbeat=HaBroker.heartbeat).session() + sn.sender("ex/k1").send("boo") + cluster[0].assert_browse_backup("q1", ["x","y","z", "boo"]) + cluster[1].assert_browse_backup("q1", ["x","y","z", "boo"]) + sn.sender("ex/k2").send("hoo") # q2 was unbound so this should be dropped. + sn.sender("q2").send("end") # mark the end of the queue for assert_browse + cluster[0].assert_browse("q2", ["hello", "end"]) + cluster[1].assert_browse_backup("q2", ["hello", "end"]) + +def open_read(name): + try: + f = open(name) + return f.read() + finally: f.close() + +class TransactionTests(HaBrokerTest): + + def tx_simple_setup(self, cluster, broker=0): + """Start a transaction, remove messages from queue a, add messages to queue b""" + c = cluster.connect(broker, protocol=self.tx_protocol) + # Send messages to a, no transaction. + sa = c.session().sender("a;{create:always,node:{durable:true}}") + tx_msgs = ["x","y","z"] + for m in tx_msgs: sa.send(qm.Message(content=m, durable=True)) + sa.close() + + # Receive messages from a, in transaction. + tx = c.session(transactional=True) + txr = tx.receiver("a") + tx_msgs2 = [txr.fetch(1).content for i in xrange(3)] + self.assertEqual(tx_msgs, tx_msgs2) + + # Send messages to b, transactional, mixed with non-transactional. + sb = c.session().sender("b;{create:always,node:{durable:true}}") + txs = tx.sender("b") + msgs = [str(i) for i in xrange(3)] + for tx_m,m in zip(tx_msgs2, msgs): + txs.send(tx_m); + sb.send(m) + sb.close() + return tx + + def tx_subscriptions(self, broker): + """Return list of queue names for tx subscriptions""" + return [q for q in broker.agent.repsub_queues() + if q.startswith("qpid.ha-tx")] + + def test_tx_simple_commit(self): + cluster = HaCluster(self, 2, test_store=True, wait=True) + tx = self.tx_simple_setup(cluster) + tx.sync() + tx_queues = cluster[0].agent.tx_queues() + + # NOTE: backup does not process transactional dequeues until prepare + cluster[1].assert_browse_backup("a", ["x","y","z"]) + cluster[1].assert_browse_backup("b", ['0', '1', '2']) + + tx.acknowledge() + tx.commit() + tx.sync() + tx.close() + + for b in cluster: + self.assert_simple_commit_outcome(b, tx_queues) + + # Verify non-tx dequeue is replicated correctly + c = cluster.connect(0, protocol=self.tx_protocol) + r = c.session().receiver("b") + ri = receiver_iter(r, timeout=1) + self.assertEqual(['0', '1', '2', 'x', 'y', 'z'], [m.content for m in ri]) + r.session.acknowledge() + for b in cluster: b.assert_browse_backup("b", [], msg=b) + c.close() + tx.connection.close() + + + def check_enq_deq(self, cluster, queue, expect): + for b in cluster: + q = b.agent.getQueue(queue) + self.assertEqual( + (b.name,)+expect, + (b.name, q.msgTotalEnqueues, q.msgTotalDequeues, q.msgTxnEnqueues, q.msgTxnDequeues)) + + def test_tx_enq_notx_deq(self): + """Verify that a non-tx dequeue of a tx enqueue is replicated correctly""" + cluster = HaCluster(self, 2, test_store=True) + c = cluster.connect(0, protocol=self.tx_protocol) + + tx = c.session(transactional=True) + c.session().sender("qq;{create:always}").send("m1") + tx.sender("qq;{create:always}").send("tx") + tx.commit() + tx.close() + c.session().sender("qq;{create:always}").send("m2") + self.check_enq_deq(cluster, 'qq', (3, 0, 1, 0)) + + notx = c.session() + self.assertEqual(['m1', 'tx', 'm2'], [m.content for m in receiver_iter(notx.receiver('qq'))]) + notx.acknowledge() + self.check_enq_deq(cluster, 'qq', (3, 3, 1, 0)) + for b in cluster: b.assert_browse_backup('qq', [], msg=b) + for b in cluster: self.assert_tx_clean(b) + + def test_tx_enq_notx_deq_qpid_send(self): + """Verify that a non-tx dequeue of a tx enqueue is replicated correctly""" + cluster = HaCluster(self, 2, test_store=True) + + self.popen( + ['qpid-send', '-a', 'qq;{create:always}', '-b', cluster[0].host_port(), '--tx=1', + '--content-string=foo'] + ).assert_exit_ok() + for b in cluster: b.assert_browse_backup('qq', ['foo'], msg=b) + self.check_enq_deq(cluster, 'qq', (1, 0, 1, 0)) + + self.popen(['qpid-receive', '-a', 'qq', '-b', cluster[0].host_port()]).assert_exit_ok() + self.check_enq_deq(cluster, 'qq', (1, 1, 1, 0)) + for b in cluster: b.assert_browse_backup('qq', [], msg=b) + for b in cluster: self.assert_tx_clean(b) + + def assert_tx_clean(self, b): + """Verify that there are no transaction artifacts + (exchanges, queues, subscriptions) on b.""" + class FunctionCache: # Call a function and cache the result. + def __init__(self, f): self.f, self.value = f, None + def __call__(self): self.value = self.f(); return self.value + + txq= FunctionCache(b.agent.tx_queues) + assert retry(lambda: not txq()), "%s: unexpected %s"%(b, txq.value) + txsub = FunctionCache(lambda: self.tx_subscriptions(b)) + assert retry(lambda: not txsub()), "%s: unexpected %s"%(b, txsub.value) + # TODO aconway 2013-10-15: TX exchanges don't show up in management. + + def assert_simple_commit_outcome(self, b, tx_queues): + b.assert_browse_backup("a", [], msg=b) + b.assert_browse_backup("b", ['0', '1', '2', 'x', 'y', 'z'], msg=b) + # Check for expected actions on the store + expect = """<enqueue a x> +<enqueue a y> +<enqueue a z> +<begin tx 1> +<dequeue a x tx=1> +<dequeue a y tx=1> +<dequeue a z tx=1> +<commit tx=1> +""" + self.assertEqual(expect, open_read(b.store_log), msg=b) + self.assert_tx_clean(b) + + def test_tx_simple_rollback(self): + cluster = HaCluster(self, 2, test_store=True) + tx = self.tx_simple_setup(cluster) + tx.sync() + tx_queues = cluster[0].agent.tx_queues() + tx.acknowledge() + tx.rollback() + tx.close() # For clean test. + for b in cluster: self.assert_simple_rollback_outcome(b, tx_queues) + tx.connection.close() + + def assert_simple_rollback_outcome(self, b, tx_queues): + b.assert_browse_backup("a", ["x","y","z"], msg=b) + b.assert_browse_backup("b", ['0', '1', '2'], msg=b) + # Check for expected actions on the store + expect = """<enqueue a x> +<enqueue a y> +<enqueue a z> +""" + self.assertEqual(open_read(b.store_log), expect, msg=b) + self.assert_tx_clean(b) + + def test_tx_simple_failure(self): + """Verify we throw TransactionAborted if there is a store error during a transaction""" + cluster = HaCluster(self, 3, test_store=True) + tx = self.tx_simple_setup(cluster) + tx.sync() + tx_queues = cluster[0].agent.tx_queues() + tx.acknowledge() + l = LogLevel(ERROR) # Hide expected WARNING log messages from failover. + try: + cluster.bounce(0) # Should cause roll-back + tx.connection.session() # Wait for reconnect + for b in cluster: self.assert_simple_rollback_outcome(b, tx_queues) + self.assertRaises(qm.TransactionAborted, tx.sync) + self.assertRaises(qm.TransactionAborted, tx.commit) + try: tx.connection.close() + except qm.TransactionAborted: pass # Occasionally get exception on close. + for b in cluster: self.assert_simple_rollback_outcome(b, tx_queues) + finally: l.restore() + + def test_tx_simple_failover(self): + """Verify we throw TransactionAborted if there is a fail-over during a transaction""" + cluster = HaCluster(self, 3, test_store=True) + tx = self.tx_simple_setup(cluster) + tx.sync() + tx_queues = cluster[0].agent.tx_queues() + tx.acknowledge() + l = LogLevel(ERROR) # Hide expected WARNING log messages from failover. + try: + cluster.bounce(0) # Should cause roll-back + tx.connection.session() # Wait for reconnect + for b in cluster: self.assert_simple_rollback_outcome(b, tx_queues) + self.assertRaises(qm.TransactionAborted, tx.sync) + self.assertRaises(qm.TransactionAborted, tx.commit) + try: tx.connection.close() + except qm.TransactionAborted: pass # Occasionally get exception on close. + for b in cluster: self.assert_simple_rollback_outcome(b, tx_queues) + finally: l.restore() + + def test_tx_unknown_failover(self): + """Verify we throw TransactionUnknown if there is a failure during commit""" + cluster = HaCluster(self, 3, test_store=True) + tx = self.tx_simple_setup(cluster) + tx.sync() + tx_queues = cluster[0].agent.tx_queues() + tx.acknowledge() + l = LogLevel(ERROR) # Hide expected WARNING log messages from failover. + try: + os.kill(cluster[2].pid, signal.SIGSTOP) # Delay prepare response + class CommitThread(Thread): + def run(self): + try: tx.commit() + except Exception, e: + self.error = e + t = CommitThread() + t.start() # Commit in progress + t.join(timeout=0.01) + self.assertTrue(t.isAlive()) + cluster.bounce(0) + os.kill(cluster[2].pid, signal.SIGCONT) + t.join() + try: raise t.error + except qm.TransactionUnknown: pass + for b in cluster: self.assert_tx_clean(b) + try: tx.connection.close() + except qm.TransactionUnknown: pass # Occasionally get exception on close. + finally: l.restore() + + def test_tx_no_backups(self): + """Test the special case of a TX where there are no backups""" + + # Test commit + cluster = HaCluster(self, 1, test_store=True) + tx = self.tx_simple_setup(cluster) + tx.acknowledge() + tx.commit() + tx.sync() + tx_queues = cluster[0].agent.tx_queues() + tx.close() + self.assert_simple_commit_outcome(cluster[0], tx_queues) + + # Test rollback + cluster = HaCluster(self, 1, test_store=True) + tx = self.tx_simple_setup(cluster) + tx.sync() + tx_queues = cluster[0].agent.tx_queues() + tx.acknowledge() + tx.rollback() + tx.sync() + tx.close() + self.assert_simple_rollback_outcome(cluster[0], tx_queues) + + def test_tx_backup_fail(self): + cluster = HaCluster(self, 2, test_store=True, s_args=[[],["--test-store-name=bang"]]) + c = cluster[0].connect(protocol=self.tx_protocol) + tx = c.session(transactional=True) + s = tx.sender("q;{create:always,node:{durable:true}}") + for m in ["foo","TEST_STORE_DO bang: throw","bar"]: s.send(qm.Message(m, durable=True)) + def commit_sync(): tx.commit(); tx.sync() + self.assertRaises(qm.TransactionAborted, commit_sync) + for b in cluster: b.assert_browse_backup("q", []) + self.assertEqual(open_read(cluster[0].store_log), "<begin tx 1>\n<enqueue q foo tx=1>\n<enqueue q TEST_STORE_DO bang: throw tx=1>\n<enqueue q bar tx=1>\n<abort tx=1>\n") + self.assertEqual(open_read(cluster[1].store_log), "<begin tx 1>\n<enqueue q foo tx=1>\n<enqueue q TEST_STORE_DO bang: throw tx=1>\n<abort tx=1>\n") + + def test_tx_join_leave(self): + """Test cluster members joining/leaving cluster. + Also check that tx-queues are cleaned up at end of transaction.""" + + cluster = HaCluster(self, 3) + + # Leaving + tx = cluster[0].connect(protocol=self.tx_protocol).session(transactional=True) + s = tx.sender("q;{create:always}") + s.send("a", sync=True) + self.assertEqual([1,1,1], [len(b.agent.tx_queues()) for b in cluster]) + cluster[1].kill(final=False) + s.send("b") + tx.commit() + tx.connection.close() + for b in [cluster[0],cluster[2]]: + self.assert_tx_clean(b) + b.assert_browse_backup("q", ["a","b"], msg=b) + # Joining + tx = cluster[0].connect(protocol=self.tx_protocol).session(transactional=True) + s = tx.sender("q;{create:always}") + s.send("foo") + cluster.restart(1) # Not a part of the current transaction. + tx.commit() + tx.connection.close() + for b in cluster: self.assert_tx_clean(b) + # The new member is not in the tx but receives the results normal replication. + for b in cluster: b.assert_browse_backup("q", ["a", "b", "foo"], msg=b) + + def test_tx_block_threads(self): + """Verify that TXs blocked in commit don't deadlock.""" + cluster = HaCluster(self, 2, args=["--worker-threads=2"], test_store=True) + n = 10 # Number of concurrent transactions + sessions = [cluster.connect(0, protocol=self.tx_protocol).session(transactional=True) for i in xrange(n)] + # Have the store delay the response for 10s + for s in sessions: + sn = s.sender("qq;{create:always,node:{durable:true}}") + sn.send(qm.Message("foo", durable=True)) + self.assertEqual(n, len(cluster[1].agent.tx_queues())) + threads = [ Thread(target=s.commit) for s in sessions] + for t in threads: t.start() + cluster[0].ready(timeout=1) # Check for deadlock + for b in cluster: b.assert_browse_backup('qq', ['foo']*n) + for t in threads: t.join() + for s in sessions: s.connection.close() + + def test_other_tx_tests(self): + try: + import qpid_tests.broker_0_10 + except ImportError: + raise Skipped("Tests not found") + cluster = HaCluster(self, 3) + if "QPID_PORT" in os.environ: del os.environ["QPID_PORT"] + self.popen(["qpid-txtest2", "--broker", cluster[0].host_port()]).assert_exit_ok() + print + self.popen(["qpid-python-test", + "-m", "qpid_tests.broker_0_10", + "-m", "qpid_tests.broker_1_0", + "-b", "localhost:%s"%(cluster[0].port()), + "*.tx.*"], stdout=None, stderr=None).assert_exit_ok() + +if __name__ == "__main__": + qpid_ha_exec = os.getenv("QPID_HA_EXEC") + if qpid_ha_exec and os.path.isfile(qpid_ha_exec): + BrokerTest.amqp_tx_warning() + outdir = "ha_tests.tmp" + shutil.rmtree(outdir, True) + os.execvp("qpid-python-test", + ["qpid-python-test", "-m", "ha_tests", "-DOUTDIR=%s"%outdir] + + sys.argv[1:]) + else: + print "Skipping ha_tests, qpid-ha not available" + + diff --git a/qpid/cpp/src/tests/header_test.cpp b/qpid/cpp/src/tests/header_test.cpp new file mode 100644 index 0000000000..c36b4f3bc3 --- /dev/null +++ b/qpid/cpp/src/tests/header_test.cpp @@ -0,0 +1,59 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include <iostream> + +#include "TestOptions.h" +#include "qpid/client/Connection.h" +#include "qpid/client/Message.h" +#include "qpid/client/Session.h" +#include "qpid/client/SubscriptionManager.h" + +using namespace qpid; +using namespace qpid::client; +using namespace std; + +int main(int argc, char** argv) +{ + TestOptions opts; + try { + opts.parse(argc, argv); + Connection connection; + connection.open(opts.con); + Session session = connection.newSession(); + std::string q("header_interop_test_queue"); + session.queueDeclare(arg::queue=q); + double pi = 3.14159265; + float e = 2.71828f; + Message msg("", q); + msg.getMessageProperties().getApplicationHeaders().setDouble("pi", pi); + msg.getMessageProperties().getApplicationHeaders().setFloat("e", e); + session.messageTransfer(arg::content=msg); + + session.close(); + connection.close(); + + return 0; + } catch(const exception& e) { + cout << e.what() << endl; + } + return 1; +} diff --git a/qpid/cpp/src/tests/header_test.py b/qpid/cpp/src/tests/header_test.py new file mode 100755 index 0000000000..d5a2c16c01 --- /dev/null +++ b/qpid/cpp/src/tests/header_test.py @@ -0,0 +1,86 @@ +#!/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. +# + +import qpid +import sys +import os +from qpid.util import connect +from qpid.connection import Connection +from qpid.datatypes import Message, RangedSet, uuid4 +from qpid.queue import Empty +from math import fabs + +def getApplicationHeaders(msg): + for h in msg.headers: + if hasattr(h, 'application_headers'): return getattr(h, 'application_headers') + return None + +# Set parameters for login + +host="127.0.0.1" +port=5672 +user="guest" +password="guest" + +if len(sys.argv) > 1 : + host=sys.argv[1] +if len(sys.argv) > 2 : + port=int(sys.argv[2]) + +# Create a connection. +socket = connect(host, port) +connection = Connection (sock=socket) +connection.start() +session = connection.session(str(uuid4())) + +q = "header_interop_test_queue" +session.queue_declare(queue=q) + +session.message_subscribe(queue=q, destination="received") +queue = session.incoming("received") +queue.start() + +msg = queue.get(timeout=10) +pi = 3.14159265 +e = 2.71828 + +headers = getApplicationHeaders(msg) +pi_ = headers["pi"] +e_ = headers["e"] +session.close(timeout=10) + +failed = False + +if pi != pi_: + print "got incorrect value for pi: ", pi_, " expected:", pi + failed = True + +if fabs(e - e_) > 0.0001: + print "got incorrect value for e: ", e_, " expected:", e + failed = True + +if failed: + sys.exit(1) +else: + print "Correct header values received." + sys.exit(0) + + + diff --git a/qpid/cpp/src/tests/headers_federation.py b/qpid/cpp/src/tests/headers_federation.py new file mode 100644 index 0000000000..60cff1da54 --- /dev/null +++ b/qpid/cpp/src/tests/headers_federation.py @@ -0,0 +1,99 @@ +#!/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. +# + +import sys +from qpid.testlib import TestBase010 +from qpid.datatypes import Message +from qpid.queue import Empty +from time import sleep + +class HeadersFederationTests(TestBase010): + + def remote_host(self): + return self.defines.get("remote-host", "localhost") + + def remote_port(self): + return int(self.defines["remote-port"]) + + def verify_cleanup(self): + attempts = 0 + total = len(self.qmf.getObjects(_class="bridge")) + len(self.qmf.getObjects(_class="link")) + while total > 0: + attempts += 1 + if attempts >= 10: + self.fail("Bridges and links didn't clean up") + return + sleep(1) + total = len(self.qmf.getObjects(_class="bridge")) + len(self.qmf.getObjects(_class="link")) + + def test_dynamic_headers_unbind(self): + session = self.session + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_dynamic_headers_unbind") + + session.exchange_declare(exchange="fed.headers_unbind", type="headers") + r_session.exchange_declare(exchange="fed.headers_unbind", type="headers") + + self.startQmf() + qmf = self.qmf + + broker = qmf.getObjects(_class="broker")[0] + result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp") + self.assertEqual(result.status, 0) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "fed.headers_unbind", "fed.headers_unbind", "", "", "", False, False, True, 0) + self.assertEqual(result.status, 0) + bridge = qmf.getObjects(_class="bridge")[0] + sleep(5) + + session.queue_declare(queue="fed1", exclusive=True, auto_delete=True) + queue = qmf.getObjects(_class="queue", name="fed1")[0] + queue.update() + self.assertEqual(queue.bindingCount, 1, + "bindings not accounted for (expected 1, got %d)" % queue.bindingCount) + + session.exchange_bind(queue="fed1", exchange="fed.headers_unbind", binding_key="key1", arguments={'x-match':'any', 'class':'first'}) + queue.update() + self.assertEqual(queue.bindingCount, 2, + "bindings not accounted for (expected 2, got %d)" % queue.bindingCount) + + session.exchange_unbind(queue="fed1", exchange="fed.headers_unbind", binding_key="key1") + queue.update() + self.assertEqual(queue.bindingCount, 1, + "bindings not accounted for (expected 1, got %d)" % queue.bindingCount) + + result = bridge.close() + self.assertEqual(result.status, 0) + result = link.close() + self.assertEqual(result.status, 0) + + self.verify_cleanup() + + def getProperty(self, msg, name): + for h in msg.headers: + if hasattr(h, name): return getattr(h, name) + return None + + def getAppHeader(self, msg, name): + headers = self.getProperty(msg, "application_headers") + if headers: + return headers[name] + return None diff --git a/qpid/cpp/src/tests/idle_timeout_tests.py b/qpid/cpp/src/tests/idle_timeout_tests.py new file mode 100755 index 0000000000..22a107a110 --- /dev/null +++ b/qpid/cpp/src/tests/idle_timeout_tests.py @@ -0,0 +1,95 @@ +#!/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. +# + +import os +import shutil +import signal +import sys + +from brokertest import * + +class AmqpIdleTimeoutTest(BrokerTest): + """ + Test AMQP 1.0 idle-timeout support + """ + def setUp(self): + BrokerTest.setUp(self) + if not BrokerTest.amqp_lib: + raise Skipped("AMQP 1.0 library not found") + if qm != qpid_messaging: + raise Skipped("AMQP 1.0 client not found") + self._broker = self.broker() + + def test_client_timeout(self): + """Ensure that the client disconnects should the broker stop + responding. + """ + conn = self._broker.connect(native=False, timeout=None, + protocol="amqp1.0", heartbeat=1) + self.assertTrue(conn.isOpen()) + # should disconnect within 2 seconds of broker stop + deadline = time.time() + 8 + os.kill(self._broker.pid, signal.SIGSTOP) + while time.time() < deadline: + if not conn.isOpen(): + break; + self.assertTrue(not conn.isOpen()) + os.kill(self._broker.pid, signal.SIGCONT) + + + def test_broker_timeout(self): + """By default, the broker will adopt the same timeout as the client + (mimics the 0-10 timeout behavior). Verify the broker disconnects + unresponsive clients. + """ + + count = len(self._broker.agent.getAllConnections()) + + # Create a new connection to the broker: + receiver_cmd = ["qpid-receive", + "--broker", self._broker.host_port(), + "--address=amq.fanout", + "--connection-options={protocol:amqp1.0, heartbeat:1}", + "--forever"] + receiver = self.popen(receiver_cmd, stdout=PIPE, stderr=PIPE, + expect=EXPECT_UNKNOWN) + start = time.time() + deadline = time.time() + 10 + while time.time() < deadline: + if count < len(self._broker.agent.getAllConnections()): + break; + self.assertTrue(count < len(self._broker.agent.getAllConnections())) + + # now 'hang' the client, the broker should disconnect + start = time.time() + os.kill(receiver.pid, signal.SIGSTOP) + deadline = time.time() + 10 + while time.time() < deadline: + if count == len(self._broker.agent.getAllConnections()): + break; + self.assertEqual(count, len(self._broker.agent.getAllConnections())) + os.kill(receiver.pid, signal.SIGCONT) + receiver.teardown() + + +if __name__ == "__main__": + shutil.rmtree("brokertest.tmp", True) + os.execvp("qpid-python-test", + ["qpid-python-test", "-m", "idle_timeout_tests"] + sys.argv[1:]) diff --git a/qpid/cpp/src/tests/install_env.sh.in b/qpid/cpp/src/tests/install_env.sh.in new file mode 100644 index 0000000000..d29a23930d --- /dev/null +++ b/qpid/cpp/src/tests/install_env.sh.in @@ -0,0 +1,26 @@ +# +# 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. +# + +absdir() { echo `cd $1 && pwd`; } + +prefix=`absdir @prefix@` +export QPID_INSTALL_PREFIX=$prefix +export PATH=$prefix/bin:$prefix/sbin:$prefix/libexec/qpid/tests:$PATH +export LD_LIBRARY_PATH=$prefix/lib:$LD_LIBRARY_PATH +export PYTHONPATH=$prefix/lib/python2.6/site-packages:$PYTHONPATH diff --git a/qpid/cpp/src/tests/interlink_tests.py b/qpid/cpp/src/tests/interlink_tests.py new file mode 100755 index 0000000000..3eec2422f1 --- /dev/null +++ b/qpid/cpp/src/tests/interlink_tests.py @@ -0,0 +1,336 @@ +#!/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. +# + +import os, signal, sys, time, imp, re, subprocess, glob, random, logging, shutil, math, unittest, random +import traceback +from qpid.messaging import Message, SessionError, NotFound, ConnectionError, ReceiverError, Connection, Timeout, Disposition, REJECTED, Empty +from brokertest import * +from ha_test import HaPort +from threading import Thread, Lock, Condition +from logging import getLogger, WARN, ERROR, DEBUG, INFO +from qpidtoollibs import BrokerObject + +class Domain(BrokerObject): + def __init__(self, broker, values): + BrokerObject.__init__(self, broker, values) + +class Config: + def __init__(self, broker, address="q;{create:always}", version="amqp1.0"): + self.url = broker.host_port() + self.address = address + self.version = version + + def __str__(self): + return "url: %s, address: %s, version: %s" % (self.url, self.address, self.version) + +class AmqpBrokerTest(BrokerTest): + """ + Tests using AMQP 1.0 support + """ + def setUp(self): + BrokerTest.setUp(self) + self.port_holder = HaPort(self) + self.broker = self.amqp_broker(port_holder=self.port_holder) + self.default_config = Config(self.broker) + self.agent = self.broker.agent + + def sender(self, config, reply_to=None): + cmd = ["qpid-send", + "--broker", config.url, + "--address", config.address, + "--connection-options", "{protocol:%s}" % config.version, + "--content-stdin", "--send-eos=1" + ] + if reply_to: + cmd.append( "--reply-to=%s" % reply_to) + return self.popen(cmd, stdin=PIPE) + + def receiver(self, config): + cmd = ["qpid-receive", + "--broker", config.url, + "--address", config.address, + "--connection-options", "{protocol:%r}" % config.version, + "--timeout=10" + ] + return self.popen(cmd, stdout=PIPE) + + def ready_receiver(self, config): + # NOTE: some tests core dump when run with SWIG binding over proton + # version<=0.6. This is fixed on proton 0.7. + def use_native(): + pv=os.environ.get("QPID_PROTON_VERSION") + return pv and [int(n) for n in pv.split(".")] <= [0,6] + s = self.broker.connect(native=use_native()).session() + r = s.receiver("readyq; {create:always}") + cmd = ["qpid-receive", + "--broker", config.url, + "--address", config.address, + "--connection-options", "{protocol:%r}" % config.version, + "--timeout=10", "--ready-address=readyq;{create:always}" + ] + result = self.popen(cmd, stdout=PIPE) + r.fetch(timeout=1) # wait until receiver is actually ready + s.acknowledge() + r.close() + s.close() + return result + + def send_and_receive(self, send_config=None, recv_config=None, count=1000, reply_to=None, wait_for_receiver=False, debug=False): + if debug: + print "sender config is %s" % (send_config or self.default_config) + print "receiver config is %s" % (recv_config or self.default_config) + sender = self.sender(send_config or self.default_config, reply_to) + sender._set_cloexec_flag(sender.stdin) #required for older python, see http://bugs.python.org/issue4112 + if wait_for_receiver: + receiver = self.ready_receiver(recv_config or self.default_config) + else: + receiver = self.receiver(recv_config or self.default_config) + + messages = ["message-%s" % (i+1) for i in range(count)] + for m in messages: + sender.stdin.write(m + "\n") + sender.stdin.flush() + sender.stdin.close() + if debug: + c = send_config or self.default_config + print "sent %s messages to %s sn %s" % (len(messages), c.address, c.url) + + if debug: + c = recv_config or self.default_config + print "reading messages from %s sn %s" % (c.address, c.url) + for m in messages: + l = receiver.stdout.readline().rstrip() + if debug: + print l + assert m == l, (m, l) + + sender.wait() + receiver.wait() + + def test_simple(self): + self.send_and_receive() + + def test_translate1(self): + self.send_and_receive(recv_config=Config(self.broker, version="amqp0-10")) + + def test_translate2(self): + self.send_and_receive(send_config=Config(self.broker, version="amqp0-10")) + + def test_translate_with_large_routingkey(self): + self.send_and_receive(send_config=Config(self.broker, address="amq.topic/a.%s" % ("x" * 256), version="amqp1.0"), recv_config=Config(self.broker, address="amq.topic/a.*", version="amqp0-10"), wait_for_receiver=True) + + def send_and_receive_empty(self, send_config=None, recv_config=None): + sconfig = send_config or self.default_config + rconfig = recv_config or self.default_config + send_cmd = ["qpid-send", + "--broker", sconfig.url, + "--address=%s" % sconfig.address, + "--connection-options={protocol:%s}" % sconfig.version, + "--content-size=0", + "--messages=1", + "-P", "my-header=abc" + ] + sender = self.popen(send_cmd) + sender.wait() + receive_cmd = ["qpid-receive", + "--broker", rconfig.url, + "--address=%s" % rconfig.address, + "--connection-options={protocol:%s}" % rconfig.version, + "--messages=1", + "--print-content=false", "--print-headers=true" + ] + receiver = self.popen(receive_cmd, stdout=PIPE) + l = receiver.stdout.read() + assert "my-header:abc" in l + receiver.wait() + + def test_translate_empty_1(self): + self.send_and_receive_empty(recv_config=Config(self.broker, version="amqp0-10")) + + def test_translate_empty_2(self): + self.send_and_receive_empty(send_config=Config(self.broker, version="amqp0-10")) + + def request_response(self, reply_to, send_config=None, request_config=None, response_config=None, count=1000, wait_for_receiver=False): + rconfig = request_config or self.default_config + echo_cmd = ["qpid-receive", + "--broker", rconfig.url, + "--address=%s" % rconfig.address, + "--connection-options={protocol:%s}" % rconfig.version, + "--timeout=10", "--print-content=false", "--print-headers=false" + ] + requests = self.popen(echo_cmd) + self.send_and_receive(send_config, response_config, count, reply_to=reply_to, wait_for_receiver=wait_for_receiver) + requests.wait() + + def request_response_local(self, request_address, response_address, wait_for_receiver=False, request_version="amqp1.0", echo_version="amqp1.0"): + self.request_response(response_address, send_config=Config(self.broker, address=request_address, version=request_version), request_config=Config(self.broker, address=request_address, version=echo_version), response_config=Config(self.broker, address=response_address, version=request_version), wait_for_receiver=wait_for_receiver) + + def test_request_reponse_queue(self): + self.agent.create("queue", "q1") + self.agent.create("queue", "q2") + self.request_response_local("q1", "q2") + + def test_request_reponse_queue_translated1(self): + self.agent.create("queue", "q1") + self.agent.create("queue", "q2") + self.request_response_local("q1", "q2", request_version="amqp0-10", echo_version="amqp1.0") + + def test_request_reponse_queue_translated2(self): + self.agent.create("queue", "q1") + self.agent.create("queue", "q2") + self.request_response_local("q1", "q2", request_version="amqp1.0", echo_version="amqp0-10") + + def test_request_reponse_exchange(self): + self.agent.create("queue", "q1") + self.request_response_local("q1", "amq.fanout", wait_for_receiver=True) + + def test_request_reponse_exchange_translated1(self): + self.agent.create("queue", "q1") + self.request_response_local("q1", "amq.fanout", wait_for_receiver=True, request_version="amqp0-10", echo_version="amqp1.0") + + def test_request_reponse_exchange_translated2(self): + self.agent.create("queue", "q1") + self.request_response_local("q1", "amq.fanout", wait_for_receiver=True, request_version="amqp1.0", echo_version="amqp0-10") + + def test_request_reponse_exchange_with_subject(self): + self.agent.create("queue", "q1") + self.request_response_local("q1", "amq.topic/abc; {node:{type:topic}}", wait_for_receiver=True) + + def test_request_reponse_exchange_with_subject_translated1(self): + self.agent.create("queue", "q1") + self.request_response_local("q1", "amq.topic/abc; {node:{type:topic}}", wait_for_receiver=True, request_version="amqp0-10", echo_version="amqp1.0") + + def test_request_reponse_exchange_with_subject_translated2(self): + self.agent.create("queue", "q1") + self.request_response_local("q1", "amq.topic/abc; {node:{type:topic}}", wait_for_receiver=True, request_version="amqp1.0", echo_version="amqp0-10") + + def test_domain(self): + brokerB = self.amqp_broker() + self.agent.create("domain", "BrokerB", {"url":brokerB.host_port()}) + domains = self.agent._getAllBrokerObjects(Domain) + assert len(domains) == 1 + assert domains[0].name == "BrokerB" + + def incoming_link(self, mechanism): + brokerB = self.amqp_broker() + agentB = brokerB.agent + self.agent.create("queue", "q") + agentB.create("queue", "q") + self.agent.create("domain", "BrokerB", {"url":brokerB.host_port(), "sasl_mechanisms":mechanism}) + self.agent.create("incoming", "Link1", {"domain":"BrokerB","source":"q","target":"q"}) + #send to brokerB, receive from brokerA + self.send_and_receive(send_config=Config(brokerB)) + + def test_incoming_link_anonymous(self): + self.incoming_link("ANONYMOUS") + + def test_incoming_link_nosasl(self): + self.incoming_link("NONE") + + def test_outgoing_link(self): + brokerB = self.amqp_broker() + agentB = brokerB.agent + self.agent.create("queue", "q") + agentB.create("queue", "q") + self.agent.create("domain", "BrokerB", {"url":brokerB.host_port(), "sasl_mechanisms":"NONE"}) + self.agent.create("outgoing", "Link1", {"domain":"BrokerB","source":"q","target":"q"}) + #send to brokerA, receive from brokerB + self.send_and_receive(recv_config=Config(brokerB)) + + def test_relay(self): + brokerB = self.amqp_broker() + agentB = brokerB.agent + agentB.create("queue", "q") + self.agent.create("domain", "BrokerB", {"url":brokerB.host_port(), "sasl_mechanisms":"NONE"}) + #send to q on broker B through brokerA + self.send_and_receive(send_config=Config(self.broker, address="q@BrokerB"), recv_config=Config(brokerB)) + + def test_reconnect(self): + receiver_cmd = ["qpid-receive", + "--broker", self.broker.host_port(), + "--address=amq.fanout", + "--connection-options={protocol:amqp1.0, reconnect:True,container_id:receiver}", + "--timeout=10", "--print-content=true", "--print-headers=false" + ] + receiver = self.popen(receiver_cmd, stdout=PIPE) + + sender_cmd = ["qpid-send", + "--broker", self.broker.host_port(), + "--address=amq.fanout", + "--connection-options={protocol:amqp1.0,reconnect:True,container_id:sender}", + "--content-stdin", "--send-eos=1" + ] + sender = self.popen(sender_cmd, stdin=PIPE) + sender._set_cloexec_flag(sender.stdin) #required for older python, see http://bugs.python.org/issue4112 + + + batch1 = ["message-%s" % (i+1) for i in range(10000)] + for m in batch1: + sender.stdin.write(m + "\n") + sender.stdin.flush() + + self.broker.kill() + self.broker = self.amqp_broker(port_holder=self.port_holder) + + batch2 = ["message-%s" % (i+1) for i in range(10000, 20000)] + for m in batch2: + sender.stdin.write(m + "\n") + sender.stdin.flush() + + sender.stdin.close() + + last = None + m = receiver.stdout.readline().rstrip() + while len(m): + last = m + m = receiver.stdout.readline().rstrip() + assert last == "message-20000", (last) + + """ Create and return a broker with AMQP 1.0 support """ + def amqp_broker(self): + assert BrokerTest.amqp_lib, "Cannot locate AMQP 1.0 plug-in" + self.port_holder = HaPort(self) #reserve port + args = ["--load-module", BrokerTest.amqp_lib, + "--socket-fd=%s" % self.port_holder.fileno, + "--listen-disable=tcp", + "--log-enable=trace+:Protocol", + "--log-enable=info+"] + return BrokerTest.broker(self, args, port=self.port_holder.port) + + def amqp_broker(self, port_holder=None): + assert BrokerTest.amqp_lib, "Cannot locate AMQP 1.0 plug-in" + if port_holder: + args = ["--load-module", BrokerTest.amqp_lib, + "--socket-fd=%s" % port_holder.fileno, + "--listen-disable=tcp", + "--log-enable=trace+:Protocol", + "--log-enable=info+"] + return BrokerTest.broker(self, args, port=port_holder.port) + else: + args = ["--load-module", BrokerTest.amqp_lib, + "--log-enable=trace+:Protocol", + "--log-enable=info+"] + return BrokerTest.broker(self, args) + + +if __name__ == "__main__": + shutil.rmtree("brokertest.tmp", True) + os.execvp("qpid-python-test", + ["qpid-python-test", "-m", "interlink_tests"] + sys.argv[1:]) diff --git a/qpid/cpp/src/tests/interop_tests.py b/qpid/cpp/src/tests/interop_tests.py new file mode 100755 index 0000000000..f76b9f634b --- /dev/null +++ b/qpid/cpp/src/tests/interop_tests.py @@ -0,0 +1,220 @@ +#!/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. +# + +""" +A set of tests that can be run against a foreign AMQP 1.0 broker. + +RUNNING WITH A FOREIGN BROKER: + +1. Start the broker +2. Create persistent queues named: interop-a interop-b interop-q tx-1 tx-2 +3. Export the environment variable QPID_INTEROP_URL with the URL to connect to your broker + in the form [user[:password]@]host[:port] +4. From the build directory run this test: + ctest -VV -R interop_tests + +If QPID_INTEROP_URL is not set, a qpidd broker will be started for the test. +""" + +import os, sys, shutil, subprocess +import qpid_messaging as qm +from brokertest import * + +URL='QPID_INTEROP_URL' + +class InteropTest(BrokerTest): + + def setUp(self): + super(InteropTest, self).setUp() + self.url = os.environ[URL] + self.connect_opts = ['--broker', self.url, '--connection-options', '{protocol:amqp1.0}'] + + def connect(self, **kwargs): + """Python connection to interop URL""" + c = qm.Connection.establish(self.url, protocol='amqp1.0', **kwargs) + self.teardown_add(c) + return c + + def drain(self, queue, connection=None): + """ + Drain a queue to make sure it is empty. Throw away the messages. + """ + c = connection or self.connect() + r = c.session().receiver(queue) + try: + while True: + r.fetch(timeout=0) + r.session.acknowledge() + except qm.Empty: + pass + r.close() + + def clear_queue(self, queue, connection=None, properties=None, durable=False): + """ + Make empty queue, prefix with self.id(). Create if needed, drain if needed + @return queue name. + """ + queue = "interop-%s" % queue + c = connection or self.connect() + props = {'create':'always'} + if durable: props['node'] = {'durable':True} + if properties: props.update(properties) + self.drain("%s;%s" % (queue, props), c) + return queue + + +class SimpleTest(InteropTest): + """Simple test to check the broker is responding.""" + + def test_send_receive_python(self): + c = self.connect() + q = self.clear_queue('q', c) + s = c.session() + s.sender(q).send('foo') + self.assertEqual('foo', s.receiver(q).fetch().content) + + def test_send_receive_cpp(self): + q = self.clear_queue('q') + args = ['-b', self.url, '-a', q] + self.check_output(['qpid-send', '--content-string=cpp_foo'] + args) + self.assertEqual('cpp_foo', self.check_output(['qpid-receive'] + args).strip()) + + +class PythonTxTest(InteropTest): + + def tx_simple_setup(self): + """Start a transaction, remove messages from queue a, add messages to queue b""" + c = self.connect() + qa, qb = self.clear_queue('a', c, durable=True), self.clear_queue('b', c, durable=True) + + # Send messages to a, no transaction. + sa = c.session().sender(qa+";{create:always,node:{durable:true}}") + tx_msgs = ['x', 'y', 'z'] + for m in tx_msgs: sa.send(qm.Message(content=m, durable=True)) + + # Receive messages from a, in transaction. + tx = c.session(transactional=True) + txr = tx.receiver(qa) + self.assertEqual(tx_msgs, [txr.fetch(1).content for i in xrange(3)]) + tx.acknowledge() + + # Send messages to b, transactional, mixed with non-transactional. + sb = c.session().sender(qb+";{create:always,node:{durable:true}}") + txs = tx.sender(qb) + msgs = [str(i) for i in xrange(3)] + for tx_m, m in zip(tx_msgs, msgs): + txs.send(tx_m); + sb.send(m) + tx.sync() + return tx, qa, qb + + def test_tx_simple_commit(self): + tx, qa, qb = self.tx_simple_setup() + s = self.connect().session() + assert_browse(s, qa, []) + assert_browse(s, qb, ['0', '1', '2']) + tx.commit() + assert_browse(s, qa, []) + assert_browse(s, qb, ['0', '1', '2', 'x', 'y', 'z']) + + def test_tx_simple_rollback(self): + tx, qa, qb = self.tx_simple_setup() + s = self.connect().session() + assert_browse(s, qa, []) + assert_browse(s, qb, ['0', '1', '2']) + tx.rollback() + assert_browse(s, qa, ['x', 'y', 'z']) + assert_browse(s, qb, ['0', '1', '2']) + + def test_tx_sequence(self): + tx = self.connect().session(transactional=True) + notx = self.connect().session() + q = self.clear_queue('q', tx.connection, durable=True) + s = tx.sender(q) + r = tx.receiver(q) + s.send('a') + tx.commit() + assert_browse(notx, q, ['a']) + s.send('b') + tx.commit() + assert_browse(notx, q, ['a', 'b']) + self.assertEqual('a', r.fetch().content) + tx.acknowledge(); + tx.commit() + assert_browse(notx, q, ['b']) + s.send('z') + tx.rollback() + assert_browse(notx, q, ['b']) + self.assertEqual('b', r.fetch().content) + tx.acknowledge(); + tx.rollback() + assert_browse(notx, q, ['b']) + + +class CppTxTest(InteropTest): + + def test_txtest2(self): + self.popen(["qpid-txtest2"] + self.connect_opts).assert_exit_ok() + + def test_send_receive(self): + q = self.clear_queue('q', durable=True) + sender = self.popen(["qpid-send", + "--address", q, + "--messages=100", + "--tx=10", + "--durable=yes"] + self.connect_opts) + receiver = self.popen(["qpid-receive", + "--address", q, + "--messages=90", + "--timeout=10", + "--tx=10"] + self.connect_opts) + sender.assert_exit_ok() + receiver.assert_exit_ok() + expect = [long(i) for i in range(91, 101)] + sn = lambda m: m.properties["sn"] + assert_browse(self.connect().session(), q, expect, transform=sn) + + +if __name__ == "__main__": + if not BrokerTest.amqp_tx_supported: + BrokerTest.amqp_tx_warning() + print "Skipping interop_tests" + sys.exit(0) + outdir = "interop_tests.tmp" + shutil.rmtree(outdir, True) + cmd = ["qpid-python-test", "-m", "interop_tests", "-DOUTDIR=%s"%outdir] + sys.argv[1:] + if "QPID_PORT" in os.environ: del os.environ["QPID_PORT"] + if os.environ.get(URL): + os.execvp(cmd[0], cmd) + else: + dir = os.getcwd() + class StartBroker(BrokerTest): + def start_qpidd(self): pass + test = StartBroker('start_qpidd') + class Config: + def __init__(self): + self.defines = { 'OUTDIR': outdir } + test.configure(Config()) + test.setUp() + os.environ[URL] = test.broker().host_port() + os.chdir(dir) + p = subprocess.Popen(cmd) + status = p.wait() + test.tearDown() + sys.exit(status) diff --git a/qpid/cpp/src/tests/ipv6_test b/qpid/cpp/src/tests/ipv6_test new file mode 100755 index 0000000000..4ac5f95fba --- /dev/null +++ b/qpid/cpp/src/tests/ipv6_test @@ -0,0 +1,120 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Check whether we have any globally configured IPv6 addresses +# - if not then we can't run the tests because ipv6 lookups won't +# work within the qpid code. This is a deliberate feature to avoid +# getting addresses that can't be routed by the machine. + +if ip -f inet6 -o addr | cut -f 9 -s -d' ' | grep global > /dev/null ; then + echo "IPv6 addresses configured continuing" +else + echo "No global IPv6 addresses configured - skipping test" + exit 0 +fi + + +# Run a simple test over IPv6 +source $QPID_TEST_COMMON + +CONFIG=$(dirname $0)/config.null +TEST_HOSTNAME=::1 +COUNT=10 + +trap cleanup EXIT + +error() { echo $*; exit 1; } + +# Don't need --no-module-dir or --no-data-dir as they are set as env vars in test_env.sh +COMMON_OPTS="--interface [::1] --daemon --auth no --config $CONFIG" + +# Record all broker ports started +unset PORTS +declare -a PORTS + +# Start new brokers: +# $1 must be integer +# $2 = extra opts +# Append used ports to PORTS variable +start_brokers() { + local -a ports + for (( i=0; $i<$1; i++)) do + ports[$i]=$($QPIDD_EXEC --port 0 $COMMON_OPTS $2) + done + PORTS=( ${PORTS[@]} ${ports[@]} ) +} + +stop_brokers() { + for port in "${PORTS[@]}"; + do + $QPIDD_EXEC -qp $port + done + PORTS=() +} + +cleanup() { + stop_brokers +} + +start_brokers 1 +PORT=${PORTS[0]} +echo "Started IPv6 smoke perftest on broker port $PORT" + +## Test connection via connection settings +./qpid-perftest --count ${COUNT} --port ${PORT} -b $TEST_HOSTNAME --summary + +## Test connection with a URL +URL="amqp:[$TEST_HOSTNAME]:$PORT" + +./qpid-send -b $URL --content-string=hello -a "foo;{create:always}" +MSG=`./qpid-receive -b $URL -a "foo;{create:always}" --messages 1` +test "$MSG" = "hello" || { echo "receive failed '$MSG' != 'hello'"; exit 1; } + +stop_brokers + +# Federation smoke test follows + +# Start 2 brokers + +# In a distribution, the python tools will be absent. +ensure_python_tests + +start_brokers 2 +echo "Started Federated brokers on ports ${PORTS[*]}" +# Make broker urls +BROKER0="[::1]:${PORTS[0]}" +BROKER1="[::1]:${PORTS[1]}" +TEST_QUEUE=ipv6-fed-test + +$QPID_CONFIG_EXEC -b $BROKER0 add queue $TEST_QUEUE +$QPID_CONFIG_EXEC -b $BROKER1 add queue $TEST_QUEUE +$QPID_ROUTE_EXEC dynamic add $BROKER1 $BROKER0 amq.direct +$QPID_CONFIG_EXEC -b $BROKER1 bind amq.direct $TEST_QUEUE $TEST_QUEUE +$QPID_ROUTE_EXEC route map $BROKER1 + +./datagen --count 100 | tee rdata-in | + ./qpid-send -b amqp:$BROKER0 -a amq.direct/$TEST_QUEUE --content-stdin +./qpid-receive -b amqp:$BROKER1 -a $TEST_QUEUE --print-content yes -m 0 > rdata-out + +cmp rdata-in rdata-out || { echo "Federated data over IPv6 does not compare"; exit 1; } + +stop_brokers +rm rdata-in rdata-out diff --git a/qpid/cpp/src/tests/legacystore/.valgrind.supp b/qpid/cpp/src/tests/legacystore/.valgrind.supp new file mode 100644 index 0000000000..5c1c5377bf --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/.valgrind.supp @@ -0,0 +1,35 @@ +{ + <insert_a_suppression_name_here> + Memcheck:Leak + fun:_Znwm + fun:_ZNSs4_Rep9_S_createEmmRKSaIcE + fun:_ZNSs12_S_constructIPKcEEPcT_S3_RKSaIcESt20forward_iterator_tag + fun:_ZNSsC1EPKcRKSaIcE +} + +{ + <insert_a_suppression_name_here> + Memcheck:Leak + fun:_Znwm + fun:_ZNSs4_Rep9_S_createEmmRKSaIcE + fun:_ZNSs4_Rep8_M_cloneERKSaIcEm + fun:_ZNSs7reserveEm +} + +{ + <insert_a_suppression_name_here> + Memcheck:Leak + fun:_Znwm + fun:_ZNSs4_Rep9_S_createEmmRKSaIcE + fun:_ZNSs9_M_mutateEmmm + fun:_ZNSs15_M_replace_safeEmmPKcm +} + +{ + <insert_a_suppression_name_here> + Memcheck:Leak + fun:_Znwm + fun:_ZNSs4_Rep9_S_createEmmRKSaIcE + fun:_ZNSsC1IPcEET_S1_RKSaIcE +} + diff --git a/qpid/cpp/src/tests/legacystore/.valgrindrc b/qpid/cpp/src/tests/legacystore/.valgrindrc new file mode 100644 index 0000000000..4aba7661de --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/.valgrindrc @@ -0,0 +1,7 @@ +--gen-suppressions=all +--leak-check=full +--demangle=yes +--suppressions=.valgrind.supp +--num-callers=25 +--trace-children=yes + diff --git a/qpid/cpp/src/tests/legacystore/CMakeLists.txt b/qpid/cpp/src/tests/legacystore/CMakeLists.txt new file mode 100644 index 0000000000..5527f23255 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/CMakeLists.txt @@ -0,0 +1,133 @@ +# +# 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. +# + +if(BUILD_LEGACYSTORE AND BUILD_TESTING) + +message(STATUS "Building legacystore tests") + +# If we're linking Boost for DLLs, turn that on for the tests too. +if (QPID_LINK_BOOST_DYNAMIC) + add_definitions(-DBOOST_TEST_DYN_LINK) +endif (QPID_LINK_BOOST_DYNAMIC) + +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ) + +set(test_wrap ${shell} ${CMAKE_SOURCE_DIR}/src/tests/run_test${test_script_suffix} -buildDir=${CMAKE_BINARY_DIR}) + +if (BUILD_TESTING_UNITTESTS) + +# Like this to work with cmake 2.4 on Unix +set (qpid_test_boost_libs + ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} ${Boost_SYSTEM_LIBRARY}) + +# Journal tests +MACRO (define_journal_test mainSourceFile) +if ("${ARGV1}" STREQUAL "LONG") + set (testname "journal_long_${mainSourceFile}") +else () + set (testname "journal_${mainSourceFile}") +endif () +add_executable (${testname} + jrnl/${mainSourceFile} + unit_test + ${platform_test_additions}) +target_link_libraries (${testname} + ${qpid_test_boost_libs} + ${clock_gettime_LIB} legacystore_shared) +if ("${ARGV1}" STREQUAL "LONG") + set_target_properties(${testname} PROPERTIES COMPILE_DEFINITIONS LONG_TEST) +endif () +remember_location(${testname}) +add_test (${testname} ${test_wrap} -boostTest -- ${${testname}_LOCATION}) +unset (testname) +ENDMACRO (define_journal_test) + +define_journal_test (_ut_time_ns) +define_journal_test (_ut_jexception) +define_journal_test (_ut_jerrno) +define_journal_test (_ut_rec_hdr) +define_journal_test (_ut_jinf) +define_journal_test (_ut_jdir) +define_journal_test (_ut_enq_map) +define_journal_test (_ut_txn_map) +define_journal_test (_ut_lpmgr) +define_journal_test (_st_basic) +define_journal_test (_st_basic_txn) +define_journal_test (_st_read) +define_journal_test (_st_read_txn) +define_journal_test (_st_auto_expand) +define_journal_test (_ut_lpmgr LONG) +define_journal_test (_st_basic LONG) +define_journal_test (_st_read LONG) + +add_executable (jtt__ut + jrnl/jtt/_ut_data_src.cpp + jrnl/jtt/_ut_jrnl_init_params.cpp + jrnl/jtt/_ut_read_arg.cpp + jrnl/jtt/_ut_jrnl_instance.cpp + jrnl/jtt/_ut_test_case.cpp + jrnl/jtt/_ut_test_case_result.cpp + jrnl/jtt/_ut_test_case_result_agregation.cpp + jrnl/jtt/_ut_test_case_set.cpp + jrnl/jtt/args.cpp + jrnl/jtt/data_src.cpp + jrnl/jtt/jrnl_init_params.cpp + jrnl/jtt/jrnl_instance.cpp + jrnl/jtt/read_arg.cpp + jrnl/jtt/test_case.cpp + jrnl/jtt/test_case_set.cpp + jrnl/jtt/test_case_result.cpp + jrnl/jtt/test_case_result_agregation.cpp + unit_test.cpp) + +target_link_libraries (jtt__ut + ${qpid_test_boost_libs} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${clock_gettime_LIB} legacystore_shared) + +add_test(journal_jtt_ut ${test_wrap} -boostTest -workingDir=${CMAKE_CURRENT_SOURCE_DIR}/jrnl/jtt -- ${CMAKE_CURRENT_BINARY_DIR}/jtt__ut) + +endif (BUILD_TESTING_UNITTESTS) + +# +# Other test programs +# + +add_executable(jtt + jrnl/jtt/args.cpp + jrnl/jtt/data_src.cpp + jrnl/jtt/jrnl_init_params.cpp + jrnl/jtt/jrnl_instance.cpp + jrnl/jtt/main.cpp + jrnl/jtt/read_arg.cpp + jrnl/jtt/test_case.cpp + jrnl/jtt/test_case_result.cpp + jrnl/jtt/test_case_result_agregation.cpp + jrnl/jtt/test_case_set.cpp + jrnl/jtt/test_mgr.cpp) + +target_link_libraries (jtt + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${clock_gettime_LIB} legacystore_shared) + +add_test(journal_jtt ${CMAKE_CURRENT_BINARY_DIR}/jtt -c ${CMAKE_CURRENT_SOURCE_DIR}/jrnl/jtt/jtt.csv) + +add_test (legacystore_python_tests ${test_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/run_python_tests${test_script_suffix}) + +endif (BUILD_LEGACYSTORE AND BUILD_TESTING) diff --git a/qpid/cpp/src/tests/legacystore/MessageUtils.h b/qpid/cpp/src/tests/legacystore/MessageUtils.h new file mode 100644 index 0000000000..cd23244293 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/MessageUtils.h @@ -0,0 +1,105 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include <qpid/broker/Message.h> +#include <qpid/broker/Queue.h> +#include <qpid/broker/amqp_0_10/MessageTransfer.h> +#include <qpid/framing/AMQFrame.h> +#include <qpid/framing/all_method_bodies.h> +#include <qpid/framing/Uuid.h> + +using namespace qpid::broker; +using namespace qpid::framing; + +struct MessageUtils +{ + static Message createMessage(const std::string& exchange, const std::string& routingKey, + const Uuid& messageId=Uuid(), const bool durable = false, + const uint64_t contentSize = 0, const std::string& correlationId = std::string()) + { + boost::intrusive_ptr<qpid::broker::amqp_0_10::MessageTransfer> msg(new qpid::broker::amqp_0_10::MessageTransfer()); + + AMQFrame method(( MessageTransferBody(ProtocolVersion(), exchange, 0, 0))); + AMQFrame header((AMQHeaderBody())); + + msg->getFrames().append(method); + msg->getFrames().append(header); + MessageProperties* props = msg->getFrames().getHeaders()->get<MessageProperties>(true); + props->setContentLength(contentSize); + props->setMessageId(messageId); + props->setCorrelationId(correlationId); + msg->getFrames().getHeaders()->get<DeliveryProperties>(true)->setRoutingKey(routingKey); + if (durable) + msg->getFrames().getHeaders()->get<DeliveryProperties>(true)->setDeliveryMode(PERSISTENT); + return Message(msg, msg); + } + + static void addContent(Message msg, const std::string& data) + { + AMQFrame content((AMQContentBody(data))); + qpid::broker::amqp_0_10::MessageTransfer::get(msg).getFrames().append(content); + } + + struct MessageRetriever : public Consumer + { + MessageRetriever(Queue& q) : Consumer("test", CONSUMER), queue(q) {}; + + bool deliver(const QueueCursor& c, const Message& m) + { + message = m; + cursor = c; + return true; + }; + void notify() {} + void cancel() {} + void acknowledged(const DeliveryRecord&) {} + OwnershipToken* getSession() { return 0; } + + const Queue& queue; + Message message; + QueueCursor cursor; + }; + + static Message get(Queue& queue, QueueCursor* cursor = 0) + { + boost::shared_ptr<MessageRetriever> consumer(new MessageRetriever(queue)); + if (!queue.dispatch(consumer))throw qpid::Exception("No message found!"); + if (cursor) *cursor = consumer->cursor; + return consumer->message; + } + + static Uuid getMessageId(const Message& message) + { + return qpid::broker::amqp_0_10::MessageTransfer::get(message).getProperties<MessageProperties>()->getMessageId(); + } + + static std::string getCorrelationId(const Message& message) + { + return qpid::broker::amqp_0_10::MessageTransfer::get(message).getProperties<MessageProperties>()->getCorrelationId(); + } + + static void deliver(Message& msg, FrameHandler& h, uint16_t framesize) + { + qpid::broker::amqp_0_10::MessageTransfer::get(msg).sendHeader(h, framesize, false, 0, qpid::types::Variant::Map()); + qpid::broker::amqp_0_10::MessageTransfer::get(msg).sendContent(h, framesize); + } + +}; diff --git a/qpid/cpp/src/tests/legacystore/TestFramework.cpp b/qpid/cpp/src/tests/legacystore/TestFramework.cpp new file mode 100644 index 0000000000..2f7faf7682 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/TestFramework.cpp @@ -0,0 +1,30 @@ +/* + * + * 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. + * + */ + +// Defines broker to be used by tests + +#include "unit_test.h" +#include "TestFramework.h" +#include "qpid/broker/Broker.h" + +#include <iostream> + +//BOOST_GLOBAL_FIXTURE( testBroker ) diff --git a/qpid/cpp/src/tests/legacystore/TestFramework.h b/qpid/cpp/src/tests/legacystore/TestFramework.h new file mode 100644 index 0000000000..f3066db602 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/TestFramework.h @@ -0,0 +1,37 @@ +/* + * + * 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. + * + */ + +// Defines broker to be used by tests + +#include "unit_test.h" + +#include <qpid/broker/Broker.h> + +namespace { + // test broker + qpid::broker::Broker::Options opts; + qpid::broker::Broker br(opts); +/* + struct testBroker { + testBroker() {} + ~testBroker() {} + };*/ +} diff --git a/qpid/cpp/src/tests/legacystore/clean.sh b/qpid/cpp/src/tests/legacystore/clean.sh new file mode 100644 index 0000000000..838f246232 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/clean.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# This script cleans up any previous database and journal files, and should +# be run prior to the store system tests, as these are prone to crashing or +# hanging under some circumstances if the database is old or inconsistent. + +if [ -d ${TMP_DATA_DIR} ]; then + rm -rf ${TMP_DATA_DIR} +fi +if [ -d ${TMP_PYTHON_TEST_DIR} ]; then + rm -rf ${TMP_PYTHON_TEST_DIR} +fi +rm -f ${abs_srcdir}/*.vglog* diff --git a/qpid/cpp/src/tests/legacystore/federation/Makefile.am b/qpid/cpp/src/tests/legacystore/federation/Makefile.am new file mode 100644 index 0000000000..c48e861a65 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/federation/Makefile.am @@ -0,0 +1,46 @@ +# +# 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. +# + + +abs_srcdir=@abs_srcdir@ + +TMP_DATA_DIR=$(abs_srcdir)/../tmp_data_dir + +TESTS = \ + run_federation_sys_tests + +LONG_TESTS = \ + run_long_federation_sys_tests + +EXTRA_DIST = \ + federation_tests_env.sh \ + run_federation_sys_tests \ + run_long_federation_sys_tests + +TESTS_ENVIRONMENT = \ + QPID_DIR=$(QPID_DIR) \ + QPID_BLD=$(QPID_BLD) \ + TMP_DATA_DIR=$(TMP_DATA_DIR) \ + abs_srcdir=$(abs_srcdir) + +check-long: all + $(MAKE) check TESTS="$(LONG_TESTS)" SUBDIRS=. + +# END + diff --git a/qpid/cpp/src/tests/legacystore/federation/federation_tests_env.sh b/qpid/cpp/src/tests/legacystore/federation/federation_tests_env.sh new file mode 100755 index 0000000000..bf75056444 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/federation/federation_tests_env.sh @@ -0,0 +1,313 @@ +# +# 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. +# + + +# --- Function definitions --- + +func_check_required_env () +#------------------------- +# Check that EITHER: +# QPID_DIR is set (for running against svn QPID) +# OR +# QPID_PREFIX is set (for running against installed QPID +# Will exit with error code 1 if neither of these is defined. +# Params: None +# Returns: 0 if env vars ok, 1 otherwise +{ + if test -z "${QPID_DIR}" -a -z "${QPID_PREFIX}"; then + # Try to find qpidd in the normal installed location + if test -x /usr/sbin/qpidd; then + QPID_PREFIX=/usr + else + echo "ERROR: Could not find installed Qpid" + echo "Either of the following must be set in the environment for this script to run:" + echo " QPID_DIR for running against a Qpid svn build" + echo " QPID_PREFIX for running against an installed Qpid" + return 1 + fi + fi + return 0 +} + + +func_check_clustering () +#----------------------- +# Check openAIS/corosync is running and user has correct privileges +# Params: None +# Returns: 0 if openAIS/corosync is running, 1 otherwise +# Sets env var COROSYNC to 1 if corosync is running, not set otherwise +{ + # Check either aisexec or corosync is running as root + cluster_prog=`ps -u root | grep 'aisexec\|corosync'` + test -n "$cluster_prog" || NODAEMON="Neither aisexec nor corosync is running as root" + if test -z "$NODAEMON"; then + # Test for corosync running + echo $cluster_prog | grep "aisexec" > /dev/null || COROSYNC=1 + if test -n "$COROSYNC"; then + # Corosync auth test + user=`whoami` + ls /etc/corosync/uidgid.d | grep $user > /dev/null || NOAUTH="You are not authorized to use corosync." + else + # OpenAis auth test + id -nG | grep '\<ais\>' >/dev/null || NOAUTH="You are not a member of the ais group." + fi + fi + + if test -n "$NODAEMON" -o -n "$NOAUTH"; then + cat <<EOF + + ========== WARNING: NOT RUNNING CLUSTER TESTS ============ + + Cluster tests will not be run because: + + $NODAEMON + $NOAUTH + + ========================================================== + +EOF + return 1 + fi + CLUSTERING_ENABLED=1 + return 0 +} + + +func_check_qpid_python () +#------------------------ +# Check that Qpid python environment is ok +# Params: None +# Returns: 0 if Python environment is ok; 1 otherwise +{ + if ! python -c "import qpid" ; then + cat <<EOF + + =========== WARNING: PYTHON TESTS DISABLED ============== + + Unable to load python qpid module - skipping python tests. + + PYTHONPATH=${PYTHONPATH} + + =========================================================== + +EOF + return 1 + fi + return 0 +} + +func_set_python_env() +#-------------------- +# Set up the python path +# Params: None +# Returns: Nothing +{ + if test "${QPID_DIR}" -a -d "${QPID_DIR}" ; then + QPID_PYTHON=${QPID_DIR}/python + QPID_TOOLS=${QPID_DIR}/tools/src/py + QMF_LIB=${QPID_DIR}/extras/qmf/src/py + export PYTHONPATH=${QPID_PYTHON}:${QMF_LIB}:${QPID_TOOLS}:$PYTHONPATH + fi +} + +func_set_env () +#-------------- +# Set up the environment based on value of ${QPID_DIR}: if ${QPID_DIR} exists, assume a svn checkout, +# otherwise set up for an installed or prefix test. +# Params: None +# Returns: Nothing +{ + if test "${QPID_DIR}" -a -d "${QPID_DIR}" ; then + # QPID_DIR is defined for source tree builds by the --with-qpid-checkout configure option. + # QPID_BLD is defined as the build directory, either $QPID_DIR/cpp or separately specified with + # the --with-qpid-build option for VPATH builds. + + # Check QPID_BLD is also set + if test -z ${QPID_BLD}; then + QPID_BLD="${QPID_DIR}/cpp" + fi + source $QPID_BLD/src/tests/test_env.sh +# CPP_CLUSTER_EXEC="${QPID_BLD}/src/tests/cluster_test" +# PYTHON_CLUSTER_EXEC="${QPID_DIR}/cpp/src/tests/$PYTHON_TESTNAME" + FEDERATION_SYS_TESTS_FAIL="${QPID_DIR}/cpp/src/tests/federation_sys_tests.fail" + if test -z ${STORE_LIB}; then + STORE_LIB="../../lib/.libs/msgstore.so" + fi +# export STORE_ENABLE=1 + else + # Set up the environment based on value of ${QPID_PREFIX} for testing against an installed qpid + # Alternatively, make sure ${QPID_BIN_DIR}, ${QPID_SBIN_DIR}, ${QPID_LIB_DIR} and ${QPID_LIBEXEC_DIR} are set for + # the installed location. + if test "${QPID_PREFIX}" -a -d "${QPID_PREFIX}" ; then + QPID_BIN_DIR=${QPID_PREFIX}/bin + QPID_SBIN_DIR=${QPID_PREFIX}/sbin + QPID_LIB_DIR=${QPID_PREFIX}/lib + QPID_LIBEXEC_DIR=${QPID_PREFIX}/libexec + export PATH="$QPID_BIN_DIR:$QPID_SBIN_DIR:$QPID_LIBEXEC_DIR/qpid/tests:$PATH" + fi + + # These four env vars must be set prior to calling this script + func_checkpaths QPID_BIN_DIR QPID_SBIN_DIR QPID_LIB_DIR QPID_LIBEXEC_DIR + + # Paths and dirs + export PYTHON_DIR="${QPID_BIN_DIR}" + export PYTHONPATH="${QPID_LIB_DIR}/python:${QPID_LIBEXEC_DIR}/qpid/tests:${QPID_LIB_DIR}/python2.4:${QPID_LIB_DIR}/python2.4/site-packages:${PYTHONPATH}" + # Libraries + export CLUSTER_LIB="${QPID_LIB_DIR}/qpid/daemon/cluster.so" + export ACL_LIB="${QPID_LIB_DIR}/qpid/daemon/acl.so" + export TEST_STORE_LIB="${QPID_LIB_DIR}/qpid/tests/test_store.so" + + # Executables +# CPP_CLUSTER_EXEC="${QPID_LIBEXEC_DIR}/qpid/tests/cluster_test" +# PYTHON_CLUSTER_EXEC="${QPID_LIBEXEC_DIR}/qpid/tests/$PYTHON_TESTNAME" + export QPIDD_EXEC="${QPID_SBIN_DIR}/qpidd" + export QPID_CONFIG_EXEC="${QPID_BIN_DIR}/qpid-config" + export QPID_ROUTE_EXEC="${QPID_BIN_DIR}/qpid-route" + export QPID_CLUSTER_EXEC="${QPID_BIN_DIR}/qpid-cluster" +# export RECEIVER_EXEC="${QPID_LIBEXEC_DIR}/qpid/tests/receiver" +# export SENDER_EXEC="${QPID_LIBEXEC_DIR}/qpid/tests/sender" + export QPID_PYTHON_TEST="${QPID_BIN_DIR}/qpid-python-test" + + # Data + FEDERATION_SYS_TESTS_FAIL="${QPID_LIBEXEC_DIR}/qpid/tests/federation_sys_tests.fail" + fi +} + + +func_mk_data_dir () +#------------------ +# Create a data dir at ${TMP_DATA_DIR} if not present, clear it otherwise. +# Set TMP_DATA_DIR if it is not set. +# Params: None +# Returns: Nothing +{ + if test -z "${TMP_DATA_DIR}"; then + TMP_DATA_DIR=/tmp/federation_sys_tests + echo "TMP_DATA_DIR not set; using ${TMP_DATA_DIR}" + fi + + # Delete old cluster test dirs if they exist + if test -d "${TMP_DATA_DIR}" ; then + rm -rf "${TMP_DATA_DIR}/cluster" + fi + mkdir -p "${TMP_DATA_DIR}/cluster" + export TMP_DATA_DIR +} + + +func_checkvar () +#--------------- +# Check that an environment var is set (ie non-zero length) +# Params: $1 - env var to be checked +# Returns: 0 = env var is set (ie non-zero length) +# 1 = env var is not set +{ + local loc_VAR=$1 + if test -z ${!loc_VAR}; then + echo "WARNING: environment variable ${loc_VAR} not set." + return 1 + fi + return 0 +} + + +func_checkpaths () +#----------------- +# Check a list of paths (each can contain ':'-separated sub-list) is set and valid (ie each path exists as a dir) +# Params: $@ - List of path env vars to be checked +# Returns: Nothing +{ + local loc_PATHS=$@ + for path in ${loc_PATHS}; do + func_checkvar ${path} + if test $? == 0; then + local temp_IFS=${IFS} + IFS=":" + local pl=${!path} + for p in ${pl[@]}; do + if test ! -d ${p}; then + echo "WARNING: Directory ${p} in var ${path} not found." + fi + done + IFS=${temp_IFS} + fi + done +} + + +func_checklibs () +#---------------- +# Check that a list of libs is set and valid (ie each lib exists as an executable file) +# Params: $@ - List of lib values to be checked +# Returns: Nothing +{ + local loc_LIBS=$@ + for lib in ${loc_LIBS[@]}; do + func_checkvar ${lib} + if test $? == 0; then + if test ! -x ${!lib}; then + echo "WARNING: Library ${lib}=${!lib} not found." + fi + fi + done +} + + +func_checkexecs () +#----------------- +# Check that a list of executable is set and valid (ie each exec exists as an executable file) +# Params: $@ - List of exec values to be checked +# Returns: Nothing +{ + local loc_EXECS=$@ + for exec in ${loc_EXECS[@]}; do + func_checkvar ${exec} + if test $? == 0; then + if test ! -x ${!exec}; then + echo "WARNING: Executable ${exec}=${!exec} not found or is not executable." + fi + fi + done +} + + +#--- Start of script --- + +func_set_python_env +func_check_required_env || exit 1 # Cannot run, exit with error +func_check_qpid_python || exit 1 # Cannot run, exit with error +func_check_clustering # Warning + +PYTHON_TESTNAME=federation_sys.py +func_set_env +func_mk_data_dir + +# Check expected environment vars are set +func_checkpaths PYTHON_DIR PYTHONPATH TMP_DATA_DIR +func_checklibs CLUSTER_LIB STORE_LIB +func_checkexecs QPIDD_EXEC QPID_CONFIG_EXEC QPID_ROUTE_EXEC QPID_PYTHON_TEST + +FAILING_PYTHON_TESTS="${abs_srcdir}/../failing_python_tests.txt" +if test -z $1; then + FEDERATION_SYS_TEST="${QPID_PYTHON_TEST} -m cluster_tests -I ${FEDERATION_SYS_TESTS_FAIL}" +else + FEDERATION_SYS_TEST="${QPID_PYTHON_TEST} -m cluster_tests -I ${FEDERATION_SYS_TESTS_FAIL} cluster_tests.LongTests.*" + LONG_TEST=1 +fi + diff --git a/qpid/cpp/src/tests/legacystore/federation/run_federation_sys_tests b/qpid/cpp/src/tests/legacystore/federation/run_federation_sys_tests new file mode 100755 index 0000000000..776f009c05 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/federation/run_federation_sys_tests @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + + +# Run the federation tests. +source ${abs_srcdir}/federation_tests_env.sh + +MODULENAME=federation_sys + +# Test for long test +if [[ "$1" == "LONG_TEST" ]]; then + USE_LONG_TEST=1 + shift # get rid of this param so it is not treated as a test name +fi + +trap stop_brokers INT TERM QUIT + +MODULES="--load-module ${STORE_LIB} --jfile-size 12 --num-jfiles 4" +CLUSTER_MODULE="--load-module ${CLUSTER_LIB} " +if [ -z ${USE_LONG_TEST} ]; then + SKIPTESTS="-i federation_sys.A_Long* -i federation_sys.B_Long* -i federation_sys.E_Long* -i federation_sys.F_Long*" +fi +if [ -z ${CLUSTERING_ENABLED} ]; then + SKIPTESTS="${SKIPTESTS} -i federation_sys.C_* -i federation_sys.D_* -i federation_sys.G_* -i federation_sys.H_*" +elif [ -z ${USE_LONG_TEST} ]; then + SKIPTESTS="${SKIPTESTS} -i federation_sys.C_Long* -i federation_sys.D_Long* -i federation_sys.G_Long* -i federation_sys.H_Long*" +fi + +start_brokers() { + clean_or_create_dir() { + if [ -n "$1" -a -d $1 ]; then + rm -rf $1/* + else + mkdir -p $1 + fi + } + start_broker() { + clean_or_create_dir $1 + ${QPIDD_EXEC} --daemon --port 0 --auth no --data-dir $1 $2 > qpidd.port + PORT=`cat qpidd.port` + eval "$3=${PORT}" + } + start_broker ${TMP_DATA_DIR}/local "${MODULES} --log-enable info+ --log-to-file ${TMP_DATA_DIR}/qpidd.log.local" LOCAL_PORT + start_broker ${TMP_DATA_DIR}/remote "${MODULES} --log-enable info+ --log-to-file ${TMP_DATA_DIR}/qpidd.log.remote" REMOTE_PORT + if [ -n "$CLUSTERING_ENABLED" ]; then + start_broker ${TMP_DATA_DIR}/cluster/c1.1 "${MODULES} ${CLUSTER_MODULE} --cluster-name test-cluster-1 --log-enable info+ --log-to-file ${TMP_DATA_DIR}/qpidd.log.cluster1.1" CLUSTER_C1_1 + start_broker ${TMP_DATA_DIR}/cluster/c1.2 "${MODULES} ${CLUSTER_MODULE} --cluster-name test-cluster-1 --log-enable info+ --log-to-file ${TMP_DATA_DIR}/qpidd.log.cluster1.2" CLUSTER_C1_2 + start_broker ${TMP_DATA_DIR}/cluster/c2.1 "${MODULES} ${CLUSTER_MODULE} --cluster-name test-cluster-2 --log-enable info+ --log-to-file ${TMP_DATA_DIR}/qpidd.log.cluster2.1" CLUSTER_C2_1 + start_broker ${TMP_DATA_DIR}/cluster/c2.2 "${MODULES} ${CLUSTER_MODULE} --cluster-name test-cluster-2 --log-enable info+ --log-to-file ${TMP_DATA_DIR}/qpidd.log.cluster2.2" CLUSTER_C2_2 + fi + rm qpidd.port +} + +stop_brokers() { + ${QPIDD_EXEC} -q --port ${LOCAL_PORT} + ${QPIDD_EXEC} -q --port ${REMOTE_PORT} + if [ -n "${CLUSTERING_ENABLED}" ]; then + ${QPID_CLUSTER_EXEC} --all-stop --force localhost:${CLUSTER_C1_1} + ${QPID_CLUSTER_EXEC} --all-stop --force localhost:${CLUSTER_C2_1} + fi +} + +if test -d ${PYTHON_DIR} ; then + start_brokers + if [ -z ${CLUSTERING_ENABLED} ]; then + echo "Running federation tests using brokers on local port ${LOCAL_PORT}, remote port ${REMOTE_PORT} (NOTE: clustering is DISABLED)" + else + echo "Running federation tests using brokers on local port ${LOCAL_PORT}, remote port ${REMOTE_PORT}, local cluster nodes ${CLUSTER_C1_1} ${CLUSTER_C1_2}, remote cluster nodes ${CLUSTER_C2_1} ${CLUSTER_C2_2}" + fi + if [ -z ${USE_LONG_TEST} ]; then + echo "NOTE: To run a full set of federation system tests, use \"make check-long\"." + fi + ${QPID_PYTHON_TEST} -m ${MODULENAME} ${SKIPTESTS} -b localhost:$REMOTE_PORT -Dlocal-port=$LOCAL_PORT -Dremote-port=$REMOTE_PORT -Dlocal-cluster-ports="$CLUSTER_C1_1 $CLUSTER_C1_2" -Dremote-cluster-ports="$CLUSTER_C2_1 $CLUSTER_C2_2" $@ + RETCODE=$? + stop_brokers + if test x$RETCODE != x0; then + echo "FAIL federation tests"; exit 1; + fi +fi diff --git a/qpid/cpp/src/tests/legacystore/federation/run_long_federation_sys_tests b/qpid/cpp/src/tests/legacystore/federation/run_long_federation_sys_tests new file mode 100755 index 0000000000..012c8d8f18 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/federation/run_long_federation_sys_tests @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + + +# Run the federation system tests (long version). + +./run_federation_sys_tests LONG_TEST $@ diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_st_auto_expand.cpp b/qpid/cpp/src/tests/legacystore/jrnl/_st_auto_expand.cpp new file mode 100644 index 0000000000..fb5c1f1742 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/_st_auto_expand.cpp @@ -0,0 +1,140 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "../unit_test.h" +#include <cmath> +#include <iostream> +#include "qpid/legacystore/jrnl/jcntl.h" + +using namespace boost::unit_test; +using namespace mrg::journal; +using namespace std; + +QPID_AUTO_TEST_SUITE(journal_auto_expand) + +const string test_filename("_st_auto_expand"); + +#include "_st_helper_fns.h" + +// === Test suite === + +QPID_AUTO_TEST_CASE(no_ae_threshold) +{ + string test_name = get_test_name(test_filename, "no_ae_threshold"); + try + { + string msg; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_DEFAULT_JFILES, false, 0, DEFAULT_JFSIZE_SBLKS); + unsigned m; + + // Fill journal to just below threshold + u_int32_t t = num_msgs_to_threshold(NUM_DEFAULT_JFILES, + DEFAULT_JFSIZE_SBLKS * JRNL_SBLK_SIZE, LARGE_MSG_REC_SIZE_DBLKS); + for (m=0; m<t; m++) + enq_msg(jc, m, create_msg(msg, m, LARGE_MSG_SIZE), false); + // This enqueue should exceed the threshold + BOOST_CHECK_EQUAL(jc.get_enq_cnt(), t); + enq_msg(jc, m, create_msg(msg, m, LARGE_MSG_SIZE), false, RHM_IORES_ENQCAPTHRESH); + BOOST_CHECK_EQUAL(jc.get_enq_cnt(), t); + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(no_ae_threshold_dequeue_some) +{ + string test_name = get_test_name(test_filename, "no_ae_threshold_dequeue_some"); + try + { + string msg; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_DEFAULT_JFILES, false, 0, DEFAULT_JFSIZE_SBLKS); + unsigned m; + + // Fill journal to just below threshold + u_int32_t t = num_msgs_to_threshold(NUM_DEFAULT_JFILES, + DEFAULT_JFSIZE_SBLKS * JRNL_SBLK_SIZE, LARGE_MSG_REC_SIZE_DBLKS); + for (m=0; m<t; m++) + enq_msg(jc, m, create_msg(msg, m, LARGE_MSG_SIZE), false); + // This enqueue should exceed the threshold + BOOST_CHECK_EQUAL(jc.get_enq_cnt(), t); + enq_msg(jc, m, create_msg(msg, m, LARGE_MSG_SIZE), false, RHM_IORES_ENQCAPTHRESH); + BOOST_CHECK_EQUAL(jc.get_enq_cnt(), t); + + // Dequeue 25 msgs + #define NUM_MSGS_DEQ 25 + for (m=0; m<NUM_MSGS_DEQ; m++) + deq_msg(jc, m, m+t); + BOOST_CHECK_EQUAL(jc.get_enq_cnt(), u_int32_t(t-NUM_MSGS_DEQ)); + + // Check we can still enqueue and dequeue + for (m=t+NUM_MSGS_DEQ; m<t+NUM_MSGS_DEQ+NUM_MSGS; m++) + enq_msg(jc, m, create_msg(msg, m, LARGE_MSG_SIZE), false); + for (m=t+NUM_MSGS_DEQ; m<t+NUM_MSGS_DEQ+NUM_MSGS; m++) + deq_msg(jc, m, m+NUM_MSGS_DEQ+NUM_MSGS); + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(no_ae_threshold_dequeue_all) +{ + string test_name = get_test_name(test_filename, "no_ae_threshold_dequeue_all"); + try + { + string msg; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_DEFAULT_JFILES, false, 0, DEFAULT_JFSIZE_SBLKS); + unsigned m; + + // Fill journal to just below threshold + u_int32_t t = num_msgs_to_threshold(NUM_DEFAULT_JFILES, + DEFAULT_JFSIZE_SBLKS * JRNL_SBLK_SIZE, LARGE_MSG_REC_SIZE_DBLKS); + for (m=0; m<t; m++) + enq_msg(jc, m, create_msg(msg, m, LARGE_MSG_SIZE), false); + // This enqueue should exceed the threshold + BOOST_CHECK_EQUAL(jc.get_enq_cnt(), t); + enq_msg(jc, m, create_msg(msg, m, LARGE_MSG_SIZE), false, RHM_IORES_ENQCAPTHRESH); + BOOST_CHECK_EQUAL(jc.get_enq_cnt(), t); + + // Dequeue all msgs + for (m=0; m<t; m++) + deq_msg(jc, m, m+t); + BOOST_CHECK_EQUAL(jc.get_enq_cnt(), u_int32_t(0)); + + // Check we can still enqueue and dequeue + for (m=2*t; m<2*t + NUM_MSGS; m++) + enq_msg(jc, m, create_msg(msg, m, LARGE_MSG_SIZE), false); + for (m=2*t; m<2*t + NUM_MSGS; m++) + deq_msg(jc, m, m+2*t+NUM_MSGS); + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_st_basic.cpp b/qpid/cpp/src/tests/legacystore/jrnl/_st_basic.cpp new file mode 100644 index 0000000000..4aa6d2e29f --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/_st_basic.cpp @@ -0,0 +1,558 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "../unit_test.h" +#include <cmath> +#include <iostream> +#include "qpid/legacystore/jrnl/jcntl.h" + +using namespace boost::unit_test; +using namespace mrg::journal; +using namespace std; + +QPID_AUTO_TEST_SUITE(journal_basic) + +const string test_filename("_st_basic"); + +#include "_st_helper_fns.h" + +// === Test suite === + +#ifndef LONG_TEST +/* + * ============================================== + * NORMAL TESTS + * This section contains normal "make check" tests + * for building/packaging. These are built when + * LONG_TEST is _not_ defined. + * ============================================== + */ + +QPID_AUTO_TEST_CASE(instantiation) +{ + string test_name = get_test_name(test_filename, "instantiation"); + try + { + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + BOOST_CHECK_EQUAL(jc.is_ready(), false); + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(initialization) +{ + string test_name = get_test_name(test_filename, "initialization"); + try + { + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + BOOST_CHECK_EQUAL(jc.is_ready(), false); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + BOOST_CHECK_EQUAL(jc.is_ready(), true); + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(enqueue_dequeue_block) +{ + string test_name = get_test_name(test_filename, "enqueue_dequeue_block"); + try + { + string msg; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + for (int m=0; m<NUM_MSGS; m++) + BOOST_CHECK_EQUAL(enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false), u_int64_t(m)); + for (int m=0; m<NUM_MSGS; m++) + deq_msg(jc, m, m+NUM_MSGS); + + // Again... + for (int m=2*NUM_MSGS; m<3*NUM_MSGS; m++) + BOOST_CHECK_EQUAL(enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false), u_int64_t(m)); + for (int m=2*NUM_MSGS; m<3*NUM_MSGS; m++) + deq_msg(jc, m, m+3*NUM_MSGS); + + // Disjoint rids + for (int m=10*NUM_MSGS; m<11*NUM_MSGS; m++) + BOOST_CHECK_EQUAL(enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false), u_int64_t(m)); + for (int m=10*NUM_MSGS; m<11*NUM_MSGS; m++) + deq_msg(jc, m, m+11*NUM_MSGS); + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(enqueue_dequeue_interleaved) +{ + string test_name = get_test_name(test_filename, "enqueue_dequeue_interleaved"); + try + { + string msg; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + for (int m=0; m<2*NUM_MSGS; m+=2) + { + BOOST_CHECK_EQUAL(enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false), u_int64_t(m)); + deq_msg(jc, m, m+1); + } + + // Again... + for (int m=2*NUM_MSGS; m<4*NUM_MSGS; m+=2) + { + BOOST_CHECK_EQUAL(enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false), u_int64_t(m)); + deq_msg(jc, m, m+1); + } + + // Disjoint rids + for (int m=10*NUM_MSGS; m<12*NUM_MSGS; m+=2) + { + BOOST_CHECK_EQUAL(enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false), u_int64_t(m)); + deq_msg(jc, m, m+1); + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(enqueue_dequeue_interleaved_file_rollover) +{ + string test_name = get_test_name(test_filename, "enqueue_dequeue_interleaved_file_rollover"); + try + { + string msg; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + unsigned n = num_msgs_to_full(NUM_TEST_JFILES, TEST_JFSIZE_SBLKS * JRNL_SBLK_SIZE, + 16*MSG_REC_SIZE_DBLKS, true); + for (unsigned m=0; m<3*2*n; m+=2) // overwrite files 3 times + { + enq_msg(jc, m, create_msg(msg, m, 16*MSG_SIZE), false); + deq_msg(jc, m, m+1); + } + jc.stop(true); + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(empty_recover) +{ + string test_name = get_test_name(test_filename, "empty_recover"); + try + { + { + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + BOOST_CHECK_EQUAL(jc.is_ready(), false); + BOOST_CHECK_EQUAL(jc.is_read_only(), false); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + BOOST_CHECK_EQUAL(jc.is_ready(), true); + BOOST_CHECK_EQUAL(jc.is_read_only(), false); + } + { + u_int64_t hrid; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + BOOST_CHECK_EQUAL(jc.is_ready(), false); + BOOST_CHECK_EQUAL(jc.is_read_only(), false); + jc.recover(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS, 0, hrid); + BOOST_CHECK_EQUAL(jc.is_ready(), true); + BOOST_CHECK_EQUAL(jc.is_read_only(), true); + BOOST_CHECK_EQUAL(hrid, u_int64_t(0)); + } + { + u_int64_t hrid; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + BOOST_CHECK_EQUAL(jc.is_ready(), false); + BOOST_CHECK_EQUAL(jc.is_read_only(), false); + jc.recover(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS, 0, hrid); + BOOST_CHECK_EQUAL(jc.is_ready(), true); + BOOST_CHECK_EQUAL(jc.is_read_only(), true); + BOOST_CHECK_EQUAL(hrid, u_int64_t(0)); + jc.recover_complete(); + BOOST_CHECK_EQUAL(jc.is_ready(), true); + BOOST_CHECK_EQUAL(jc.is_read_only(), false); + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(enqueue_recover_dequeue_block) +{ + string test_name = get_test_name(test_filename, "enqueue_recover_dequeue_block"); + try + { + { + string msg; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + for (int m=0; m<NUM_MSGS; m++) + enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false); + } + { + u_int64_t hrid; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.recover(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS, 0, hrid); + BOOST_CHECK_EQUAL(hrid, u_int64_t(NUM_MSGS - 1)); + jc.recover_complete(); + for (int m=0; m<NUM_MSGS; m++) + deq_msg(jc, m, m+NUM_MSGS); + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(enqueue_recover_dequeue_interleaved) +{ + string test_name = get_test_name(test_filename, "enqueue_recover_dequeue_interleaved"); + try + { + string msg; + u_int64_t hrid; + + for (int m=0; m<2*NUM_MSGS; m+=2) + { + { + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + if (m == 0) + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); // First time only + else + { + jc.recover(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS, 0, hrid); + BOOST_CHECK_EQUAL(hrid, u_int64_t(m - 1)); + jc.recover_complete(); + } + enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false); + } + { + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.recover(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS, 0, hrid); + BOOST_CHECK_EQUAL(hrid, u_int64_t(m)); + jc.recover_complete(); + deq_msg(jc, m, m+1); + } + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(header_flags) +{ + string test_name = get_test_name(test_filename, "header_flags"); + try + { + { + string msg; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + // Transient msgs - should not recover + for (int m=0; m<NUM_MSGS; m++) + enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), true); + // Persistent msgs + for (int m=NUM_MSGS; m<NUM_MSGS*2; m++) + enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false); + // Transient extern msgs - should not recover + for (int m=NUM_MSGS*2; m<NUM_MSGS*3; m++) + enq_extern_msg(jc, m, MSG_SIZE, true); + // Persistnet extern msgs + for (int m=NUM_MSGS*3; m<NUM_MSGS*4; m++) + enq_extern_msg(jc, m, MSG_SIZE, false); + } + { + string msg; + u_int64_t hrid; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.recover(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS, 0, hrid); + // Recover non-transient msgs + for (int m=NUM_MSGS; m<NUM_MSGS*2; m++) + { + string rmsg; + string xid; + bool transientFlag; + bool externalFlag; + + read_msg(jc, rmsg, xid, transientFlag, externalFlag); + BOOST_CHECK_MESSAGE(transientFlag == false, "Transient message recovered."); + BOOST_CHECK_MESSAGE(externalFlag == false, "External flag incorrect."); + BOOST_CHECK_MESSAGE(create_msg(msg, m, MSG_SIZE).compare(rmsg) == 0, + "Non-transient message corrupt during recover."); + } + // Recover non-transient extern msgs + for (int m=NUM_MSGS*3; m<NUM_MSGS*4; m++) + { + string rmsg; + string xid; + bool transientFlag; + bool externalFlag; + + read_msg(jc, rmsg, xid, transientFlag, externalFlag); + BOOST_CHECK_MESSAGE(transientFlag == false, "Transient message recovered."); + BOOST_CHECK_MESSAGE(externalFlag == true, "External flag incorrect."); + BOOST_CHECK_MESSAGE(rmsg.size() == 0, "External message returned non-zero size."); + } + jc.recover_complete(); + // Read recovered non-transient msgs + for (int m=NUM_MSGS; m<NUM_MSGS*2; m++) + { + string rmsg; + string xid; + bool transientFlag; + bool externalFlag; + + read_msg(jc, rmsg, xid, transientFlag, externalFlag); + BOOST_CHECK_MESSAGE(transientFlag == false, "Transient message recovered."); + BOOST_CHECK_MESSAGE(externalFlag == false, "External flag incorrect."); + BOOST_CHECK_MESSAGE(create_msg(msg, m, MSG_SIZE).compare(rmsg) == 0, + "Non-transient message corrupt during recover."); + } + // Read recovered non-transient extern msgs + for (int m=NUM_MSGS*3; m<NUM_MSGS*4; m++) + { + string rmsg; + string xid; + bool transientFlag; + bool externalFlag; + + read_msg(jc, rmsg, xid, transientFlag, externalFlag); + BOOST_CHECK_MESSAGE(transientFlag == false, "Transient message recovered."); + BOOST_CHECK_MESSAGE(externalFlag == true, "External flag incorrect."); + BOOST_CHECK_MESSAGE(rmsg.size() == 0, "External message returned non-zero size."); + } + // Dequeue recovered messages + for (int m=NUM_MSGS; m<NUM_MSGS*2; m++) + deq_msg(jc, m, m+3*NUM_MSGS); + for (int m=NUM_MSGS*3; m<NUM_MSGS*4; m++) + deq_msg(jc, m, m+2*NUM_MSGS); + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(double_dequeue) +{ + string test_name = get_test_name(test_filename, "double_dequeue"); + try + { + string msg; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + enq_msg(jc, 0, create_msg(msg, 0, MSG_SIZE), false); + deq_msg(jc, 0, 1); + try{ deq_msg(jc, 0, 2); BOOST_ERROR("Did not throw exception on second dequeue."); } + catch (const jexception& e){ BOOST_CHECK_EQUAL(e.err_code(), jerrno::JERR_WMGR_DEQRIDNOTENQ); } + enq_msg(jc, 2, create_msg(msg, 1, MSG_SIZE), false); + deq_msg(jc, 2, 3); + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +#else +/* + * ============================================== + * LONG TESTS + * This section contains long tests and soak tests, + * and are run using target check-long (ie "make + * check-long"). These are built when LONG_TEST is + * defined. + * ============================================== + */ + +QPID_AUTO_TEST_CASE(journal_overflow) +{ + string test_name = get_test_name(test_filename, "journal_overflow"); + try + { + string msg; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_DEFAULT_JFILES, false, 0, DEFAULT_JFSIZE_SBLKS); + unsigned m; + + // Fill journal to just below threshold + u_int32_t t = num_msgs_to_threshold(NUM_DEFAULT_JFILES, DEFAULT_JFSIZE_SBLKS * JRNL_SBLK_SIZE, + MSG_REC_SIZE_DBLKS); + u_int32_t d = num_dequeues_rem(NUM_DEFAULT_JFILES, DEFAULT_JFSIZE_SBLKS * JRNL_SBLK_SIZE); + for (m=0; m<t; m++) + enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false); + // This enqueue should exceed the threshold + enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false, RHM_IORES_ENQCAPTHRESH); + + // Dequeue as many msgs as possible except first + for (m=1; m<=d; m++) + deq_msg(jc, m, m+t); + deq_msg(jc, d+1, d+2, RHM_IORES_FULL); + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(file_cycle_block) +{ + string test_name = get_test_name(test_filename, "file_cycle_block"); + try + { + string msg; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_DEFAULT_JFILES, false, 0, DEFAULT_JFSIZE_SBLKS); + + // 5 cycles of enqueue/dequeue blocks of half threshold exception size + u_int32_t t = num_msgs_to_threshold(NUM_DEFAULT_JFILES, DEFAULT_JFSIZE_SBLKS * JRNL_SBLK_SIZE, + LARGE_MSG_REC_SIZE_DBLKS)/2; + for (unsigned i=0; i<5; i++) + { + for (unsigned m=2*i*t; m<(2*i+1)*t; m++) + enq_msg(jc, m, create_msg(msg, m, LARGE_MSG_SIZE), false); + for (unsigned m=2*i*t; m<(2*i+1)*t; m++) + deq_msg(jc, m, m+t); + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(file_cycle_interleaved) +{ + string test_name = get_test_name(test_filename, "file_cycle_interleaved"); + try + { + string msg; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_DEFAULT_JFILES, false, 0, DEFAULT_JFSIZE_SBLKS); + + // 5 cycles of enqueue/dequeue blocks of half threshold exception size + u_int32_t t = num_msgs_to_threshold(NUM_DEFAULT_JFILES, DEFAULT_JFSIZE_SBLKS * JRNL_SBLK_SIZE, + LARGE_MSG_REC_SIZE_DBLKS)/2; + for (unsigned m=0; m<5*2*t; m+=2) + { + enq_msg(jc, m, create_msg(msg, m, LARGE_MSG_SIZE), false); + deq_msg(jc, m, m+1); + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(recover_file_cycle_block) +{ + string test_name = get_test_name(test_filename, "recover_file_cycle_block"); + try + { + string msg; + u_int64_t hrid; + + // 5 cycles of enqueue/dequeue blocks of half threshold exception size + u_int32_t t = num_msgs_to_threshold(NUM_DEFAULT_JFILES, DEFAULT_JFSIZE_SBLKS * JRNL_SBLK_SIZE, + LARGE_MSG_REC_SIZE_DBLKS)/2; + for (unsigned i=0; i<5; i++) + { + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + if (i) + { + jc.recover(NUM_DEFAULT_JFILES, false, 0, DEFAULT_JFSIZE_SBLKS, 0, hrid); + BOOST_CHECK_EQUAL(hrid, u_int64_t(2*i*t - 1)); + jc.recover_complete(); + } + else + jc.initialize(NUM_DEFAULT_JFILES, false, 0, DEFAULT_JFSIZE_SBLKS); + + for (unsigned m=2*i*t; m<(2*i+1)*t; m++) + enq_msg(jc, m, create_msg(msg, m, LARGE_MSG_SIZE), false); + for (unsigned m=2*i*t; m<(2*i+1)*t; m++) + deq_msg(jc, m, m+t); + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(recover_file_cycle_interleaved) +{ + string test_name = get_test_name(test_filename, "recover_file_cycle_interleaved"); + try + { + string msg; + u_int64_t hrid; + + // 5 cycles of enqueue/dequeue blocks of half threshold exception size + u_int32_t t = num_msgs_to_threshold(NUM_DEFAULT_JFILES, DEFAULT_JFSIZE_SBLKS * JRNL_SBLK_SIZE, + LARGE_MSG_REC_SIZE_DBLKS)/2; + for (unsigned i=0; i<5; i++) + { + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + if (i) + { + jc.recover(NUM_DEFAULT_JFILES, false, 0, DEFAULT_JFSIZE_SBLKS, 0, hrid); + BOOST_CHECK_EQUAL(hrid, u_int64_t(2*i*t - 1)); + jc.recover_complete(); + } + else + jc.initialize(NUM_DEFAULT_JFILES, false, 0, DEFAULT_JFSIZE_SBLKS); + + for (unsigned m=2*i*t; m<2*(i+1)*t; m+=2) + { + enq_msg(jc, m, create_msg(msg, m, LARGE_MSG_SIZE), false); + deq_msg(jc, m, m+1); + } + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +#endif + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_st_basic_txn.cpp b/qpid/cpp/src/tests/legacystore/jrnl/_st_basic_txn.cpp new file mode 100644 index 0000000000..aa2d31c2ae --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/_st_basic_txn.cpp @@ -0,0 +1,239 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "../unit_test.h" +#include <cmath> +#include <iostream> +#include "qpid/legacystore/jrnl/jcntl.h" + +using namespace boost::unit_test; +using namespace mrg::journal; +using namespace std; + +QPID_AUTO_TEST_SUITE(journal_basic_txn) + +const string test_filename("_st_basic_txn"); + +#include "_st_helper_fns.h" + +// === Test suite === + +QPID_AUTO_TEST_CASE(enqueue_commit_dequeue_block) +{ + string test_name = get_test_name(test_filename, "enqueue_commit_dequeue_block"); + try + { + string msg; + string xid; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + create_xid(xid, 0, XID_SIZE); + for (int m=0; m<NUM_MSGS; m++) + BOOST_CHECK_EQUAL(enq_txn_msg(jc, m, create_msg(msg, m, MSG_SIZE), xid, false), u_int64_t(m)); + txn_commit(jc, NUM_MSGS, xid); + for (int m=0; m<NUM_MSGS; m++) + deq_msg(jc, m, m+NUM_MSGS+1); + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(enqueue_abort_dequeue_block) +{ + string test_name = get_test_name(test_filename, "enqueue_abort_dequeue_block"); + try + { + string msg; + string xid; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + create_xid(xid, 0, XID_SIZE); + for (int m=0; m<NUM_MSGS; m++) + BOOST_CHECK_EQUAL(enq_txn_msg(jc, m, create_msg(msg, m, MSG_SIZE), xid, false), u_int64_t(m)); + txn_abort(jc, NUM_MSGS, xid); + for (int m=0; m<NUM_MSGS; m++) + { + try + { + deq_msg(jc, m, m+NUM_MSGS+1); + BOOST_ERROR("Expected dequeue to fail with exception JERR_WMGR_DEQRIDNOTENQ."); + } + catch (const jexception& e) { if (e.err_code() != jerrno::JERR_WMGR_DEQRIDNOTENQ) throw; } + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(enqueue_commit_dequeue_interleaved) +{ + string test_name = get_test_name(test_filename, "enqueue_commit_dequeue_interleaved"); + try + { + string msg; + string xid; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + for (int m=0; m<NUM_MSGS; m++) + { + create_xid(xid, m, XID_SIZE); + BOOST_CHECK_EQUAL(enq_txn_msg(jc, 3*m, create_msg(msg, m, MSG_SIZE), xid, false), u_int64_t(3*m)); + txn_commit(jc, 3*m+1, xid); + deq_msg(jc, 3*m, 3*m+2); + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(enqueue_abort_dequeue_interleaved) +{ + string test_name = get_test_name(test_filename, "enqueue_abort_dequeue_interleaved"); + try + { + string msg; + string xid; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + for (int m=0; m<NUM_MSGS; m++) + { + create_xid(xid, m, XID_SIZE); + BOOST_CHECK_EQUAL(enq_txn_msg(jc, 3*m, create_msg(msg, m, MSG_SIZE), xid, false), u_int64_t(3*m)); + txn_abort(jc, 3*m+1, xid); + try + { + deq_msg(jc, 2*m, 2*m+2); + BOOST_ERROR("Expected dequeue to fail with exception JERR_WMGR_DEQRIDNOTENQ."); + } + catch (const jexception& e) { if (e.err_code() != jerrno::JERR_WMGR_DEQRIDNOTENQ) throw; } + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(enqueue_dequeue_commit_block) +{ + string test_name = get_test_name(test_filename, "enqueue_dequeue_commit_block"); + try + { + string msg; + string xid; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + create_xid(xid, 0, XID_SIZE); + for (int m=0; m<NUM_MSGS; m++) + BOOST_CHECK_EQUAL(enq_txn_msg(jc, m, create_msg(msg, m, MSG_SIZE), xid, false), u_int64_t(m)); + BOOST_CHECK_EQUAL(jc.get_enq_cnt(), u_int32_t(0)); + for (int m=0; m<NUM_MSGS; m++) + deq_txn_msg(jc, m, m+NUM_MSGS, xid); + BOOST_CHECK_EQUAL(jc.get_enq_cnt(), u_int32_t(0)); + txn_commit(jc, 2*NUM_MSGS, xid); + BOOST_CHECK_EQUAL(jc.get_enq_cnt(), u_int32_t(0)); + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(enqueue_dequeue_abort_block) +{ + string test_name = get_test_name(test_filename, "enqueue_dequeue_abort_block"); + try + { + string msg; + string xid; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + create_xid(xid, 0, XID_SIZE); + for (int m=0; m<NUM_MSGS; m++) + BOOST_CHECK_EQUAL(enq_txn_msg(jc, m, create_msg(msg, m, MSG_SIZE), xid, false), u_int64_t(m)); + BOOST_CHECK_EQUAL(jc.get_enq_cnt(), u_int32_t(0)); + for (int m=0; m<NUM_MSGS; m++) + deq_txn_msg(jc, m, m+NUM_MSGS, xid); + BOOST_CHECK_EQUAL(jc.get_enq_cnt(), u_int32_t(0)); + txn_abort(jc, 2*NUM_MSGS, xid); + BOOST_CHECK_EQUAL(jc.get_enq_cnt(), u_int32_t(0)); + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(enqueue_dequeue_commit_interleaved) +{ + string test_name = get_test_name(test_filename, "enqueue_dequeue_commit_interleaved"); + try + { + string msg; + string xid; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + for (int m=0; m<NUM_MSGS; m++) + { + create_xid(xid, m, XID_SIZE); + BOOST_CHECK_EQUAL(enq_txn_msg(jc, 3*m, create_msg(msg, m, MSG_SIZE), xid, false), u_int64_t(3*m)); + deq_txn_msg(jc, 3*m, 3*m+1, xid); + txn_commit(jc, 3*m+2, xid); + BOOST_CHECK_EQUAL(jc.get_enq_cnt(), u_int32_t(0)); + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(enqueue_dequeue_abort_interleaved) +{ + string test_name = get_test_name(test_filename, "enqueue_dequeue_abort_interleaved"); + try + { + string msg; + string xid; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + for (int m=0; m<NUM_MSGS; m++) + { + create_xid(xid, m, XID_SIZE); + BOOST_CHECK_EQUAL(enq_txn_msg(jc, 3*m, create_msg(msg, m, MSG_SIZE), xid, false), u_int64_t(3*m)); + deq_txn_msg(jc, 3*m, 3*m+1, xid); + txn_abort(jc, 3*m+2, xid); + BOOST_CHECK_EQUAL(jc.get_enq_cnt(), u_int32_t(0)); + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_st_helper_fns.h b/qpid/cpp/src/tests/legacystore/jrnl/_st_helper_fns.h new file mode 100644 index 0000000000..923065dd11 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/_st_helper_fns.h @@ -0,0 +1,882 @@ +/* + * + * 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. + * + */ + +// NOTE: This file is included in _st_*.cpp files inside the QPID_AUTO_TEST_SUITE() +// definition. + +#define MAX_AIO_SLEEPS 500 +#define AIO_SLEEP_TIME 1000 +#define NUM_TEST_JFILES 4 +#define NUM_DEFAULT_JFILES 8 +#define JRNL_DEFAULT_FSIZE 24 // Multiples of JRNL_RMGR_PAGE_SIZE +#define TEST_JFSIZE_SBLKS 128 +#define DEFAULT_JFSIZE_SBLKS (JRNL_DEFAULT_FSIZE * JRNL_RMGR_PAGE_SIZE) +#define NUM_MSGS 5 +#define MSG_REC_SIZE_DBLKS 2 +#define MSG_SIZE (MSG_REC_SIZE_DBLKS * JRNL_DBLK_SIZE) - sizeof(enq_hdr) - sizeof(rec_tail) +#define LARGE_MSG_REC_SIZE_DBLKS (JRNL_SBLK_SIZE * JRNL_RMGR_PAGE_SIZE) +#define LARGE_MSG_SIZE (LARGE_MSG_REC_SIZE_DBLKS * JRNL_DBLK_SIZE) - sizeof(enq_hdr) - sizeof(rec_tail) +#define XID_SIZE 64 + +#define XLARGE_MSG_RATIO (1.0 * LARGE_MSG_REC_SIZE / JRNL_DBLK_SIZE / JRNL_SBLK_SIZE / JRNL_RMGR_PAGE_SIZE) +#define XLARGE_MSG_THRESHOLD (int)(JRNL_DEFAULT_FSIZE * NUM_DEFAULT_JFILES * JRNL_ENQ_THRESHOLD / 100 / LARGE_MSG_RATIO) + +#define NUM_JFILES 4 +#define JFSIZE_SBLKS 128 + +const char* tdp = getenv("TMP_DATA_DIR"); +const string test_dir(tdp && strlen(tdp) > 0 ? string(tdp) + "/" + test_filename : "/var/tmp/jrnl_test"); + +class test_dtok : public data_tok +{ +private: + bool flag; +public: + test_dtok() : data_tok(), flag(false) {} + virtual ~test_dtok() {} + bool done() { if (flag || _wstate == NONE) return true; else { flag = true; return false; } } +}; + +class test_jrnl_cb : public aio_callback { + virtual void wr_aio_cb(std::vector<data_tok*>& dtokl) + { + for (std::vector<data_tok*>::const_iterator i=dtokl.begin(); i!=dtokl.end(); i++) + { + test_dtok* dtp = static_cast<test_dtok*>(*i); + if (dtp->done()) + delete dtp; + } + } + virtual void rd_aio_cb(std::vector<u_int16_t>& /*pil*/) {} +}; + +class test_jrnl : public jcntl +{ +test_jrnl_cb* cb; + +public: + test_jrnl(const std::string& jid, const std::string& jdir, const std::string& base_filename, test_jrnl_cb& cb0) : + jcntl(jid, jdir, base_filename), + cb(&cb0) {} + virtual ~test_jrnl() {} + void initialize(const u_int16_t num_jfiles, const bool ae, const u_int16_t ae_max_jfiles, + const u_int32_t jfsize_sblks) + { + jcntl::initialize(num_jfiles, ae, ae_max_jfiles, jfsize_sblks, JRNL_WMGR_DEF_PAGES, JRNL_WMGR_DEF_PAGE_SIZE, + cb); + _jdir.create_dir(); + } + void recover(const u_int16_t num_jfiles, const bool ae, const u_int16_t ae_max_jfiles, const u_int32_t jfsize_sblks, + vector<string>* txn_list, u_int64_t& highest_rid) + { jcntl::recover(num_jfiles, ae, ae_max_jfiles, jfsize_sblks, JRNL_WMGR_DEF_PAGES, JRNL_WMGR_DEF_PAGE_SIZE, cb, + txn_list, highest_rid); } +}; + +/* +* This class is for testing recover functionality by maintaining an internal lfid-pfid map, then creating physical +* journal file stubs (just the fhdr section of the journal) and jinf file. This allows the recover functionality (which +* analyzes these components to determine recover order). +* +* First set up a map or "blueprint" of what the journal should look like for recovery, then have the class create the +* physical files. The jinf object under test then reads and analyzes the created journal, and it's analysis is checked +* against what is expected. +* +* General usage pattern: +* 1. Create instance of lfid_pfid_map. +* 2. Call lfid_pfid_map::journal_create() to simulate initial journal creation. +* 3. (optional) Call lfid_pfid_map::journal_insert() one or more times to simulate the addition of journal files. +* 4. Call lfid_pfid_map::write_journal() to create dummy journal files (files containing only file headers) +* 5. Create and initialize the jinf object under test +* 6. Call jinf::analyze() to determine the pfid order - and thus also first and last lids +* 7. Call lfid_pfid_map::check_analysis() to check the conclusions of the analysis +* 8. Call lfid_pfid_map::destroy_journal() to delete the journal files and reset the lfid_pfid_map object. +* 9. (optional) Back to step 2 for more tests +* +* See the individual methods below for more details. +*/ +class lfid_pfid_map +{ + public: + typedef pair<u_int16_t, file_hdr> lppair; // Used for loading the map + typedef multimap<u_int16_t, file_hdr> lpmap; // Stores the journal "plan" before it is created on-disk + typedef lpmap::const_iterator lpmap_citr; // General purpose iterator + typedef pair<lpmap_citr, lpmap_citr> lpmap_range; // Range of values returned by multimap's equal_range() fn + + private: + string _jid; // Journal id + string _base_filename; // Base filename + lpmap _map; // Stores the journal "blueprint" before it is created on-disk + u_int16_t _num_used_files; // number of files which contain jorunals + u_int16_t _oldest_lfid; // lfid where owi flips; always 0 if !_full + u_int16_t _last_pfid; // last pfid (ie last file added) + + public: + lfid_pfid_map(const string& jid, const string& base_filename) : + _jid(jid), _base_filename(base_filename), _num_used_files(0), _oldest_lfid(0), _last_pfid(0) + {} + virtual ~lfid_pfid_map() {} + + // Mainly used for debugging + void print() + { + int cnt = 0; + for (lpmap_citr i=_map.begin(); i!=_map.end(); i++, cnt++) + { + const file_hdr fh = i->second; + cout << " " << cnt << ": owi=" << (fh.get_owi()?"t":"f") << hex << " frid=0x" << fh._rid; + cout << " pfid=0x" << fh._pfid << " lfid=0x" << fh._lfid << " fro=0x" << fh._fro << dec << endl; + } + } + + std::size_t size() + { + return _map.size(); + } + + /* + * Method journal_create(): Used to simulate the initial creation of a journal before file insertions + * take place. + * + * num_jfiles: The initial journal file count. + * num_used_jfiles: If this number is less than num_jfiles, it indicates a clean journal that has not yet + * completed its first rotation, and some files are empty (ie all null). The first + * num_used_jfiles will contain file headers, the remainder will be blank. + * oldest_lfid: The lfid (==pfid, see note 1 below) at which the owi flag flips. During normal operation, + * each time the journal rotates back to file 0, a flag (called the overwrite indicator or owi) + * is flipped. This flag is saved in the file header. During recovery, if scanning from logical + * file 0 upwards, the file at which this flag reverses from its value in file 0 is the file + * that was to have been overwritten next, and is thus the "oldest" file. Recovery analysis must + * start with this file. oldest_lfid sets the file at which this flag will flip value for the + * simulated recovery analysis. Note that this will be ignored if num_used_jfiles < num_jfiles, + * as it is not possible for an overwrite to have occurred if not all the files have been used. + * first_owi: Sets the value of the owi flag in file 0. If set to false, then the flip will be found with + * a true flag (and visa versa). + * + * NOTES: + * 1. By definition, the lfids and pfids coincide for a journal containing no inserted files. Thus pfid == lfid + * for all journals created after using initial_journal_create() alone. + * 2. By definition, if a journal is not full (num_used_jfiles < num_jfiles), then all owi flags for those files + * that are used must be the same. It is not possible for an overwrite situation to arise if a journal is not + * full. + * 3. This function acts on map _map only, and does not create any test files. Call write_journal() to do that. + * 4. This function must be called on a clean test object or on one where the previous test data has been + * cleared by calling journal_destroy(). Running this function more than once on existing data will + * result in invalid journals which cannot be recovered. + */ + void journal_create(const u_int16_t num_jfiles, // Total number of files + const u_int16_t num_used_jfiles, // Number of used files, rest empty at end + const u_int16_t oldest_lfid = 0, // Fid where owi reverses + const u_int16_t bad_lfid = 0, // Fid where owi reverses again (must be > oldest_lifd), + // used for testing bad owi detection + const bool first_owi = false) // Value of first owi flag (ie pfid=0) + { + const bool full = num_used_jfiles == num_jfiles; + bool owi = first_owi; + _oldest_lfid = full ? oldest_lfid : 0; + for (u_int16_t lfid = 0; lfid < num_jfiles; lfid++) + { + const u_int16_t pfid = lfid; + file_hdr fh; + if (pfid < num_used_jfiles) + { + _num_used_files = num_used_jfiles; + /* + * Invert the owi flag from its current value (initially given by first_owi param) only if: + * 1. The journal is full (ie all files are used) + * AND + * 2. oldest_lfid param is non-zero (this is default, but lfid 0 being inverted is logically + * inconsistent with first_owi parameter being present) + * AND + * 3. Either: + * * current lfid == oldest_lfid (ie we are preparing the oldest lfid) + * OR + * * current lfid == bad_lfid AND bad_lfid > oldest (ie we are past the oldest and preparing the + * bad lfid) + */ + if (full && oldest_lfid > 0 && + (lfid == oldest_lfid || (bad_lfid > oldest_lfid && lfid == bad_lfid))) + owi = !owi; + const u_int64_t frid = u_int64_t(random()); + init_fhdr(fh, frid, pfid, lfid, owi); + } + _map.insert(lppair(lfid, fh)); + } + } + + /* + * Method journal_insert(): Used to simulate the insertion of journal files into an existing journal. + * + * after_lfid: The logical file id (lfid) after which the new file is to be inserted. + * num_files: The number of files to be inserted. + * adjust_lids: Flag indicating that the lids of files _following_ the inserted files are to be adjusted upwards + * by the number of inserted files. Not doing so simulates a recovery immediately after insertion + * but before the following files are overwritten with their new lids. If this is set false, then: + * a) after_lfid MUST be the most recent file (_oldest_lfid-1 ie last lfid before owi changes). + * b) This call must be the last insert call. + * + * NOTES: + * 1. It is not possible to insert before lfid/pfid 0; thus these are always coincidental. This operation is + * logically equivalent to inserting after the last lfid, which is possible. + * 2. It is not possible to insert into a journal that is not full. Doing so will result in an unrecoverable + * journal (one that is logically inconsistent that can never occur in reality). + * 3. If a journal is stopped/interrupted immediately after a file insertion, there could be duplicate lids in + * play at recovery, as the following file lids in their headers are only overwritten when the file is + * eventually written to during normal operation. The owi flags, however, are used to determine which of the + * ambiguous lids are the inserted files. + * 4. This function acts on map _map only, and does not create any test files. Call write_journal() to do that. + */ + void journal_insert(const u_int16_t after_lfid, // Insert files after this lfid + const u_int16_t num_files = 1, // Number of files to insert + const bool adjust_lids = true) // Adjust lids following inserted files + { + if (num_files == 0) return; + _num_used_files += num_files; + const u_int16_t num_jfiles_before_append = _map.size(); + lpmap_citr i = _map.find(after_lfid); + if (i == _map.end()) BOOST_FAIL("Unable to find lfid=" << after_lfid << " in map."); + const file_hdr fh_before = (*i).second; + + // Move overlapping lids (if req'd) + if (adjust_lids && after_lfid < num_jfiles_before_append - 1) + { + for (u_int16_t lfid = num_jfiles_before_append - 1; lfid > after_lfid; lfid--) + { + lpmap_citr itr = _map.find(lfid); + if (itr == _map.end()) BOOST_FAIL("Unable to find lfid=" << after_lfid << " in map."); + file_hdr fh = itr->second; + _map.erase(lfid); + fh._lfid += num_files; + if (lfid == _oldest_lfid) + _oldest_lfid += num_files; + _map.insert(lppair(fh._lfid, fh)); + } + } + + // Add new file headers + u_int16_t pfid = num_jfiles_before_append; + u_int16_t lfid = after_lfid + 1; + while (pfid < num_jfiles_before_append + num_files) + { + const u_int64_t frid = u_int64_t(random()); + const size_t fro = 0x200; + const file_hdr fh(RHM_JDAT_FILE_MAGIC, RHM_JDAT_VERSION, frid, pfid, lfid, fro, fh_before.get_owi(), + true); + _map.insert(lppair(lfid, fh)); + _last_pfid = pfid; + pfid++; + lfid++; + } + } + + /* + * Get the list of pfids in the map in order of lfid. The pfids are appended to the supplied vector. Only + * as many headers as are in the map are appended. + * NOTE: will clear any contents from supplied vector before appending pfid list. + */ + void get_pfid_list(vector<u_int16_t>& pfid_list) + { + pfid_list.clear(); + for (lpmap_citr i = _map.begin(); i != _map.end(); i++) + pfid_list.push_back(i->second._pfid); + } + + /* + * Get the list of lfids in the map. The lfids are appended to the supplied vector in the order they appear + * in the map (which is not necessarily the natural or sorted order). + * NOTE: will clear any contents from supplied vector before appending lfid list. + */ + void get_lfid_list(vector<u_int16_t>& lfid_list) + { + lfid_list.clear(); + lfid_list.assign(_map.size(), 0); + for (lpmap_citr i = _map.begin(); i != _map.end(); i++) + lfid_list[i->second._pfid] = i->first; + } + + /* + * Method check_analysis(): Used to check the result of the test jinf object analysis by comparing the pfid order + * array it produces against the internal map. + * + * ji: A ref to the jinf object under test. + */ + void check_analysis(jinf& ji) // jinf object under test after analyze() has been called + { + BOOST_CHECK_EQUAL(ji.get_first_pfid(), get_first_pfid()); + BOOST_CHECK_EQUAL(ji.get_last_pfid(), get_last_pfid()); + + jinf::pfid_list& pfidl = ji.get_pfid_list(); + const u_int16_t num_jfiles = _map.size(); + const bool all_used = _num_used_files == num_jfiles; + BOOST_CHECK_EQUAL(pfidl.size(), _num_used_files); + + const u_int16_t lfid_start = all_used ? _oldest_lfid : 0; + // Because a simulated failure would leave lfid dups in map and last_fid would not exist in map in this + // case, we must find lfid_stop via pfid instead. Search for pfid == num_files. + lpmap_citr itr = _map.begin(); + while (itr != _map.end() && itr->second._pfid != _num_used_files - 1) itr++; + if (itr == _map.end()) + BOOST_FAIL("check(): Unable to find pfid=" << (_num_used_files - 1) << " in map."); + const u_int16_t lfid_stop = itr->second._lfid; + + std::size_t fidl_index = 0; + for (u_int16_t lfid_cnt = lfid_start; lfid_cnt < lfid_stop; lfid_cnt++, fidl_index++) + { + const u_int16_t lfid = lfid_cnt % num_jfiles; + lpmap_citr itr = _map.find(lfid); + if (itr == _map.end()) + BOOST_FAIL("check(): Unable to find lfid=" << lfid << " in map."); + BOOST_CHECK_EQUAL(itr->second._pfid, pfidl[fidl_index]); + } + } + + /* + * Method get_pfid(): Look up a pfid from a known lfid. + */ + u_int16_t get_pfid(const u_int16_t lfid, const bool initial_owi = false) + { + switch (_map.count(lfid)) + { + case 1: + return _map.find(lfid)->second._pfid; + case 2: + for (lpmap_citr itr = _map.lower_bound(lfid); itr != _map.upper_bound(lfid); itr++) + { + if (itr->second.get_owi() != initial_owi) + return itr->second._pfid; + } + default:; + } + BOOST_FAIL("get_pfid(): lfid=" << lfid << " not found in map."); + return 0xffff; + } + + /* + * Method get_first_pfid(): Look up the first (oldest, or next-to-be-overwritten) pfid in the analysis sequence. + */ + u_int16_t get_first_pfid() + { + return get_pfid(_oldest_lfid); + } + + /* + * Method get_last_pfid(): Look up the last (newest, or most recently written) pfid in the analysis sequence. + */ + u_int16_t get_last_pfid() + { + u_int16_t flfid = 0; + if (_num_used_files == _map.size()) // journal full? + { + if (_oldest_lfid) + { + // if failed insert, cycle past duplicate lids + while (_map.count(_oldest_lfid) == 2) + _oldest_lfid++; + while (_map.find(_oldest_lfid) != _map.end() && _map.find(_oldest_lfid)->second.get_owi() == false) + _oldest_lfid++; + flfid = _oldest_lfid - 1; + } + else + flfid = _map.size() - 1; + } + else + flfid = _num_used_files - 1; + return get_pfid(flfid, true); + } + + /* + * Method write_journal(): Used to create the dummy journal files from the built-up map created by calling + * initial_journal_create() and optionally journal_append() one or more times. Since the jinf object reads the + * jinf file and the file headers only, the create object creates a dummy journal file containing only a file + * header (512 bytes each) and a single jinf file which contains the journal metadata required for recovery + * analysis. + */ + void write_journal(const bool ae, const u_int16_t ae_max_jfiles, const u_int32_t fsize_sblks = JFSIZE_SBLKS) + { + create_jinf(ae, ae_max_jfiles); + u_int16_t pfid = 0; + for (lpmap_citr itr = _map.begin(); itr != _map.end(); itr++, pfid++) + { + if (itr->second._pfid == 0 && itr->second._magic == 0) // empty header, use pfid counter instead + create_journal_file(pfid, itr->second, _base_filename, fsize_sblks); + else + create_journal_file(itr->second._pfid, itr->second, _base_filename, fsize_sblks); + } + } + + /* + * Method destroy_journal(): Destroy the files created by create_journal() and reset the lfid_pfid_map test + * object. A new test may be started using the same lfid_pfid_map test object once this call has been made. + */ + void destroy_journal() + { + for (u_int16_t pfid = 0; pfid < _map.size(); pfid++) + { + string fn = create_journal_filename(pfid, _base_filename); + BOOST_WARN_MESSAGE(::unlink(fn.c_str()) == 0, "destroy_journal(): Failed to remove file " << fn); + } + clean_journal_info_file(_base_filename); + _map.clear(); + _num_used_files = 0; + _oldest_lfid = 0; + _last_pfid = 0; + } + + /* + * Method create_new_jinf(): This static call creates a default jinf file only. This is used to test the read + * constructor of a jinf test object which reads a jinf file at instantiation. + */ + static void create_new_jinf(const string jid, const string base_filename, const bool ae) + { + if (jdir::exists(test_dir)) + jdir::delete_dir(test_dir); + create_jinf(NUM_JFILES, ae, (ae ? 5 * NUM_JFILES : 0), jid, base_filename); + } + + /* + * Method clean_journal_info_file(): This static method deletes only a jinf file without harming any other + * journal file or its directory. This is used to clear those tests which rely only on the existence of a + * jinf file. + */ + static void clean_journal_info_file(const string base_filename) + { + stringstream fn; + fn << test_dir << "/" << base_filename << "." << JRNL_INFO_EXTENSION; + BOOST_WARN_MESSAGE(::unlink(fn.str().c_str()) == 0, "clean_journal_info_file(): Failed to remove file " << + fn.str()); + } + + static string create_journal_filename(const u_int16_t pfid, const string base_filename) + { + stringstream fn; + fn << test_dir << "/" << base_filename << "."; + fn << setfill('0') << hex << setw(4) << pfid << "." << JRNL_DATA_EXTENSION; + return fn.str(); + } + + private: + static void init_fhdr(file_hdr& fh, + const u_int64_t frid, + const u_int16_t pfid, + const u_int16_t lfid, + const bool owi, + const bool no_enq = false) + { + fh._magic = RHM_JDAT_FILE_MAGIC; + fh._version = RHM_JDAT_VERSION; +#if defined(JRNL_BIG_ENDIAN) + fh._eflag = RHM_BENDIAN_FLAG; +#else + fh._eflag = RHM_LENDIAN_FLAG; +#endif + fh._uflag = owi ? rec_hdr::HDR_OVERWRITE_INDICATOR_MASK : 0; + fh._rid = frid; + fh._pfid = pfid; + fh._lfid = lfid; + fh._fro = no_enq ? 0 : 0x200; + timespec ts; + ::clock_gettime(CLOCK_REALTIME, &ts); + fh._ts_sec = ts.tv_sec; + fh._ts_nsec = ts.tv_nsec; + } + + void create_jinf(const bool ae, const u_int16_t ae_max_jfiles) + { + if (jdir::exists(test_dir)) + jdir::delete_dir(test_dir); + create_jinf(_map.size(), ae, ae_max_jfiles, _jid, _base_filename); + } + + static void create_jinf(u_int16_t num_files, const bool ae, const u_int16_t ae_max_jfiles, const string jid, + const string base_filename) + { + jdir::create_dir(test_dir); // Check test dir exists; create it if not + timespec ts; + ::clock_gettime(CLOCK_REALTIME, &ts); + jinf ji(jid, test_dir, base_filename, num_files, ae, ae_max_jfiles, JFSIZE_SBLKS, JRNL_WMGR_DEF_PAGE_SIZE, + JRNL_WMGR_DEF_PAGES, ts); + ji.write(); + } + + static void create_journal_file(const u_int16_t pfid, + const file_hdr& fh, + const string base_filename, + const u_int32_t fsize_sblks = JFSIZE_SBLKS, + const char fill_char = 0) + { + const std::string filename = create_journal_filename(pfid, base_filename); + ofstream of(filename.c_str(), ofstream::out | ofstream::trunc); + if (!of.good()) + BOOST_FAIL("Unable to open test journal file \"" << filename << "\" for writing."); + + write_file_header(filename, of, fh, fill_char); + write_file_body(of, fsize_sblks, fill_char); + + of.close(); + if (of.fail() || of.bad()) + BOOST_FAIL("Error closing test journal file \"" << filename << "\"."); + } + + static void write_file_header(const std::string& filename, + ofstream& of, + const file_hdr& fh, + const char fill_char) + { + // write file header + u_int32_t cnt = sizeof(file_hdr); + of.write((const char*)&fh, cnt); + if (of.fail() || of.bad()) + BOOST_FAIL("Error writing file header to test journal file \"" << filename << "\"."); + + // fill remaining sblk with fill char + while (cnt++ < JRNL_DBLK_SIZE * JRNL_SBLK_SIZE) + { + of.put(fill_char); + if (of.fail() || of.bad()) + BOOST_FAIL("Error writing filler to test journal file \"" << filename << "\"."); + } + } + + static void write_file_body(ofstream& of, const u_int32_t fsize_sblks, const char fill_char) + { + if (fsize_sblks > 1) + { + std::vector<char> sblk_buffer(JRNL_DBLK_SIZE * JRNL_SBLK_SIZE, fill_char); + u_int32_t fwritten_sblks = 0; // hdr + while (fwritten_sblks++ < fsize_sblks) + of.write(&sblk_buffer[0], JRNL_DBLK_SIZE * JRNL_SBLK_SIZE); + } + } +}; + +const string +get_test_name(const string& file, const string& test_name) +{ + cout << test_filename << "." << test_name << ": " << flush; + return file + "." + test_name; +} + +bool +check_iores(const string& ctxt, const iores ret, const iores exp_ret, test_dtok* dtp) +{ + if (ret != exp_ret) + { + delete dtp; + BOOST_FAIL(ctxt << ": Expected " << iores_str(exp_ret) << "; got " << iores_str(ret)); + } + return false; +} + +bool +handle_jcntl_response(const iores res, jcntl& jc, unsigned& aio_sleep_cnt, const std::string& ctxt, const iores exp_ret, + test_dtok* dtp) +{ + if (res == RHM_IORES_PAGE_AIOWAIT) + { + if (++aio_sleep_cnt <= MAX_AIO_SLEEPS) + { + jc.get_wr_events(0); // *** GEV2 + usleep(AIO_SLEEP_TIME); + } + else + return check_iores(ctxt, res, exp_ret, dtp); + } + else + return check_iores(ctxt, res, exp_ret, dtp); + return true; +} + +u_int64_t +enq_msg(jcntl& jc, + const u_int64_t rid, + const string& msg, + const bool transient, + const iores exp_ret = RHM_IORES_SUCCESS) +{ + ostringstream ctxt; + ctxt << "enq_msg(" << rid << ")"; + test_dtok* dtp = new test_dtok; + BOOST_CHECK_MESSAGE(dtp != 0, "Data token allocation failed (dtp == 0)."); + dtp->set_rid(rid); + dtp->set_external_rid(true); + try + { + iores res = jc.enqueue_data_record(msg.c_str(), msg.size(), msg.size(), dtp, transient); + check_iores(ctxt.str(), res, exp_ret, dtp); + u_int64_t dtok_rid = dtp->rid(); + if (dtp->done()) delete dtp; + return dtok_rid; + } + catch (exception& e) { delete dtp; throw; } +} + +u_int64_t +enq_extern_msg(jcntl& jc, const u_int64_t rid, const std::size_t msg_size, const bool transient, + const iores exp_ret = RHM_IORES_SUCCESS) +{ + ostringstream ctxt; + ctxt << "enq_extern_msg(" << rid << ")"; + test_dtok* dtp = new test_dtok; + BOOST_CHECK_MESSAGE(dtp != 0, "Data token allocation failed (dtp == 0)."); + dtp->set_rid(rid); + dtp->set_external_rid(true); + try + { + iores res = jc.enqueue_extern_data_record(msg_size, dtp, transient); + check_iores(ctxt.str(), res, exp_ret, dtp); + u_int64_t dtok_rid = dtp->rid(); + if (dtp->done()) delete dtp; + return dtok_rid; + } + catch (exception& e) { delete dtp; throw; } +} + +u_int64_t +enq_txn_msg(jcntl& jc, const u_int64_t rid, const string& msg, const string& xid, const bool transient, + const iores exp_ret = RHM_IORES_SUCCESS) +{ + ostringstream ctxt; + ctxt << "enq_txn_msg(" << rid << ")"; + test_dtok* dtp = new test_dtok; + BOOST_CHECK_MESSAGE(dtp != 0, "Data token allocation failed (dtp == 0)."); + dtp->set_rid(rid); + dtp->set_external_rid(true); + try + { + iores res = jc.enqueue_txn_data_record(msg.c_str(), msg.size(), msg.size(), dtp, xid, + transient); + check_iores(ctxt.str(), res, exp_ret, dtp); + u_int64_t dtok_rid = dtp->rid(); + if (dtp->done()) delete dtp; + return dtok_rid; + } + catch (exception& e) { delete dtp; throw; } +} + +u_int64_t +enq_extern_txn_msg(jcntl& jc, const u_int64_t rid, const std::size_t msg_size, const string& xid, const bool transient, + const iores exp_ret = RHM_IORES_SUCCESS) +{ + ostringstream ctxt; + ctxt << "enq_extern_txn_msg(" << rid << ")"; + test_dtok* dtp = new test_dtok; + BOOST_CHECK_MESSAGE(dtp != 0, "Data token allocation failed (dtp == 0)."); + dtp->set_rid(rid); + dtp->set_external_rid(true); + try + { + iores res = jc.enqueue_extern_txn_data_record(msg_size, dtp, xid, transient); + check_iores(ctxt.str(), res, exp_ret, dtp); + u_int64_t dtok_rid = dtp->rid(); + if (dtp->done()) delete dtp; + return dtok_rid; + } + catch (exception& e) { delete dtp; throw; } +} + +u_int64_t +deq_msg(jcntl& jc, const u_int64_t drid, const u_int64_t rid, const iores exp_ret = RHM_IORES_SUCCESS) +{ + ostringstream ctxt; + ctxt << "deq_msg(" << drid << ")"; + test_dtok* dtp = new test_dtok; + BOOST_CHECK_MESSAGE(dtp != 0, "Data token allocation failed (dtp == 0)."); + dtp->set_rid(rid); + dtp->set_dequeue_rid(drid); + dtp->set_external_rid(true); + dtp->set_wstate(data_tok::ENQ); + try + { + iores res = jc.dequeue_data_record(dtp); + check_iores(ctxt.str(), res, exp_ret, dtp); + u_int64_t dtok_rid = dtp->rid(); + if (dtp->done()) delete dtp; + return dtok_rid; + } + catch (exception& e) { delete dtp; throw; } +} + +u_int64_t +deq_txn_msg(jcntl& jc, const u_int64_t drid, const u_int64_t rid, const string& xid, + const iores exp_ret = RHM_IORES_SUCCESS) +{ + ostringstream ctxt; + ctxt << "deq_txn_msg(" << drid << ")"; + test_dtok* dtp = new test_dtok; + BOOST_CHECK_MESSAGE(dtp != 0, "Data token allocation failed (dtp == 0)."); + dtp->set_rid(rid); + dtp->set_dequeue_rid(drid); + dtp->set_external_rid(true); + dtp->set_wstate(data_tok::ENQ); + try + { + iores res = jc.dequeue_txn_data_record(dtp, xid); + check_iores(ctxt.str(), res, exp_ret, dtp); + u_int64_t dtok_rid = dtp->rid(); + if (dtp->done()) delete dtp; + return dtok_rid; + } + catch (exception& e) { delete dtp; throw; } +} + +u_int64_t +txn_abort(jcntl& jc, const u_int64_t rid, const string& xid, const iores exp_ret = RHM_IORES_SUCCESS) +{ + test_dtok* dtp = new test_dtok; + BOOST_CHECK_MESSAGE(dtp != 0, "Data token allocation failed (dtp == 0)."); + dtp->set_rid(rid); + dtp->set_external_rid(true); + try + { + iores res = jc.txn_abort(dtp, xid); + check_iores("txn_abort", res, exp_ret, dtp); + u_int64_t dtok_rid = dtp->rid(); + if (dtp->done()) delete dtp; + return dtok_rid; + } + catch (exception& e) { delete dtp; throw; } +} + +u_int64_t +txn_commit(jcntl& jc, const u_int64_t rid, const string& xid, const iores exp_ret = RHM_IORES_SUCCESS) +{ + test_dtok* dtp = new test_dtok; + BOOST_CHECK_MESSAGE(dtp != 0, "Data token allocation failed (dtp == 0)."); + dtp->set_rid(rid); + dtp->set_external_rid(true); + try + { + iores res = jc.txn_commit(dtp, xid); + check_iores("txn_commit", res, exp_ret, dtp); + u_int64_t dtok_rid = dtp->rid(); + if (dtp->done()) delete dtp; + return dtok_rid; + } + catch (exception& e) { delete dtp; throw; } +} + +void +read_msg(jcntl& jc, string& msg, string& xid, bool& transient, bool& external, const iores exp_ret = RHM_IORES_SUCCESS) +{ + void* mp = 0; + std::size_t msize = 0; + void* xp = 0; + std::size_t xsize = 0; + test_dtok* dtp = new test_dtok; + BOOST_CHECK_MESSAGE(dtp != 0, "Data token allocation failed (dtp == 0)."); + dtp->set_wstate(data_tok::ENQ); + + unsigned aio_sleep_cnt = 0; + try + { + iores res = jc.read_data_record(&mp, msize, &xp, xsize, transient, external, dtp); + while (handle_jcntl_response(res, jc, aio_sleep_cnt, "read_msg", exp_ret, dtp)) + res = jc.read_data_record(&mp, msize, &xp, xsize, transient, external, dtp); + } + catch (exception& e) { delete dtp; throw; } + + if (mp) + msg.assign((char*)mp, msize); + if (xp) + { + xid.assign((char*)xp, xsize); + std::free(xp); + xp = 0; + } + else if (mp) + { + std::free(mp); + mp = 0; + } + delete dtp; +} + +/* + * Returns the number of messages of size msg_rec_size_dblks that will fit into an empty journal with or without + * corresponding dequeues (controlled by include_deq) without a threshold - ie until the journal is full. Assumes + * that dequeue records fit into one dblk. + */ +u_int32_t +num_msgs_to_full(const u_int16_t num_files, const u_int32_t file_size_dblks, const u_int32_t msg_rec_size_dblks, + bool include_deq) +{ + u_int32_t rec_size_dblks = msg_rec_size_dblks; + if (include_deq) + rec_size_dblks++; + return u_int32_t(::floor(1.0 * num_files * file_size_dblks / rec_size_dblks)); +} + +/* + * Returns the number of messages of size msg_rec_size_dblks that will fit into an empty journal before the enqueue + * threshold of JRNL_ENQ_THRESHOLD (%). + */ +u_int32_t +num_msgs_to_threshold(const u_int16_t num_files, const u_int32_t file_size_dblks, const u_int32_t msg_rec_size_dblks) +{ + return u_int32_t(::floor(1.0 * num_files * file_size_dblks * JRNL_ENQ_THRESHOLD / msg_rec_size_dblks / 100)); +} + +/* + * Returns the amount of space reserved in dblks (== num dequeues assuming dequeue size of 1 dblk) for the enqueue + * threshold of JRNL_ENQ_THRESHOLD (%). + */ +u_int32_t +num_dequeues_rem(const u_int16_t num_files, const u_int32_t file_size_dblks) +{ + /* + * Fraction of journal remaining after threshold is used --------------+ + * Total no. dblks in journal ------+ | + * | | + * +------------+------------+ +-----------------+---------------------+ + */ + return u_int32_t(::ceil(num_files * file_size_dblks * (1.0 - (1.0 * JRNL_ENQ_THRESHOLD / 100)))); +} + +string& +create_msg(string& s, const int msg_num, const int len) +{ + ostringstream oss; + oss << "MSG_" << setfill('0') << setw(6) << msg_num << "_"; + for (int i=12; i<=len; i++) + oss << (char)('0' + i%10); + s.assign(oss.str()); + return s; +} + +string& +create_xid(string& s, const int msg_num, const int len) +{ + ostringstream oss; + oss << "XID_" << setfill('0') << setw(6) << msg_num << "_"; + for (int i=11; i<len; i++) + oss << (char)('a' + i%26); + s.assign(oss.str()); + return s; +} + +long +get_seed() +{ + timespec ts; + if (::clock_gettime(CLOCK_REALTIME, &ts)) + BOOST_FAIL("Unable to read clock to generate seed."); + long tenths = ts.tv_nsec / 100000000; + return long(10 * ts.tv_sec + tenths); // time in tenths of a second +} diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_st_read.cpp b/qpid/cpp/src/tests/legacystore/jrnl/_st_read.cpp new file mode 100644 index 0000000000..ff2c39e14c --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/_st_read.cpp @@ -0,0 +1,460 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "../unit_test.h" +#include <cmath> +#include <iostream> +#include "qpid/legacystore/jrnl/jcntl.h" + +using namespace boost::unit_test; +using namespace mrg::journal; +using namespace std; + +QPID_AUTO_TEST_SUITE(journal_read) + +const string test_filename("_st_read"); + +#include "_st_helper_fns.h" + +// === Test suite === + +#ifndef LONG_TEST +/* + * ============================================== + * NORMAL TESTS + * This section contains normal "make check" tests + * for building/packaging. These are built when + * LONG_TEST is _not_ defined. + * ============================================== + */ + +QPID_AUTO_TEST_CASE(empty_read) +{ + string test_name = get_test_name(test_filename, "empty_read"); + try + { + string msg; + string rmsg; + string xid; + bool transientFlag; + bool externalFlag; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + read_msg(jc, rmsg, xid, transientFlag, externalFlag, RHM_IORES_EMPTY); + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(enqueue_read_dequeue_block) +{ + string test_name = get_test_name(test_filename, "enqueue_read_dequeue_block"); + try + { + string msg; + string rmsg; + string xid; + bool transientFlag; + bool externalFlag; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + for (int m=0; m<NUM_MSGS; m++) + enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false); + jc.flush(); + for (int m=0; m<NUM_MSGS; m++) + { + read_msg(jc, rmsg, xid, transientFlag, externalFlag); + BOOST_CHECK_EQUAL(create_msg(msg, m, MSG_SIZE), rmsg); + BOOST_CHECK_EQUAL(xid.size(), std::size_t(0)); + BOOST_CHECK_EQUAL(transientFlag, false); + BOOST_CHECK_EQUAL(externalFlag, false); + } + read_msg(jc, rmsg, xid, transientFlag, externalFlag, RHM_IORES_EMPTY); + for (int m=0; m<NUM_MSGS; m++) + deq_msg(jc, m, m+NUM_MSGS); + read_msg(jc, rmsg, xid, transientFlag, externalFlag, RHM_IORES_EMPTY); + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(enqueue_read_dequeue_interleaved) +{ + string test_name = get_test_name(test_filename, "enqueue_read_dequeue_interleaved"); + try + { + string msg; + string rmsg; + string xid; + bool transientFlag; + bool externalFlag; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + for (int m=0; m<500*NUM_MSGS; m+=2) + { + enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false); + jc.flush(); + read_msg(jc, rmsg, xid, transientFlag, externalFlag); + BOOST_CHECK_EQUAL(create_msg(msg, m, MSG_SIZE), rmsg); + BOOST_CHECK_EQUAL(xid.size(), std::size_t(0)); + BOOST_CHECK_EQUAL(transientFlag, false); + BOOST_CHECK_EQUAL(externalFlag, false); + deq_msg(jc, m, m+1); + jc.flush(); + read_msg(jc, rmsg, xid, transientFlag, externalFlag, RHM_IORES_EMPTY); + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(enqueue_recovered_read_dequeue) +{ + string test_name = get_test_name(test_filename, "enqueue_recovered_read_dequeue"); + try + { + { + string msg; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + for (int m=0; m<NUM_MSGS; m++) + enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false); + } + { + string msg; + u_int64_t hrid; + string rmsg; + string xid; + bool transientFlag; + bool externalFlag; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.recover(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS, 0, hrid); + BOOST_CHECK_EQUAL(hrid, u_int64_t(NUM_MSGS - 1)); + jc.recover_complete(); + for (int m=0; m<NUM_MSGS; m++) + { + read_msg(jc, rmsg, xid, transientFlag, externalFlag); + BOOST_CHECK_EQUAL(create_msg(msg, m, MSG_SIZE), rmsg); + BOOST_CHECK_EQUAL(xid.size(), std::size_t(0)); + BOOST_CHECK_EQUAL(transientFlag, false); + BOOST_CHECK_EQUAL(externalFlag, false); + } + read_msg(jc, rmsg, xid, transientFlag, externalFlag, RHM_IORES_EMPTY); + for (int m=0; m<NUM_MSGS; m++) + deq_msg(jc, m, m+NUM_MSGS); + read_msg(jc, rmsg, xid, transientFlag, externalFlag, RHM_IORES_EMPTY); + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(multi_page_enqueue_recovered_read_dequeue_block) +{ + string test_name = get_test_name(test_filename, "multi_page_enqueue_recovered_read_dequeue_block"); + try + { + { + string msg; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(2*NUM_TEST_JFILES, false, 0, 10*TEST_JFSIZE_SBLKS); + for (int m=0; m<NUM_MSGS*125; m++) + enq_msg(jc, m, create_msg(msg, m, 16*MSG_SIZE), false); + } + { + string msg; + u_int64_t hrid; + string rmsg; + string xid; + bool transientFlag; + bool externalFlag; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.recover(2*NUM_TEST_JFILES, false, 0, 10*TEST_JFSIZE_SBLKS, 0, hrid); + BOOST_CHECK_EQUAL(hrid, u_int64_t(NUM_MSGS*125 - 1)); + jc.recover_complete(); + for (int m=0; m<NUM_MSGS*125; m++) + { + read_msg(jc, rmsg, xid, transientFlag, externalFlag); + BOOST_CHECK_EQUAL(create_msg(msg, m, 16*MSG_SIZE), rmsg); + BOOST_CHECK_EQUAL(xid.size(), std::size_t(0)); + BOOST_CHECK_EQUAL(transientFlag, false); + BOOST_CHECK_EQUAL(externalFlag, false); + } + read_msg(jc, rmsg, xid, transientFlag, externalFlag, RHM_IORES_EMPTY); + for (int m=0; m<NUM_MSGS*125; m++) + deq_msg(jc, m, m+NUM_MSGS*125); + read_msg(jc, rmsg, xid, transientFlag, externalFlag, RHM_IORES_EMPTY); + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(enqueue_recover_read_recovered_read_dequeue_block) +{ + string test_name = get_test_name(test_filename, "enqueue_recover_read_recovered_read_dequeue_block"); + try + { + { + string msg; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + for (int m=0; m<NUM_MSGS; m++) + enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false); + } + { + string msg; + u_int64_t hrid; + string rmsg; + string xid; + bool transientFlag; + bool externalFlag; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.recover(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS, 0, hrid); + BOOST_CHECK_EQUAL(hrid, u_int64_t(NUM_MSGS - 1)); + for (int m=0; m<NUM_MSGS; m++) + { + read_msg(jc, rmsg, xid, transientFlag, externalFlag); + BOOST_CHECK_EQUAL(create_msg(msg, m, MSG_SIZE), rmsg); + BOOST_CHECK_EQUAL(xid.size(), std::size_t(0)); + BOOST_CHECK_EQUAL(transientFlag, false); + BOOST_CHECK_EQUAL(externalFlag, false); + } + read_msg(jc, rmsg, xid, transientFlag, externalFlag, RHM_IORES_EMPTY); + } + { + string msg; + u_int64_t hrid; + string rmsg; + string xid; + bool transientFlag; + bool externalFlag; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.recover(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS, 0, hrid); + BOOST_CHECK_EQUAL(hrid, u_int64_t(NUM_MSGS - 1)); + for (int m=0; m<NUM_MSGS; m++) + { + read_msg(jc, rmsg, xid, transientFlag, externalFlag); + BOOST_CHECK_EQUAL(create_msg(msg, m, MSG_SIZE), rmsg); + BOOST_CHECK_EQUAL(xid.size(), std::size_t(0)); + BOOST_CHECK_EQUAL(transientFlag, false); + BOOST_CHECK_EQUAL(externalFlag, false); + } + read_msg(jc, rmsg, xid, transientFlag, externalFlag, RHM_IORES_EMPTY); + jc.recover_complete(); + for (int m=0; m<NUM_MSGS; m++) + { + read_msg(jc, rmsg, xid, transientFlag, externalFlag); + BOOST_CHECK_EQUAL(create_msg(msg, m, MSG_SIZE), rmsg); + BOOST_CHECK_EQUAL(xid.size(), std::size_t(0)); + BOOST_CHECK_EQUAL(transientFlag, false); + BOOST_CHECK_EQUAL(externalFlag, false); + } + read_msg(jc, rmsg, xid, transientFlag, externalFlag, RHM_IORES_EMPTY); + for (int m=0; m<NUM_MSGS; m++) + deq_msg(jc, m, m+NUM_MSGS); + read_msg(jc, rmsg, xid, transientFlag, externalFlag, RHM_IORES_EMPTY); + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(delayed_read) +{ + string test_name = get_test_name(test_filename, "delayed_read"); + try + { + string msg; + string rmsg; + string xid; + bool transientFlag; + bool externalFlag; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + unsigned m; + for (m=0; m<2*NUM_MSGS; m+=2) + { + enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false); + deq_msg(jc, m, m+1); + } + enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false); + jc.flush(); + read_msg(jc, rmsg, xid, transientFlag, externalFlag); + BOOST_CHECK_EQUAL(msg, rmsg); + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(cache_cycled_delayed_read) +{ + string test_name = get_test_name(test_filename, "cache_cycled_delayed_read"); + try + { + string msg; + string rmsg; + string xid; + bool transientFlag; + bool externalFlag; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + unsigned m; + unsigned read_buffer_size_dblks = JRNL_RMGR_PAGES * JRNL_RMGR_PAGE_SIZE * JRNL_SBLK_SIZE; + unsigned n = num_msgs_to_full(1, read_buffer_size_dblks, 16*MSG_REC_SIZE_DBLKS, true); + for (m=0; m<2*2*n + 20; m+=2) // fill read buffer twice + 10 msgs + { + enq_msg(jc, m, create_msg(msg, m, 16*MSG_SIZE), false); + deq_msg(jc, m, m+1); + } + enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false); + jc.flush(); + read_msg(jc, rmsg, xid, transientFlag, externalFlag); + BOOST_CHECK_EQUAL(msg, rmsg); + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +#else +/* + * ============================================== + * LONG TESTS + * This section contains long tests and soak tests, + * and are run using target check-long (ie "make + * check-long"). These are built when LONG_TEST is + * defined. + * ============================================== + */ + +QPID_AUTO_TEST_CASE(multi_page_enqueue_read_dequeue_block) +{ + string test_name = get_test_name(test_filename, "multi_page_enqueue_read_dequeue_block"); + try + { + string msg; + string rmsg; + string xid; + bool transientFlag; + bool externalFlag; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(2*NUM_TEST_JFILES, false, 0, 10*TEST_JFSIZE_SBLKS); + for (int i=0; i<10; i++) + { + for (int m=0; m<NUM_MSGS*125; m++) + enq_msg(jc, m, create_msg(msg, m, 16*MSG_SIZE), false); + jc.flush(); + for (int m=0; m<NUM_MSGS*125; m++) + { + read_msg(jc, rmsg, xid, transientFlag, externalFlag); + BOOST_CHECK_EQUAL(create_msg(msg, m, 16*MSG_SIZE), rmsg); + BOOST_CHECK_EQUAL(xid.size(), std::size_t(0)); + BOOST_CHECK_EQUAL(transientFlag, false); + BOOST_CHECK_EQUAL(externalFlag, false); + } + read_msg(jc, rmsg, xid, transientFlag, externalFlag, RHM_IORES_EMPTY); + for (int m=0; m<NUM_MSGS*125; m++) + deq_msg(jc, m, m+NUM_MSGS*125); + read_msg(jc, rmsg, xid, transientFlag, externalFlag, RHM_IORES_EMPTY); + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(increasing_interval_delayed_read) +{ + string test_name = get_test_name(test_filename, "increasing_interval_delayed_read"); + try + { + string msg; + string rmsg; + string xid; + bool transientFlag; + bool externalFlag; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + unsigned read_buffer_size_dblks = JRNL_RMGR_PAGES * JRNL_RMGR_PAGE_SIZE * JRNL_SBLK_SIZE; + unsigned n = num_msgs_to_full(1, read_buffer_size_dblks, MSG_REC_SIZE_DBLKS, true); + unsigned m = 0; + + // Validate read pipeline + enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false); + jc.flush(); + read_msg(jc, rmsg, xid, transientFlag, externalFlag); + deq_msg(jc, m, m+1); + m += 2; + + // repeat the following multiple times... + for (int i=0; i<10; i++) + { + // Invalidate read pipeline with large write + unsigned t = m + (i*n) + 25; + for (; m<t; m+=2) + { + enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false); + deq_msg(jc, m, m+1); + } + + // Revalidate read pipeline + enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false); + jc.flush(); + read_msg(jc, rmsg, xid, transientFlag, externalFlag); + BOOST_CHECK_EQUAL(msg, rmsg); + deq_msg(jc, m, m+1); + m += 2; + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +#endif + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_st_read_txn.cpp b/qpid/cpp/src/tests/legacystore/jrnl/_st_read_txn.cpp new file mode 100644 index 0000000000..621777d8d3 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/_st_read_txn.cpp @@ -0,0 +1,353 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "../unit_test.h" +#include <cmath> +#include <iostream> +#include "qpid/legacystore/jrnl/jcntl.h" + +using namespace boost::unit_test; +using namespace mrg::journal; +using namespace std; + +QPID_AUTO_TEST_SUITE(journal_read_txn) + +const string test_filename("_st_read_txn"); + +#include "_st_helper_fns.h" + +// === Test suite === + +QPID_AUTO_TEST_CASE(tx_enqueue_commit_block) +{ + string test_name = get_test_name(test_filename, "tx_enqueue_commit_block"); + try + { + string msg; + string xid; + string rmsg; + string rxid; + bool transientFlag; + bool externalFlag; + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + create_xid(xid, 0, XID_SIZE); + for (int m=0; m<NUM_MSGS; m++) + enq_txn_msg(jc, m, create_msg(msg, m, MSG_SIZE), xid, false); + jc.flush(); + read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_TXPENDING); + txn_commit(jc, NUM_MSGS, xid); + jc.flush(); + for (int m=0; m<NUM_MSGS; m++) + { + read_msg(jc, rmsg, rxid, transientFlag, externalFlag); + BOOST_CHECK_EQUAL(create_msg(msg, m, MSG_SIZE), rmsg); + BOOST_CHECK_EQUAL(rxid, xid); + BOOST_CHECK_EQUAL(transientFlag, false); + BOOST_CHECK_EQUAL(externalFlag, false); + } + read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_EMPTY); + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(tx_enqueue_commit_interleaved) +{ + string test_name = get_test_name(test_filename, "tx_enqueue_commit_interleaved"); + try + { + string msg; + string xid; + string rmsg; + string rxid; + bool transientFlag; + bool externalFlag; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + for (int m=0; m<NUM_MSGS; m++) + { + create_xid(xid, 2*m, XID_SIZE); + enq_txn_msg(jc, 2*m, create_msg(msg, 2*m, MSG_SIZE), xid, false); + jc.flush(); + read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_TXPENDING); + txn_commit(jc, 2*m+1, xid); + jc.flush(); + read_msg(jc, rmsg, rxid, transientFlag, externalFlag); + BOOST_CHECK_EQUAL(create_msg(msg, 2*m, MSG_SIZE), rmsg); + BOOST_CHECK_EQUAL(rxid, xid); + BOOST_CHECK_EQUAL(transientFlag, false); + BOOST_CHECK_EQUAL(externalFlag, false); + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(tx_enqueue_abort_block) +{ + string test_name = get_test_name(test_filename, "tx_enqueue_abort_block"); + try + { + string msg; + string xid; + string rmsg; + string rxid; + bool transientFlag; + bool externalFlag; + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + create_xid(xid, 1, XID_SIZE); + for (int m=0; m<NUM_MSGS; m++) + enq_txn_msg(jc, m, create_msg(msg, m, MSG_SIZE), xid, false); + jc.flush(); + read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_TXPENDING); + txn_abort(jc, NUM_MSGS, xid); + jc.flush(); + read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_EMPTY); + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(tx_enqueue_abort_interleaved) +{ + string test_name = get_test_name(test_filename, "tx_enqueue_abort_interleaved"); + try + { + string msg; + string xid; + string rmsg; + string rxid; + bool transientFlag; + bool externalFlag; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + for (int m=0; m<NUM_MSGS; m++) + { + create_xid(xid, 2*m, XID_SIZE); + enq_txn_msg(jc, 2*m, create_msg(msg, 2*m, MSG_SIZE), xid, false); + jc.flush(); + read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_TXPENDING); + txn_abort(jc, 2*m+1, xid); + jc.flush(); + read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_EMPTY); + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(tx_enqueue_commit_dequeue_block) +{ + string test_name = get_test_name(test_filename, "tx_enqueue_commit_dequeue_block"); + try + { + string msg; + string xid; + string rmsg; + string rxid; + bool transientFlag; + bool externalFlag; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + create_xid(xid, 2, XID_SIZE); + for (int m=0; m<NUM_MSGS; m++) + enq_txn_msg(jc, m, create_msg(msg, m, MSG_SIZE), xid, false); + txn_commit(jc, NUM_MSGS, xid); + for (int m=0; m<NUM_MSGS; m++) + deq_msg(jc, m, m+NUM_MSGS+1); + jc.flush(); + read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_EMPTY); + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(tx_enqueue_commit_dequeue_interleaved) +{ + string test_name = get_test_name(test_filename, "tx_enqueue_commit_dequeue_interleaved"); + try + { + string msg; + string xid; + string rmsg; + string rxid; + bool transientFlag; + bool externalFlag; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + for (int m=0; m<NUM_MSGS; m++) + { + create_xid(xid, 3*m, XID_SIZE); + enq_txn_msg(jc, 3*m, create_msg(msg, m, MSG_SIZE), xid, false); + txn_commit(jc, 3*m+1, xid); + deq_msg(jc, 3*m, 3*m+2); + jc.flush(); + read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_EMPTY); + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(enqueue_tx_dequeue_commit_block) +{ + string test_name = get_test_name(test_filename, "enqueue_tx_dequeue_commit_block"); + try + { + string msg; + string xid; + string rmsg; + string rxid; + bool transientFlag; + bool externalFlag; + + create_xid(xid, 3, XID_SIZE); + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + for (int m=0; m<NUM_MSGS; m++) + enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false); + for (int m=0; m<NUM_MSGS; m++) + deq_txn_msg(jc, m, m+NUM_MSGS, xid); + jc.flush(); + read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_TXPENDING); + txn_commit(jc, 2*NUM_MSGS, xid); + jc.flush(); + read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_EMPTY); + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(enqueue_tx_dequeue_commit_interleaved) +{ + string test_name = get_test_name(test_filename, "enqueue_tx_dequeue_commit_interleaved"); + try + { + string msg; + string xid; + string rmsg; + string rxid; + bool transientFlag; + bool externalFlag; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + for (int m=0; m<NUM_MSGS; m++) + { + enq_msg(jc, 3*m, create_msg(msg, 3*m, MSG_SIZE), false); + create_xid(xid, 3*m, XID_SIZE); + deq_txn_msg(jc, 3*m, 3*m+1, xid); + jc.flush(); + read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_TXPENDING); + txn_commit(jc, 3*m+2, xid); + jc.flush(); + read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_EMPTY); + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(enqueue_tx_dequeue_abort_block) +{ + string test_name = get_test_name(test_filename, "enqueue_tx_dequeue_abort_block"); + try + { + string msg; + string xid; + string rmsg; + string rxid; + bool transientFlag; + bool externalFlag; + + create_xid(xid, 4, XID_SIZE); + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + for (int m=0; m<NUM_MSGS; m++) + enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false); + for (int m=0; m<NUM_MSGS; m++) + deq_txn_msg(jc, m, m+NUM_MSGS, xid); + jc.flush(); + read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_TXPENDING); + txn_abort(jc, 2*NUM_MSGS, xid); + jc.flush(); + for (int m=0; m<NUM_MSGS; m++) + { + read_msg(jc, rmsg, rxid, transientFlag, externalFlag); + BOOST_CHECK_EQUAL(create_msg(msg, m, MSG_SIZE), rmsg); + BOOST_CHECK_EQUAL(rxid.length(), std::size_t(0)); + BOOST_CHECK_EQUAL(transientFlag, false); + BOOST_CHECK_EQUAL(externalFlag, false); + } + read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_EMPTY); + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(enqueue_tx_dequeue_abort_interleaved) +{ + string test_name = get_test_name(test_filename, "enqueue_tx_dequeue_abort_interleaved"); + try + { + string msg; + string xid; + string rmsg; + string rxid; + bool transientFlag; + bool externalFlag; + + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); + for (int m=0; m<NUM_MSGS; m++) + { + enq_msg(jc, 3*m, create_msg(msg, 3*m, MSG_SIZE), false); + create_xid(xid, 3*m, XID_SIZE); + deq_txn_msg(jc, 3*m, 3*m+1, xid); + jc.flush(); + read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_TXPENDING); + txn_abort(jc, 3*m+2, xid); + jc.flush(); + read_msg(jc, rmsg, rxid, transientFlag, externalFlag); + read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_EMPTY); + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_ut_enq_map.cpp b/qpid/cpp/src/tests/legacystore/jrnl/_ut_enq_map.cpp new file mode 100644 index 0000000000..f05dcb824c --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/_ut_enq_map.cpp @@ -0,0 +1,320 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "../unit_test.h" + +#include <iostream> +#include "qpid/legacystore/jrnl/enq_map.h" +#include "qpid/legacystore/jrnl/jerrno.h" + +using namespace boost::unit_test; +using namespace mrg::journal; +using namespace std; + +QPID_AUTO_TEST_SUITE(enq_map_suite) + +const string test_filename("_ut_enq_map"); + +QPID_AUTO_TEST_CASE(constructor) +{ + cout << test_filename << ".constructor: " << flush; + enq_map e1; + BOOST_CHECK(e1.empty()); + BOOST_CHECK_EQUAL(e1.size(), u_int32_t(0)); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(insert_get) +{ + cout << test_filename << ".insert_get: " << flush; + u_int16_t pfid; + u_int64_t rid; + u_int16_t pfid_start = 0x2000U; + u_int64_t rid_begin = 0xffffffff00000000ULL; + u_int64_t rid_end = 0xffffffff00000200ULL; + + // insert with no dups + u_int64_t rid_incr_1 = 4ULL; + enq_map e2; + e2.set_num_jfiles(pfid_start + (rid_end - rid_begin)/rid_incr_1); + for (rid = rid_begin, pfid = pfid_start; rid < rid_end; rid += rid_incr_1, pfid++) + BOOST_CHECK_EQUAL(e2.insert_pfid(rid, pfid), enq_map::EMAP_OK); + BOOST_CHECK(!e2.empty()); + BOOST_CHECK_EQUAL(e2.size(), u_int32_t(128)); + + // get + u_int64_t rid_incr_2 = 6ULL; + for (u_int64_t rid = rid_begin; rid < rid_end; rid += rid_incr_2) + { + BOOST_CHECK_EQUAL(e2.is_enqueued(rid), (rid%rid_incr_1 ? false : true)); + u_int16_t exp_pfid = pfid_start + (u_int16_t)((rid - rid_begin)/rid_incr_1); + int16_t ret_fid = e2.get_pfid(rid); + if (ret_fid < enq_map::EMAP_OK) // fail + { + BOOST_CHECK_EQUAL(ret_fid, enq_map::EMAP_RID_NOT_FOUND); + BOOST_CHECK(rid%rid_incr_1); + } + else + { + BOOST_CHECK_EQUAL(ret_fid, exp_pfid); + BOOST_CHECK(rid%rid_incr_1 == 0); + } + if ((rid + rid_incr_2)%(8 * rid_incr_2) == 0) + pfid++; + } + + // insert with dups + for (rid = rid_begin, pfid = pfid_start; rid < rid_end; rid += rid_incr_2, pfid++) + { + int16_t res = e2.insert_pfid(rid, pfid); + if (res < enq_map::EMAP_OK) // fail + { + BOOST_CHECK_EQUAL(res, enq_map::EMAP_DUP_RID); + BOOST_CHECK(rid%rid_incr_1 == 0); + } + else + BOOST_CHECK(rid%rid_incr_1); + } + BOOST_CHECK_EQUAL(e2.size(), u_int32_t(171)); + e2.clear(); + BOOST_CHECK(e2.empty()); + BOOST_CHECK_EQUAL(e2.size(), u_int32_t(0)); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(get_remove) +{ + cout << test_filename << ".get_remove: " << flush; + u_int16_t pfid; + u_int64_t rid; + u_int16_t pfid_start = 0x3000U; + u_int64_t rid_begin = 0xeeeeeeee00000000ULL; + u_int64_t rid_end = 0xeeeeeeee00000200ULL; + + u_int64_t rid_incr_1 = 4ULL; + u_int64_t num_incr_1 = (rid_end - rid_begin)/rid_incr_1; + enq_map e3; + e3.set_num_jfiles(pfid_start + (rid_end - rid_begin)/rid_incr_1); + for (rid = rid_begin, pfid = pfid_start; rid < rid_end; rid += rid_incr_1, pfid++) + BOOST_CHECK_EQUAL(e3.insert_pfid(rid, pfid), enq_map::EMAP_OK); + BOOST_CHECK_EQUAL(e3.size(), num_incr_1); + + u_int64_t rid_incr_2 = 6ULL; + for (rid = rid_begin, pfid = pfid_start; rid < rid_end; rid += rid_incr_2, pfid++) + { + u_int16_t exp_pfid = pfid_start + (u_int16_t)((rid - rid_begin)/rid_incr_1); + int16_t ret_fid = e3.get_remove_pfid(rid); + if (ret_fid < enq_map::EMAP_OK) // fail + { + BOOST_CHECK_EQUAL(ret_fid, enq_map::EMAP_RID_NOT_FOUND); + BOOST_CHECK(rid%rid_incr_1); + } + else + { + BOOST_CHECK_EQUAL(ret_fid, exp_pfid); + BOOST_CHECK(rid%rid_incr_1 == 0); + } + } + BOOST_CHECK_EQUAL(e3.size(), u_int32_t(85)); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(lock) +{ + cout << test_filename << ".lock: " << flush; + u_int16_t pfid; + u_int64_t rid; + u_int16_t pfid_start = 0x4000U; + u_int64_t rid_begin = 0xdddddddd00000000ULL; + u_int64_t rid_end = 0xdddddddd00000200ULL; + + // insert, every second entry is locked + u_int64_t rid_incr_1 = 4ULL; + u_int64_t num_incr_1 = (rid_end - rid_begin)/rid_incr_1; + bool locked = false; + enq_map e4; + e4.set_num_jfiles(pfid_start + (rid_end - rid_begin)/rid_incr_1); + for (rid = rid_begin, pfid = pfid_start; rid < rid_end; rid += rid_incr_1, pfid++) + { + BOOST_CHECK_EQUAL(e4.insert_pfid(rid, pfid, locked), enq_map::EMAP_OK); + locked = !locked; + } + BOOST_CHECK_EQUAL(e4.size(), num_incr_1); + + // unlock and lock non-existent rids + int16_t res = e4.lock(1ULL); + if (res < enq_map::EMAP_OK) + BOOST_CHECK_EQUAL(res, enq_map::EMAP_RID_NOT_FOUND); + else + BOOST_ERROR("Failed to detect locking non-existent rid."); + res = e4.unlock(2ULL); + if (res < enq_map::EMAP_OK) + BOOST_CHECK_EQUAL(res, enq_map::EMAP_RID_NOT_FOUND); + else + BOOST_ERROR("Failed to detect unlocking non-existent rid."); + + // get / unlock + for (u_int64_t rid = rid_begin; rid < rid_end; rid += rid_incr_1) + { + int16_t fid = e4.get_pfid(rid); + if (fid < enq_map::EMAP_OK) // fail + { + BOOST_CHECK_EQUAL(fid, enq_map::EMAP_LOCKED); + BOOST_CHECK(rid%(2*rid_incr_1)); + // unlock, read, then relock + BOOST_CHECK_EQUAL(e4.unlock(rid), enq_map::EMAP_OK); + BOOST_CHECK(e4.get_pfid(rid) >= enq_map::EMAP_OK); + BOOST_CHECK_EQUAL(e4.lock(rid), enq_map::EMAP_OK); + fid = e4.get_pfid(rid); + if (fid < enq_map::EMAP_OK) // fail + BOOST_CHECK_EQUAL(fid, enq_map::EMAP_LOCKED); + else + BOOST_ERROR("Failed to prevent getting locked record"); + } + } + + // remove all; if locked, use with txn_flag true; should ignore all locked records + for (u_int64_t rid = rid_begin; rid < rid_end; rid += rid_incr_1) + BOOST_CHECK(e4.get_remove_pfid(rid, true) >= enq_map::EMAP_OK); + BOOST_CHECK(e4.empty()); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(lists) +{ + cout << test_filename << ".lists: " << flush; + u_int16_t pfid; + u_int64_t rid; + u_int16_t pfid_start = 0x5000UL; + u_int64_t rid_begin = 0xdddddddd00000000ULL; + u_int64_t rid_end = 0xdddddddd00000200ULL; + + // insert, every second entry is locked + u_int64_t rid_incr_1 = 4ULL; + u_int64_t num_incr_1 = (rid_end - rid_begin)/rid_incr_1; + vector<u_int64_t> rid_list; + vector<u_int16_t> pfid_list; + enq_map e5; + e5.set_num_jfiles(pfid_start + (rid_end - rid_begin)/rid_incr_1); + for (rid = rid_begin, pfid = pfid_start; rid < rid_end; rid += rid_incr_1, pfid++) + { + BOOST_CHECK_EQUAL(e5.insert_pfid(rid, pfid), enq_map::EMAP_OK); + rid_list.push_back(rid); + pfid_list.push_back(pfid); + } + BOOST_CHECK_EQUAL(e5.size(), num_incr_1); + BOOST_CHECK_EQUAL(rid_list.size(), num_incr_1); + BOOST_CHECK_EQUAL(pfid_list.size(), num_incr_1); + + vector<u_int64_t> ret_rid_list; + e5.rid_list(ret_rid_list); + BOOST_CHECK_EQUAL(ret_rid_list.size(), num_incr_1); + for (unsigned i=0; i<ret_rid_list.size(); i++) + BOOST_CHECK_EQUAL(rid_list[i], ret_rid_list[i]); + + vector<u_int16_t> ret_pfid_list; + e5.pfid_list(ret_pfid_list); + BOOST_CHECK_EQUAL(ret_pfid_list.size(), num_incr_1); + for (unsigned i=0; i<ret_pfid_list.size(); i++) + BOOST_CHECK_EQUAL(pfid_list[i], ret_pfid_list[i]); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(enq_count) +{ + cout << test_filename << ".enq_count: " << flush; + + enq_map e6; + + // Check the allocation and cleanup as the file size is set both up and down + e6.set_num_jfiles(24); + e6.set_num_jfiles(0); + e6.set_num_jfiles(100); + e6.set_num_jfiles(4); + + // Add 100 enqueues to file 1, check that the counts match + for (u_int16_t pfid=0; pfid<4; pfid++) + BOOST_CHECK_EQUAL(e6.get_enq_cnt(pfid), u_int32_t(0)); + for (u_int64_t rid=0; rid<100; rid++) + BOOST_CHECK_EQUAL(e6.insert_pfid(rid, 1), enq_map::EMAP_OK); + for (u_int16_t pfid=0; pfid<4; pfid++) + { + if (pfid == 1) + BOOST_CHECK_EQUAL(e6.get_enq_cnt(pfid), u_int32_t(100)); + else + BOOST_CHECK_EQUAL(e6.get_enq_cnt(pfid), u_int32_t(0)); + } + + // Now remove 10 from file 1, check that the counts match + for (u_int64_t rid=0; rid<100; rid+=10) + //e6.Xget_remove_pfid(rid); + BOOST_CHECK(e6.get_remove_pfid(rid) >= enq_map::EMAP_OK); + for (u_int16_t pfid=0; pfid<4; pfid++) + { + if (pfid == 1) + BOOST_CHECK_EQUAL(e6.get_enq_cnt(pfid), u_int32_t(90)); + else + BOOST_CHECK_EQUAL(e6.get_enq_cnt(pfid), u_int32_t(0)); + } + + // Now resize the file up and make sure the count in file 1 still exists + e6.set_num_jfiles(8); + for (u_int16_t pfid=0; pfid<8; pfid++) + { + if (pfid == 1) + BOOST_CHECK_EQUAL(e6.get_enq_cnt(pfid), u_int32_t(90)); + else + BOOST_CHECK_EQUAL(e6.get_enq_cnt(pfid), u_int32_t(0)); + } + + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(stress) +{ + cout << test_filename << ".stress: " << flush; + u_int64_t rid; + u_int64_t rid_cnt; + u_int64_t rid_begin = 0xffffffff00000000ULL; + u_int64_t num_rid = 10; + + enq_map e7; + e7.set_num_jfiles(rid_begin + num_rid); + + // insert even rids with no dups + for (rid = rid_begin, rid_cnt = u_int64_t(0); rid_cnt < num_rid; rid += 2ULL, rid_cnt++) + BOOST_CHECK_EQUAL(e7.insert_pfid(rid, u_int16_t(0)), enq_map::EMAP_OK); + BOOST_CHECK_EQUAL(e7.size(), num_rid); + + // insert odd rids with no dups + for (rid = rid_begin + 1, rid_cnt = u_int64_t(0); rid_cnt < num_rid; rid += 2ULL, rid_cnt++) + BOOST_CHECK_EQUAL(e7.insert_pfid(rid, u_int16_t(0)), enq_map::EMAP_OK); + BOOST_CHECK_EQUAL(e7.size(), num_rid * 2); + + // remove even rids + for (rid = rid_begin, rid_cnt = u_int64_t(0); rid_cnt < num_rid; rid += 2ULL, rid_cnt++) + BOOST_CHECK(e7.get_remove_pfid(rid) >= enq_map::EMAP_OK); + BOOST_CHECK_EQUAL(e7.size(), num_rid); + + cout << "ok" << endl; +} + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_ut_jdir.cpp b/qpid/cpp/src/tests/legacystore/jrnl/_ut_jdir.cpp new file mode 100644 index 0000000000..b55d5ff8ef --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/_ut_jdir.cpp @@ -0,0 +1,416 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "../unit_test.h" + +#include <cerrno> +#include <cstring> +#include <dirent.h> +#include <fstream> +#include <iomanip> +#include <iostream> +#include "qpid/legacystore/jrnl/file_hdr.h" +#include "qpid/legacystore/jrnl/jcfg.h" +#include "qpid/legacystore/jrnl/jdir.h" +#include "qpid/legacystore/jrnl/jerrno.h" +#include "qpid/legacystore/jrnl/jexception.h" +#include <sys/stat.h> + +#define NUM_JFILES 4 +#define JFSIZE_SBLKS 128 + +#define ERRORSTR(e) std::strerror(e) << " (" << e << ")" +#define NUM_CLEAR_OPS 20 + +using namespace boost::unit_test; +using namespace mrg::journal; +using namespace std; + +QPID_AUTO_TEST_SUITE(jdir_suite) + +const string test_filename("_ut_jdir"); +const char* tdp = getenv("TMP_DATA_DIR"); +const string test_dir(tdp && strlen(tdp) > 0 ? string(tdp) + "/_ut_jdir" : "/var/tmp/_ut_jdir"); + +// === Helper functions === + +void create_file(const char* filename, mode_t fmode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) +{ + ofstream of(filename, ofstream::out | ofstream::trunc); + if (!of.good()) + BOOST_FAIL("Unable to open file " << filename << " for writing."); + of.write(filename, std::strlen(filename)); + of.close(); + ::chmod(filename, fmode); +} + +void create_file(const string filename, mode_t fmode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) +{ + create_file(filename.c_str(), fmode); +} + +void create_jdat_file(const char* dirname, const char* base_filename, u_int32_t fid, + u_int64_t first_rid) +{ + stringstream fn; + fn << dirname << "/" << base_filename << "."; + fn << setfill('0') << hex << setw(4) << fid << ".jdat"; + file_hdr fh(RHM_JDAT_FILE_MAGIC, RHM_JDAT_VERSION, 0, first_rid, fid, 0x200, true); + ofstream of(fn.str().c_str(), ofstream::out | ofstream::trunc); + if (!of.good()) + BOOST_FAIL("Unable to open journal data file " << fn.str() << " for writing."); + of.write((const char*)&fh, sizeof(file_hdr)); + of.close(); +} + +void create_jinf_file(const char* dirname, const char* base_filename) +{ + timespec ts; + ::clock_gettime(CLOCK_REALTIME, &ts); + jinf ji("test journal id", dirname, base_filename, NUM_JFILES, false, 0, JFSIZE_SBLKS, + JRNL_WMGR_DEF_PAGE_SIZE, JRNL_WMGR_DEF_PAGES, ts); + ji.write(); +} + +void create_jrnl_fileset(const char* dirname, const char* base_filename) +{ + create_jinf_file(dirname, base_filename); + for (u_int32_t fid = 0; fid < NUM_JFILES; fid++) + { + u_int64_t rid = 0x12340000 + (fid * 0x25); + create_jdat_file(dirname, base_filename, fid, rid); + } +} + +unsigned count_dir_contents(const char* dirname, bool incl_files, bool incl_dirs = true) +{ + struct dirent* entry; + struct stat s; + unsigned file_cnt = 0; + unsigned dir_cnt = 0; + unsigned other_cnt = 0; + DIR* dir = ::opendir(dirname); + if (!dir) + BOOST_FAIL("Unable to open directory " << dirname); + while ((entry = ::readdir(dir)) != NULL) + { + // Ignore . and .. + if (std::strcmp(entry->d_name, ".") != 0 && std::strcmp(entry->d_name, "..") != 0) + { + stringstream fn; + fn << dirname << "/" << entry->d_name; + if (::stat(fn.str().c_str(), &s)) + BOOST_FAIL("Unable to stat dir entry " << entry->d_name << "; err=" << + ERRORSTR(errno)); + if (S_ISREG(s.st_mode)) + file_cnt++; + else if (S_ISDIR(s.st_mode)) + dir_cnt++; + else + other_cnt++; + } + } + ::closedir(dir); + if (incl_files) + { + if (incl_dirs) + return file_cnt + dir_cnt; + return file_cnt; + } + else if (incl_dirs) + return dir_cnt; + return other_cnt; +} + +void check_dir_contents(const char* dirname, const char* base_filename, unsigned num_subdirs, + bool jrnl_present) +{ + if (jdir::is_dir(dirname)) + { + // Subdir count + BOOST_CHECK_EQUAL(count_dir_contents(dirname, false, true), num_subdirs); + + // Journal file count + unsigned num_jrnl_files = jrnl_present ? NUM_JFILES + 1 : 0; + BOOST_CHECK_EQUAL(count_dir_contents(dirname, true, false), num_jrnl_files); + + // Check journal files are present + if (jrnl_present) + try { jdir::verify_dir(dirname, base_filename); } + catch(const jexception& e) { BOOST_ERROR(e); } + for (unsigned subdir_num = 1; subdir_num <= num_subdirs; subdir_num++) + { + stringstream subdir_name; + subdir_name << dirname << "/_" << base_filename << ".bak."; + subdir_name << hex << setfill('0') << setw(4) << subdir_num; + try { jdir::verify_dir(subdir_name.str().c_str(), base_filename); } + catch(const jexception& e) { BOOST_ERROR(e); } + } + } + else + BOOST_ERROR(dirname << " is not a directory"); +} + +void check_dir_not_existing(const char* dirname) +{ + if (jdir::exists(dirname) && jdir::is_dir(dirname)) + jdir::delete_dir(dirname); + if (jdir::exists(dirname)) + BOOST_FAIL("Unable to remove directory " << dirname); +} + +void check_dir_not_existing(const string dirname) +{ + check_dir_not_existing(dirname.c_str()); +} + +// === Test suite === + +QPID_AUTO_TEST_CASE(constructor) +{ + cout << test_filename << ".constructor: " << flush; + string dir(test_dir + "/A/B/C/D/E/F"); + string bfn("test_base"); + jdir dir1(dir, bfn); + BOOST_CHECK(dir1.dirname().compare(dir) == 0); + BOOST_CHECK(dir1.base_filename().compare(bfn) == 0); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(create_delete_dir) +{ + cout << test_filename << ".create_delete_dir: " << flush; + // Use instance + string dir_A(test_dir + "/A"); + string dir_Ats(test_dir + "/A/"); // trailing '/' + check_dir_not_existing(test_dir + "/A"); + jdir dir1(dir_A, "test_base"); + dir1.create_dir(); + // check all combos of jdir::exists and jdir::is_dir() + BOOST_CHECK(jdir::exists(dir_A)); + BOOST_CHECK(jdir::exists(dir_Ats)); + BOOST_CHECK(jdir::exists(dir_A.c_str())); + BOOST_CHECK(jdir::exists(dir_Ats.c_str())); + BOOST_CHECK(jdir::is_dir(dir_A)); + BOOST_CHECK(jdir::is_dir(dir_Ats)); + BOOST_CHECK(jdir::is_dir(dir_Ats.c_str())); + BOOST_CHECK(jdir::is_dir(dir_Ats.c_str())); + // do it a second time when dir exists + dir1.create_dir(); + BOOST_CHECK(jdir::is_dir(dir_A)); + dir1.delete_dir(); + BOOST_CHECK(!jdir::exists(dir_A)); + + // Use static fn + check_dir_not_existing(test_dir + "/B"); + jdir::create_dir(test_dir + "/B"); + BOOST_CHECK(jdir::is_dir(test_dir + "/B")); + jdir::create_dir(test_dir + "/B"); + BOOST_CHECK(jdir::is_dir(test_dir + "/B")); + jdir::delete_dir(test_dir + "/B"); + BOOST_CHECK(!jdir::exists(test_dir + "/B")); + + // Non-empty dirs + check_dir_not_existing(test_dir + "/C"); + jdir::create_dir(test_dir + "/C"); + BOOST_CHECK(jdir::is_dir(test_dir + "/C")); + create_file(test_dir + "/C/test_file_1.txt"); // mode 644 (default) + create_file(test_dir + "/C/test_file_2.txt", S_IRWXU | S_IRWXG | S_IRWXO); // mode 777 + create_file(test_dir + "/C/test_file_3.txt", S_IRUSR | S_IRGRP | S_IROTH); // mode 444 (read-only) + create_file(test_dir + "/C/test_file_4.txt", 0); // mode 000 (no permissions) + BOOST_CHECK(jdir::is_dir(test_dir + "/C")); + jdir::create_dir(test_dir + "/C"); + BOOST_CHECK(jdir::is_dir(test_dir + "/C")); + jdir::delete_dir(test_dir + "/C"); + BOOST_CHECK(!jdir::exists(test_dir + "/C")); + + // Check non-existent dirs fail + check_dir_not_existing(test_dir + "/D"); + try + { + jdir::is_dir(test_dir + "/D"); + BOOST_ERROR("jdir::is_dir() failed to throw jexeption for non-existent directory."); + } + catch(const jexception& e) + { + BOOST_CHECK_EQUAL(e.err_code(), jerrno::JERR_JDIR_STAT); + } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(create_delete_dir_recursive) +{ + cout << test_filename << ".create_delete_dir_recursive: " << flush; + // Use instances + check_dir_not_existing(test_dir + "/E"); + jdir dir1(test_dir + "/E/F/G/H", "test_base"); + dir1.create_dir(); + BOOST_CHECK(jdir::is_dir(test_dir + "/E/F/G/H")); + dir1.delete_dir(); + BOOST_CHECK(!jdir::exists(test_dir + "/E/F/G/H")); // only H deleted, E/F/G remain + BOOST_CHECK(jdir::exists(test_dir + "/E/F/G")); + jdir::delete_dir(test_dir + "/E"); // delete remaining dirs + BOOST_CHECK(!jdir::exists(test_dir + "/E")); + + check_dir_not_existing(test_dir + "/F"); + jdir dir2(test_dir + "/F/G/H/I/", "test_base"); // trailing '/' + dir2.create_dir(); + BOOST_CHECK(jdir::is_dir(test_dir + "/F/G/H/I/")); + dir2.delete_dir(); + BOOST_CHECK(!jdir::exists(test_dir + "/F/G/H/I/")); + BOOST_CHECK(jdir::exists(test_dir + "/F/G/H/")); + jdir::delete_dir(test_dir + "/F"); + BOOST_CHECK(!jdir::exists(test_dir + "/F")); + + check_dir_not_existing(test_dir + "/G"); + jdir dir3(test_dir + "/G/H//I//J", "test_base"); // extra '/' in path + dir3.create_dir(); + BOOST_CHECK(jdir::is_dir(test_dir + "/G/H//I//J")); + dir3.delete_dir(); + BOOST_CHECK(!jdir::exists(test_dir + "/G/H//I//J")); + BOOST_CHECK(jdir::exists(test_dir + "/G/H//I")); + jdir::delete_dir(test_dir + "/F"); + BOOST_CHECK(!jdir::exists(test_dir + "/F")); + + // Use static fn + check_dir_not_existing(test_dir + "/H"); + jdir::create_dir(test_dir + "/H/I/J/K"); + BOOST_CHECK(jdir::is_dir(test_dir + "/H/I/J/K")); + jdir::delete_dir(test_dir + "/H/I/J/K"); + BOOST_CHECK(!jdir::exists(test_dir + "/H/I/J/K")); // only J deleted, H/I/J remain + BOOST_CHECK(jdir::exists(test_dir + "/H/I/J")); + jdir::delete_dir(test_dir + "/H"); + BOOST_CHECK(!jdir::exists(test_dir + "/H")); + + check_dir_not_existing(test_dir + "/I"); + jdir::create_dir(test_dir + "/I/J/K/L/"); // trailing '/' + BOOST_CHECK(jdir::is_dir(test_dir + "/I/J/K/L/")); + jdir::delete_dir(test_dir + "/I/J/K/L/"); + BOOST_CHECK(!jdir::exists(test_dir + "/I/J/K/L/")); + BOOST_CHECK(jdir::exists(test_dir + "/I/J/K/")); + jdir::delete_dir(test_dir + "/I"); + BOOST_CHECK(!jdir::exists(test_dir + "/I")); + + check_dir_not_existing(test_dir + "//J"); + jdir::create_dir(test_dir + "//J//K//L//M"); // extra '/' in path + BOOST_CHECK(jdir::is_dir(test_dir + "//J//K//L//M")); + jdir::delete_dir(test_dir + "//J//K//L//M"); + BOOST_CHECK(!jdir::exists(test_dir + "//J//K//L//M")); + BOOST_CHECK(jdir::exists(test_dir + "//J//K//L")); + jdir::delete_dir(test_dir + "//J"); + BOOST_CHECK(!jdir::exists(test_dir + "//J")); + + // Non-empty dirs + check_dir_not_existing(test_dir + "/K"); + jdir::create_dir(test_dir + "/K/L/M1/N1"); + jdir::create_dir(test_dir + "/K/L/M1/N2"); + jdir::create_dir(test_dir + "/K/L/M1/N3"); + jdir::create_dir(test_dir + "/K/L/M1/N4"); + create_file(test_dir + "/K/L/M1/N4/test_file_1.txt"); // mode 644 (default) + create_file(test_dir + "/K/L/M1/N4/test_file_2.txt", S_IRWXU | S_IRWXG | S_IRWXO); // mode 777 + create_file(test_dir + "/K/L/M1/N4/test_file_3.txt", S_IRUSR | S_IRGRP | S_IROTH); // mode 444 + create_file(test_dir + "/K/L/M1/N4/test_file_4.txt", 0); // mode 000 (no permissions) + jdir::create_dir(test_dir + "/K/L/M2"); + jdir::create_dir(test_dir + "/K/L/M3/N5"); + jdir::create_dir(test_dir + "/K/L/M3/N6"); + BOOST_CHECK(jdir::is_dir(test_dir + "/K/L/M1/N1")); + BOOST_CHECK(jdir::is_dir(test_dir + "/K/L/M1/N2")); + BOOST_CHECK(jdir::is_dir(test_dir + "/K/L/M1/N3")); + BOOST_CHECK(jdir::is_dir(test_dir + "/K/L/M1/N4")); + BOOST_CHECK(jdir::is_dir(test_dir + "/K/L/M2")); + BOOST_CHECK(jdir::is_dir(test_dir + "/K/L/M3/N5")); + BOOST_CHECK(jdir::is_dir(test_dir + "/K/L/M3/N6")); + jdir::delete_dir(test_dir + "/K"); + BOOST_CHECK(!jdir::exists(test_dir + "/K")); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(clear_verify_dir) +{ + cout << test_filename << ".clear_verify_dir: " << flush; + // Use instances + const char* jrnl_dir = "/var/tmp/test_dir_1"; + const char* bfn = "test_base"; + check_dir_not_existing(jrnl_dir); + jdir test_dir_1(jrnl_dir, bfn); + test_dir_1.create_dir(); + BOOST_CHECK(jdir::is_dir(jrnl_dir)); + // add journal files, check they exist, then clear them + unsigned cnt = 0; + while (cnt < NUM_CLEAR_OPS) + { + create_jrnl_fileset(jrnl_dir, bfn); + check_dir_contents(jrnl_dir, bfn, cnt, true); + test_dir_1.clear_dir(); + check_dir_contents(jrnl_dir, bfn, ++cnt, false); + } + // clean up + test_dir_1.delete_dir(); + BOOST_CHECK(!jdir::exists(jrnl_dir)); + + // Non-existent dir with auto-create true + jrnl_dir = "/var/tmp/test_dir_2"; + check_dir_not_existing(jrnl_dir); + jdir test_dir_2(jrnl_dir, bfn); + // clear dir + test_dir_2.clear_dir(); // create flag is true by default + check_dir_contents(jrnl_dir, bfn, 0, false); + // clear empty dir, should not create subdir + test_dir_2.clear_dir(); // create flag is true by default + check_dir_contents(jrnl_dir, bfn, 0, false); + // clean up + test_dir_2.delete_dir(); + BOOST_CHECK(!jdir::exists(jrnl_dir)); + + // non-existent dir with auto-create false + jrnl_dir = "/var/tmp/test_dir_3"; + check_dir_not_existing(jrnl_dir); + jdir test_dir_3(jrnl_dir, bfn); + try + { + test_dir_3.clear_dir(false); + BOOST_ERROR("jdir::clear_dir(flase) failed to throw jexeption for non-existent directory."); + } + catch(const jexception& e) + { + BOOST_CHECK_EQUAL(e.err_code(), jerrno::JERR_JDIR_OPENDIR); + } + + // Use static fn + jrnl_dir = "/var/tmp/test_dir_4"; + check_dir_not_existing(jrnl_dir); + jdir::clear_dir(jrnl_dir, bfn); // should create dir if it does not exist + // add journal files, check they exist, then clear them + cnt = 0; + while (cnt < NUM_CLEAR_OPS) + { + create_jrnl_fileset(jrnl_dir, bfn); + check_dir_contents(jrnl_dir, bfn, cnt, true); + jdir::clear_dir(jrnl_dir, bfn); + check_dir_contents(jrnl_dir, bfn, ++cnt, false); + } + // clean up + jdir::delete_dir(jrnl_dir); + BOOST_CHECK(!jdir::exists(jrnl_dir)); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_ut_jerrno.cpp b/qpid/cpp/src/tests/legacystore/jrnl/_ut_jerrno.cpp new file mode 100644 index 0000000000..1ae1138355 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/_ut_jerrno.cpp @@ -0,0 +1,47 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "../unit_test.h" + +#include <cstring> +#include <iostream> +#include "qpid/legacystore/jrnl/jerrno.h" + +using namespace boost::unit_test; +using namespace mrg::journal; +using namespace std; + +QPID_AUTO_TEST_SUITE(jerrno_suite) +using namespace mrg::journal; + +const string test_filename("_ut_jerrno"); + +QPID_AUTO_TEST_CASE(jerrno_val) +{ + cout << test_filename << ".jerrno_val: " << flush; + const char* m = "JERR__MALLOC"; + string malloc_msg = string(jerrno::err_msg(jerrno::JERR__MALLOC)); + BOOST_CHECK(malloc_msg.substr(0, std::strlen(m)).compare(m) == 0); + BOOST_CHECK(std::strcmp(jerrno::err_msg(0), "<Unknown error code>") == 0); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_ut_jexception.cpp b/qpid/cpp/src/tests/legacystore/jrnl/_ut_jexception.cpp new file mode 100644 index 0000000000..8b9e876aa6 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/_ut_jexception.cpp @@ -0,0 +1,346 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "../unit_test.h" + +#include <cstring> +#include <iostream> +#include "qpid/legacystore/jrnl/jerrno.h" +#include "qpid/legacystore/jrnl/jexception.h" + +using namespace boost::unit_test; +using namespace mrg::journal; +using namespace std; + +QPID_AUTO_TEST_SUITE(jexception_suite) + +const string test_filename("_ut_jexception"); + +// === Helper functions === + +void throw_exception(const jexception& e, std::size_t what_len, std::size_t ai_len, + std::size_t tc_len, std::size_t tf_len) +{ + try { throw e; } + catch (const jexception& e) + { + BOOST_CHECK_EQUAL(std::strlen(e.what()), what_len); + BOOST_CHECK_EQUAL(e.additional_info().size(), ai_len); + BOOST_CHECK_EQUAL(e.throwing_class().size(), tc_len); + BOOST_CHECK_EQUAL(e.throwing_fn().size(), tf_len); + } +} + +void throw_exception(const jexception& e, std::size_t what_len, std::size_t ai_len) +{ + throw_exception(e, what_len, ai_len, 0, 0); +} + +void throw_exception(const jexception& e, std::size_t what_len, std::size_t tc_len, + std::size_t tf_len) +{ + throw_exception(e, what_len, 0, tc_len, tf_len); +} + +// === Test suite === + +QPID_AUTO_TEST_CASE(constructor_1) +{ + cout << test_filename << ".constructor_1: " << flush; + try + { + jexception e1; + BOOST_CHECK_EQUAL(e1.err_code(), (u_int32_t)0); + BOOST_CHECK(e1.additional_info().size() == 0); + BOOST_CHECK(e1.throwing_class().size() == 0); + BOOST_CHECK(e1.throwing_fn().size() == 0); + BOOST_CHECK(std::strlen(e1.what()) > 0); + throw e1; + } + catch (const jexception& e) + { + BOOST_CHECK_EQUAL(e.err_code(), (u_int32_t)0); + BOOST_CHECK(e.additional_info().size() == 0); + BOOST_CHECK(e.throwing_class().size() == 0); + BOOST_CHECK(e.throwing_fn().size() == 0); + BOOST_CHECK(std::strlen(e.what()) > 0); + } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(constructor_2) +{ + cout << test_filename << ".constructor_2: " << flush; + const u_int32_t err_code = 2; + try + { + jexception e2(err_code); + BOOST_CHECK_EQUAL(e2.err_code(), err_code); + BOOST_CHECK(e2.additional_info().size() == 0); + BOOST_CHECK(e2.throwing_class().size() == 0); + BOOST_CHECK(e2.throwing_fn().size() == 0); + BOOST_CHECK(std::strlen(e2.what()) > 0); + throw e2; + } + catch (const jexception& e) + { + BOOST_CHECK_EQUAL(e.err_code(), err_code); + BOOST_CHECK(e.additional_info().size() == 0); + BOOST_CHECK(e.throwing_class().size() == 0); + BOOST_CHECK(e.throwing_fn().size() == 0); + BOOST_CHECK(std::strlen(e.what()) > 0); + } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(constructor_3a) +{ + cout << test_filename << ".constructor_3a: " << flush; + const char* err_msg = "exception3"; + try + { + jexception e3(err_msg); + BOOST_CHECK_EQUAL(e3.err_code(), (u_int32_t)0); + BOOST_CHECK(e3.additional_info().compare(err_msg) == 0); + BOOST_CHECK(e3.throwing_class().size() == 0); + BOOST_CHECK(e3.throwing_fn().size() == 0); + BOOST_CHECK(std::strlen(e3.what()) > 0); + throw e3; + } + catch (const jexception& e) + { + BOOST_CHECK_EQUAL(e.err_code(), (u_int32_t)0); + BOOST_CHECK(e.additional_info().compare(err_msg) == 0); + BOOST_CHECK(e.throwing_class().size() == 0); + BOOST_CHECK(e.throwing_fn().size() == 0); + BOOST_CHECK(std::strlen(e.what()) > 0); + } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(constructor_3b) +{ + cout << test_filename << ".constructor_3b: " << flush; + const string err_msg("exception3"); + try + { + jexception e3(err_msg); + BOOST_CHECK_EQUAL(e3.err_code(), (u_int32_t)0); + BOOST_CHECK(e3.additional_info().compare(err_msg) == 0); + BOOST_CHECK(e3.throwing_class().size() == 0); + BOOST_CHECK(e3.throwing_fn().size() == 0); + BOOST_CHECK(std::strlen(e3.what()) > 0); + throw e3; + } + catch (const jexception& e) + { + BOOST_CHECK_EQUAL(e.err_code(), (u_int32_t)0); + BOOST_CHECK(e.additional_info().compare(err_msg) == 0); + BOOST_CHECK(e.throwing_class().size() == 0); + BOOST_CHECK(e.throwing_fn().size() == 0); + BOOST_CHECK(std::strlen(e.what()) > 0); + } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(constructor_4a) +{ + cout << test_filename << ".constructor_4a: " << flush; + const u_int32_t err_code = 4; + const char* err_msg = "exception4"; + try + { + jexception e4(err_code, err_msg); + BOOST_CHECK_EQUAL(e4.err_code(), err_code); + BOOST_CHECK(e4.additional_info().compare(err_msg) == 0); + BOOST_CHECK(e4.throwing_class().size() == 0); + BOOST_CHECK(e4.throwing_fn().size() == 0); + BOOST_CHECK(std::strlen(e4.what()) > 0); + throw e4; + } + catch (const jexception& e) + { + BOOST_CHECK_EQUAL(e.err_code(), err_code); + BOOST_CHECK(e.additional_info().compare(err_msg) == 0); + BOOST_CHECK(e.throwing_class().size() == 0); + BOOST_CHECK(e.throwing_fn().size() == 0); + BOOST_CHECK(std::strlen(e.what()) > 0); + } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(constructor_4b) +{ + cout << test_filename << ".constructor_4b: " << flush; + const u_int32_t err_code = 4; + const string err_msg("exception4"); + try + { + jexception e4(err_code, err_msg); + BOOST_CHECK_EQUAL(e4.err_code(), err_code); + BOOST_CHECK(e4.additional_info().compare(err_msg) == 0); + BOOST_CHECK(e4.throwing_class().size() == 0); + BOOST_CHECK(e4.throwing_fn().size() == 0); + BOOST_CHECK(std::strlen(e4.what()) > 0); + throw e4; + } + catch (const jexception& e) + { + BOOST_CHECK_EQUAL(e.err_code(), err_code); + BOOST_CHECK(e.additional_info().compare(err_msg) == 0); + BOOST_CHECK(e.throwing_class().size() == 0); + BOOST_CHECK(e.throwing_fn().size() == 0); + BOOST_CHECK(std::strlen(e.what()) > 0); + } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(constructor_5a) +{ + cout << test_filename << ".constructor_5a: " << flush; + const u_int32_t err_code = 5; + const char* err_class = "class5"; + const char* err_fn = "fn5"; + try + { + jexception e5(err_code, err_class, err_fn); + BOOST_CHECK_EQUAL(e5.err_code(), err_code); + BOOST_CHECK(e5.additional_info().size() == 0); + BOOST_CHECK(e5.throwing_class().compare(err_class) == 0); + BOOST_CHECK(e5.throwing_fn().compare(err_fn) == 0); + BOOST_CHECK(std::strlen(e5.what()) > 0); + throw e5; + } + catch (const jexception& e) + { + BOOST_CHECK_EQUAL(e.err_code(), err_code); + BOOST_CHECK(e.additional_info().size() == 0); + BOOST_CHECK(e.throwing_class().compare(err_class) == 0); + BOOST_CHECK(e.throwing_fn().compare(err_fn) == 0); + BOOST_CHECK(std::strlen(e.what()) > 0); + } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(constructor_5b) +{ + cout << test_filename << ".constructor_5b: " << flush; + const u_int32_t err_code = 5; + const string err_class("class5"); + const string err_fn("fn5"); + try + { + jexception e5(err_code, err_class, err_fn); + BOOST_CHECK_EQUAL(e5.err_code(), err_code); + BOOST_CHECK(e5.additional_info().size() == 0); + BOOST_CHECK(e5.throwing_class().compare(err_class) == 0); + BOOST_CHECK(e5.throwing_fn().compare(err_fn) == 0); + BOOST_CHECK(std::strlen(e5.what()) > 0); + throw e5; + } + catch (const jexception& e) + { + BOOST_CHECK_EQUAL(e.err_code(), err_code); + BOOST_CHECK(e.additional_info().size() == 0); + BOOST_CHECK(e.throwing_class().compare(err_class) == 0); + BOOST_CHECK(e.throwing_fn().compare(err_fn) == 0); + BOOST_CHECK(std::strlen(e.what()) > 0); + } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(constructor_6a) +{ + cout << test_filename << ".constructor_6a: " << flush; + const u_int32_t err_code = 6; + const char* err_msg = "exception6"; + const char* err_class = "class6"; + const char* err_fn = "fn6"; + try + { + jexception e6(err_code, err_msg, err_class, err_fn); + BOOST_CHECK_EQUAL(e6.err_code(), err_code); + BOOST_CHECK(e6.additional_info().compare(err_msg) == 0); + BOOST_CHECK(e6.throwing_class().compare(err_class) == 0); + BOOST_CHECK(e6.throwing_fn().compare(err_fn) == 0); + BOOST_CHECK(std::strlen(e6.what()) > 0); + throw e6; + } + catch (const jexception& e) + { + BOOST_CHECK_EQUAL(e.err_code(), err_code); + BOOST_CHECK(e.additional_info().compare(err_msg) == 0); + BOOST_CHECK(e.throwing_class().compare(err_class) == 0); + BOOST_CHECK(e.throwing_fn().compare(err_fn) == 0); + BOOST_CHECK(std::strlen(e.what()) > 0); + } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(constructor_6b) +{ + cout << test_filename << ".constructor_6b: " << flush; + const u_int32_t err_code = 6; + const string err_msg("exception6"); + const string err_class("class6"); + const string err_fn("fn6"); + try + { + jexception e6(err_code, err_msg, err_class, err_fn); + BOOST_CHECK_EQUAL(e6.err_code(), err_code); + BOOST_CHECK(e6.additional_info().compare(err_msg) == 0); + BOOST_CHECK(e6.throwing_class().compare(err_class) == 0); + BOOST_CHECK(e6.throwing_fn().compare(err_fn) == 0); + BOOST_CHECK(std::strlen(e6.what()) > 0); + throw e6; + } + catch (const jexception& e) + { + BOOST_CHECK_EQUAL(e.err_code(), err_code); + BOOST_CHECK(e.additional_info().compare(err_msg) == 0); + BOOST_CHECK(e.throwing_class().compare(err_class) == 0); + BOOST_CHECK(e.throwing_fn().compare(err_fn) == 0); + BOOST_CHECK(std::strlen(e.what()) > 0); + } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(msg_scope) +{ + cout << test_filename << ".msg_scope: " << flush; + try + { + // These will go out of scope as soon as jexception is thrown... + const string msg("Error message"); + const string cls("class"); + const string fn("function"); + throw jexception(100, msg, cls, fn); + } + catch (const jexception& e) + { + stringstream ss; + ss << e; + BOOST_CHECK(ss.str().size() > 0); + } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_ut_jinf.cpp b/qpid/cpp/src/tests/legacystore/jrnl/_ut_jinf.cpp new file mode 100644 index 0000000000..f239139306 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/_ut_jinf.cpp @@ -0,0 +1,402 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "../unit_test.h" + +#include <cmath> +#include <iostream> +#include "qpid/legacystore/jrnl/jcntl.h" + +using namespace boost::unit_test; +using namespace mrg::journal; +using namespace std; + +QPID_AUTO_TEST_SUITE(jinf_suite) + +const string test_filename("_ut_jinf"); + +#include "_st_helper_fns.h" + +timespec ts; + +QPID_AUTO_TEST_CASE(write_constructor) +{ + string test_name = get_test_name(test_filename, "write_constructor"); + const string jid = test_name + "_jid"; + const string base_filename = test_name + "_bfn"; + jdir::create_dir(test_dir); // Check test dir exists; create it if not + ::clock_gettime(CLOCK_REALTIME, &ts); + jinf ji(jid, test_dir, base_filename, NUM_JFILES, false, 0, JFSIZE_SBLKS, JRNL_WMGR_DEF_PAGE_SIZE, JRNL_WMGR_DEF_PAGES, ts); + BOOST_CHECK_EQUAL(ji.jver(), RHM_JDAT_VERSION); + BOOST_CHECK(ji.jid().compare(jid) == 0); + BOOST_CHECK(ji.jdir().compare(test_dir) == 0); + BOOST_CHECK(ji.base_filename().compare(base_filename) == 0); + const timespec this_ts = ji.ts(); + BOOST_CHECK_EQUAL(this_ts.tv_sec, ts.tv_sec); + BOOST_CHECK_EQUAL(this_ts.tv_nsec, ts.tv_nsec); + BOOST_CHECK_EQUAL(ji.num_jfiles(), u_int16_t(NUM_JFILES)); + BOOST_CHECK_EQUAL(ji.is_ae(), false); + BOOST_CHECK_EQUAL(ji.ae_max_jfiles(), u_int16_t(0)); + BOOST_CHECK_EQUAL(ji.jfsize_sblks(), u_int32_t(JFSIZE_SBLKS)); + BOOST_CHECK_EQUAL(ji.sblk_size_dblks(), u_int16_t(JRNL_SBLK_SIZE)); + BOOST_CHECK_EQUAL(ji.dblk_size(), u_int32_t(JRNL_DBLK_SIZE)); + BOOST_CHECK_EQUAL(ji.wcache_pgsize_sblks(), u_int32_t(JRNL_WMGR_DEF_PAGE_SIZE)); + BOOST_CHECK_EQUAL(ji.wcache_num_pages(), u_int16_t(JRNL_WMGR_DEF_PAGES)); + BOOST_CHECK_EQUAL(ji.rcache_pgsize_sblks(), u_int32_t(JRNL_RMGR_PAGE_SIZE)); + BOOST_CHECK_EQUAL(ji.rcache_num_pages(), u_int16_t(JRNL_RMGR_PAGES)); + ji.write(); + cout << "done" << endl; +} + +QPID_AUTO_TEST_CASE(read_constructor) +{ + string test_name = get_test_name(test_filename, "read_constructor"); + const string jid = test_name + "_jid"; + const string base_filename = test_name + "_bfn"; + lfid_pfid_map::create_new_jinf(jid, base_filename, false); + + stringstream fn; + fn << test_dir << "/" <<base_filename << "." << JRNL_INFO_EXTENSION; + jinf ji(fn.str(), false); + BOOST_CHECK_EQUAL(ji.jver(), RHM_JDAT_VERSION); + BOOST_CHECK(ji.jid().compare(jid) == 0); + BOOST_CHECK(ji.jdir().compare(test_dir) == 0); + BOOST_CHECK(ji.base_filename().compare(base_filename) == 0); +// const timespec this_ts = ji.ts(); +// BOOST_CHECK_EQUAL(this_ts.tv_sec, ts.tv_sec); +// BOOST_CHECK_EQUAL(this_ts.tv_nsec, ts.tv_nsec); + BOOST_CHECK_EQUAL(ji.num_jfiles(), u_int16_t(NUM_JFILES)); + BOOST_CHECK_EQUAL(ji.is_ae(), false); + BOOST_CHECK_EQUAL(ji.ae_max_jfiles(), u_int16_t(0)); + BOOST_CHECK_EQUAL(ji.jfsize_sblks(), u_int32_t(JFSIZE_SBLKS)); + BOOST_CHECK_EQUAL(ji.sblk_size_dblks(), u_int16_t(JRNL_SBLK_SIZE)); + BOOST_CHECK_EQUAL(ji.dblk_size(), u_int32_t(JRNL_DBLK_SIZE)); + BOOST_CHECK_EQUAL(ji.wcache_pgsize_sblks(), u_int32_t(JRNL_WMGR_DEF_PAGE_SIZE)); + BOOST_CHECK_EQUAL(ji.wcache_num_pages(), u_int16_t(JRNL_WMGR_DEF_PAGES)); + BOOST_CHECK_EQUAL(ji.rcache_pgsize_sblks(), u_int32_t(JRNL_RMGR_PAGE_SIZE)); + BOOST_CHECK_EQUAL(ji.rcache_num_pages(), u_int16_t(JRNL_RMGR_PAGES)); + + cout << "done" << endl; +} + +QPID_AUTO_TEST_CASE(set_functions) +{ + string test_name = get_test_name(test_filename, "set_functions"); + const string jid = test_name + "_jid"; + const string base_filename = test_name + "_bfn"; + lfid_pfid_map::create_new_jinf(jid, base_filename, false); + + stringstream fn; + fn << test_dir << "/" << base_filename << "." << JRNL_INFO_EXTENSION; + jinf ji(fn.str(), false); + + ji.set_jdir("abc123"); + BOOST_CHECK(ji.jdir().compare("abc123") == 0); + ji.set_jdir(test_dir); + BOOST_CHECK(ji.jdir().compare(test_dir) == 0); + ji.incr_num_jfiles(); + BOOST_CHECK_EQUAL(ji.num_jfiles(), u_int16_t(NUM_JFILES+1)); + ji.incr_num_jfiles(); + BOOST_CHECK_EQUAL(ji.num_jfiles(), u_int16_t(NUM_JFILES+2)); + + lfid_pfid_map::clean_journal_info_file(test_dir); + cout << "done" << endl; +} + +QPID_AUTO_TEST_CASE(validate) +{ + string test_name = get_test_name(test_filename, "validate"); + const string jid = test_name + "_jid"; + const string base_filename = test_name + "_bfn"; + lfid_pfid_map::create_new_jinf(jid, base_filename, false); + + stringstream fn; + fn << test_dir << "/" << base_filename << "." << JRNL_INFO_EXTENSION; + jinf ji(fn.str(), true); + // TODO: Check validation picks up conflict, but need to be friend to jinf to do it + + lfid_pfid_map::clean_journal_info_file(test_dir); + cout << "done" << endl; +} + +QPID_AUTO_TEST_CASE(analyze_empty_journal) +{ + string test_name = get_test_name(test_filename, "analyze_empty_journal"); + const string jid = test_name + "_jid"; + const string base_filename = test_name + "_bfn"; + jdir::create_dir(test_dir); // Check test dir exists; create it if not + + lfid_pfid_map m(jid, base_filename); + m.journal_create(NUM_JFILES, 0, 0); + m.write_journal(false, 0); + + stringstream fn; + fn << test_dir << "/" << base_filename << "." << JRNL_INFO_EXTENSION; + jinf ji(fn.str(), false); + try { ji.analyze(); } + catch (const jexception& e) + { + if (e.err_code() != jerrno::JERR_JINF_JDATEMPTY) + BOOST_ERROR("Failed to throw expected exception jerrno::JERR_JINF_JDATEMPTY"); + } + + m.destroy_journal(); + cout << "done" << endl; +} + +QPID_AUTO_TEST_CASE(analyze_part_full_journal) +{ + string test_name = get_test_name(test_filename, "analyze_part_full_journal"); + const string jid = test_name + "_jid"; + const string base_filename = test_name + "_bfn"; + lfid_pfid_map m(jid, base_filename); + for (u_int16_t num_files = 1; num_files < NUM_JFILES; num_files++) + { + m.journal_create(NUM_JFILES, num_files, 0); + m.write_journal(false, 0); + + stringstream fn; + fn << test_dir << "/" << base_filename << "." << JRNL_INFO_EXTENSION; + jinf ji(fn.str(), false); + ji.analyze(); + m.check_analysis(ji); + + m.destroy_journal(); + } + cout << "done" << endl; +} + +QPID_AUTO_TEST_CASE(analyze_full_journal) +{ + string test_name = get_test_name(test_filename, "analyze_full_journal"); + const string jid = test_name + "_jid"; + const string base_filename = test_name + "_bfn"; + lfid_pfid_map m(jid, base_filename); + for (u_int16_t file_num = 0; file_num < NUM_JFILES; file_num++) + { + m.journal_create(NUM_JFILES, NUM_JFILES, file_num); + m.write_journal(false, 0); + + stringstream fn; + fn << test_dir << "/" << base_filename << "." << JRNL_INFO_EXTENSION; + jinf ji(fn.str(), false); + ji.analyze(); + m.check_analysis(ji); + + m.destroy_journal(); + } + cout << "done" << endl; +} + +QPID_AUTO_TEST_CASE(analyze_single_appended_journal) +{ + string test_name = get_test_name(test_filename, "analyze_single_appended_journal"); + const string jid = test_name + "_jid"; + const string base_filename = test_name + "_bfn"; + lfid_pfid_map m(jid, base_filename); + for (u_int16_t oldest_lid = 0; oldest_lid < NUM_JFILES; oldest_lid++) + for (u_int16_t after_lid = 0; after_lid < NUM_JFILES; after_lid++) + for (u_int16_t num_files = 1; num_files <= 5; num_files++) + { + m.journal_create(NUM_JFILES, NUM_JFILES, oldest_lid); + m.journal_insert(after_lid, num_files); + m.write_journal(true, 16); + + stringstream fn; + fn << test_dir << "/" << base_filename << "." << JRNL_INFO_EXTENSION; + jinf ji(fn.str(), false); + ji.analyze(); + m.check_analysis(ji); + + m.destroy_journal(); + } + cout << "done" << endl; +} + +QPID_AUTO_TEST_CASE(analyze_multi_appended_journal) +{ + string test_name = get_test_name(test_filename, "analyze_multi_appended_journal"); + const string jid = test_name + "_jid"; + const string base_filename = test_name + "_bfn"; + lfid_pfid_map m(jid, base_filename); + ::srand48(1); + + for (u_int16_t num_appends = 1; num_appends <= 2*NUM_JFILES; num_appends++) + { + const u_int16_t oldest_lid = u_int16_t(NUM_JFILES * ::drand48()); + m.journal_create(NUM_JFILES, NUM_JFILES, oldest_lid); + for (u_int16_t a = 0; a < num_appends; a++) + { + const u_int16_t num_files = u_int16_t(1 + (NUM_JFILES * ::drand48())); + const u_int16_t after_lid = u_int16_t(m.size() * ::drand48()); + m.journal_insert(after_lid, num_files); + } + m.write_journal(true, 24); + + stringstream fn; + fn << test_dir << "/" << base_filename << "." << JRNL_INFO_EXTENSION; + jinf ji(fn.str(), false); + ji.analyze(); + m.check_analysis(ji); + + m.destroy_journal(); + } + + cout << "done" << endl; +} + +QPID_AUTO_TEST_CASE(analyze_multi_appended_then_failed_journal) +{ + string test_name = get_test_name(test_filename, "analyze_multi_appended_then_failed_journal"); + const string jid = test_name + "_jid"; + const string base_filename = test_name + "_bfn"; + lfid_pfid_map m(jid, base_filename); + ::srand48(1); + + // As this test relies on repeatable but random sequences, use many iterations for coverage + for (int c = 1; c <= 100; c++) + { + for (u_int16_t num_appends = 1; num_appends <= 2*NUM_JFILES; num_appends++) + { + u_int16_t oldest_lid = u_int16_t(NUM_JFILES * ::drand48()); + m.journal_create(NUM_JFILES, NUM_JFILES, oldest_lid); + for (u_int16_t a = 0; a < num_appends-1; a++) + { + const u_int16_t num_files = u_int16_t(1 + (NUM_JFILES * ::drand48())); + const u_int16_t after_lid = u_int16_t(m.size() * ::drand48()); + m.journal_insert(after_lid, num_files); + if (after_lid < oldest_lid) + oldest_lid += num_files; + } + const u_int16_t num_files = u_int16_t(1 + (NUM_JFILES * ::drand48())); + const u_int16_t after_lid = oldest_lid == 0 ? m.size() - 1 : oldest_lid - 1; + m.journal_insert(after_lid, num_files, false); + m.write_journal(true, 32); + + stringstream fn; + fn << test_dir << "/" << base_filename << "." << JRNL_INFO_EXTENSION; + jinf ji(fn.str(), false); + ji.analyze(); + m.check_analysis(ji); + + m.destroy_journal(); + } + } + + cout << "done" << endl; +} + +QPID_AUTO_TEST_CASE(analyze_inconsistent_jdat_file_size_in_journal) +{ + string test_name = get_test_name(test_filename, "analyze_inconsistent_jdat_file_size_in_journal"); + const string jid = test_name + "_jid"; + const string base_filename = test_name + "_bfn"; + lfid_pfid_map m(jid, base_filename); + ::srand48(1); + + for (u_int16_t pfid = 1; pfid < NUM_JFILES; pfid++) + { + m.journal_create(NUM_JFILES, NUM_JFILES, 0); + m.write_journal(false, 0); + + const std::string filename = m.create_journal_filename(pfid, base_filename); + std::ofstream of(filename.c_str(), ofstream::out | ofstream::app); + if (!of.good()) + BOOST_FAIL("Unable to open test journal file \"" << filename << "\" for writing."); + std::size_t expand_size = std::size_t(10 * JRNL_DBLK_SIZE * JRNL_SBLK_SIZE * ::drand48()); + std::vector<char> sblk_buffer(expand_size, 0); + of.write(&sblk_buffer[0], expand_size); + of.close(); + + stringstream fn; + fn << test_dir << "/" << base_filename << "." << JRNL_INFO_EXTENSION; + jinf ji(fn.str(), false); + try + { + ji.analyze(); + BOOST_FAIL("Failed to detect irregular journal file size in file \"" << filename << "\""); + } + catch (const jexception& e) {} // ignore - expected + + m.destroy_journal(); + } + cout << "done" << endl; +} + +QPID_AUTO_TEST_CASE(analyze_owi_in_non_ae_journal) +{ + string test_name = get_test_name(test_filename, "analyze_owi_in_non_ae_journal"); + const string jid = test_name + "_jid"; + const string base_filename = test_name + "_bfn"; + lfid_pfid_map m(jid, base_filename); + for (u_int16_t oldest_file = 1; oldest_file < NUM_DEFAULT_JFILES-1; oldest_file++) + { + for (u_int16_t bad_owi_file = oldest_file + 1; bad_owi_file < NUM_DEFAULT_JFILES; bad_owi_file++) + { + m.journal_create(NUM_DEFAULT_JFILES, NUM_DEFAULT_JFILES, oldest_file, bad_owi_file); + m.write_journal(false, 0); + + stringstream fn; + fn << test_dir << "/" << base_filename << "." << JRNL_INFO_EXTENSION; + jinf ji(fn.str(), false); + try + { + ji.analyze(); + BOOST_FAIL("Failed to detect irregular OWI flag in non-ae journal file \"" << fn.str() << "\""); + } + catch (const jexception& e) {} // ignore - expected + + m.destroy_journal(); + } + } + cout << "done" << endl; +} + +QPID_AUTO_TEST_CASE(analyze_owi_in_ae_min_size_journal) +{ + string test_name = get_test_name(test_filename, "analyze_owi_in_ae_min_size_journal"); + const string jid = test_name + "_jid"; + const string base_filename = test_name + "_bfn"; + lfid_pfid_map m(jid, base_filename); + for (u_int16_t oldest_file = 1; oldest_file < NUM_JFILES-1; oldest_file++) + { + for (u_int16_t bad_owi_file = oldest_file + 1; bad_owi_file < NUM_JFILES; bad_owi_file++) + { + m.journal_create(NUM_JFILES, NUM_JFILES, oldest_file, bad_owi_file); + m.write_journal(true, 16); + + stringstream fn; + fn << test_dir << "/" << base_filename << "." << JRNL_INFO_EXTENSION; + jinf ji(fn.str(), false); + try + { + ji.analyze(); + BOOST_FAIL("Failed to detect irregular OWI flag in min-sized ae journal file \"" << fn.str() << "\""); + } + catch (const jexception& e) {} // ignore - expected + + m.destroy_journal(); + } + } + cout << "done" << endl; +} + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_ut_lpmgr.cpp b/qpid/cpp/src/tests/legacystore/jrnl/_ut_lpmgr.cpp new file mode 100644 index 0000000000..2dc20ffa7c --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/_ut_lpmgr.cpp @@ -0,0 +1,886 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "../unit_test.h" +#include <cmath> +#include <iostream> +#include "qpid/legacystore/jrnl/jcntl.h" +#include "qpid/legacystore/jrnl/lpmgr.h" + +using namespace boost::unit_test; +using namespace mrg::journal; +using namespace std; + +QPID_AUTO_TEST_SUITE(arr_cnt_suite) + +const string test_filename("_ut_lpmgr"); + +#include "_st_helper_fns.h" + +// === Helper functions and definitions === + +typedef vector<u_int16_t> flist; +typedef flist::const_iterator flist_citr; + +class lpmgr_test_helper +{ + lpmgr_test_helper() {} + virtual ~lpmgr_test_helper() {} + +public: + static void check_pfids_lfids(const lpmgr& lm, const u_int16_t pfids[], const u_int16_t lfids[], + const size_t pfid_lfid_size) + { + vector<u_int16_t> res; + lm.get_pfid_list(res); + vectors_equal(lm, pfids, pfid_lfid_size, res, true); + lm.get_lfid_list(res); + vectors_equal(lm, lfids, pfid_lfid_size, res, false); + } + + static void check_pfids_lfids(const lpmgr& lm, const flist& pfids, const flist lfids) + { + vector<u_int16_t> res; + lm.get_pfid_list(res); + vectors_equal(lm, pfids, res, true); + lm.get_lfid_list(res); + vectors_equal(lm, lfids, res, false); + } + + static void check_linear_pfids_lfids(const lpmgr& lm, const size_t pfid_lfid_size) + { + vector<u_int16_t> res; + lm.get_pfid_list(res); + linear_vectors_equal(lm, pfid_lfid_size, res, true); + lm.get_lfid_list(res); + linear_vectors_equal(lm, pfid_lfid_size, res, false); + } + + static void rcvdat_init(rcvdat& rd, const u_int16_t num_jfiles, const bool ae, const u_int16_t ae_max_jfiles, + const u_int16_t pfids[]) + { + rd.reset(num_jfiles, ae, ae_max_jfiles); + load_vector(pfids, num_jfiles, rd._fid_list); + rd._jempty = false; + rd._lfid = pfids[num_jfiles - 1]; + rd._eo = 100 * JRNL_DBLK_SIZE * JRNL_SBLK_SIZE; + } + + static void rcvdat_init(rcvdat& rd, const flist& pfidl, const bool ae, const u_int16_t ae_max_jfiles) + { + const u_int16_t num_jfiles = pfidl.size(); + rd.reset(num_jfiles, ae, ae_max_jfiles); + load_vector(pfidl, rd._fid_list); + rd._jempty = false; + rd._lfid = pfidl[num_jfiles - 1]; + rd._eo = 100 * JRNL_DBLK_SIZE * JRNL_SBLK_SIZE; + } + + static void initialize(lpmgr& lm, test_jrnl& jc, const u_int16_t num_jfiles, const bool ae, + const u_int16_t ae_max_jfiles) + { + lm.initialize(num_jfiles, ae, ae_max_jfiles, &jc, &jc.new_fcntl); + BOOST_CHECK_EQUAL(lm.is_init(), true); + BOOST_CHECK_EQUAL(lm.is_ae(), ae); + BOOST_CHECK_EQUAL(lm.ae_max_jfiles(), ae_max_jfiles); + BOOST_CHECK_EQUAL(lm.num_jfiles(), num_jfiles); + if (num_jfiles) + check_linear_pfids_lfids(lm, num_jfiles); + else + BOOST_CHECK_EQUAL(lm.get_fcntlp(0), (void*)0); + } + + // version which sets up the lfid_pfid_map for later manipulation by insert tests + static void initialize(lfid_pfid_map& lfm, lpmgr& lm, test_jrnl& jc, const u_int16_t num_jfiles, const bool ae, + const u_int16_t ae_max_jfiles) + { + lfm.journal_create(num_jfiles, num_jfiles); + initialize(lm, jc, num_jfiles, ae, ae_max_jfiles); + } + + static void prepare_recover(lfid_pfid_map& lfm, const u_int16_t size) + { + if (size < 4) BOOST_FAIL("prepare_recover(): size parameter (" << size << ") too small."); + lfm.journal_create(4, 4); // initial journal of size 4 + u_int16_t s = 4; // cumulative size + while (s < size) + { + const u_int16_t ins_posn = u_int16_t(s * ::drand48()); // this insert posn + if (3.0 * ::drand48() > 1.0 || size - s < 2) // 2:1 chance of single insert when >= 2 still to insert + { + lfm.journal_insert(ins_posn); // single insert + s++; + } + else + { + // multiple insert, either 2 - 5 + const u_int16_t max_ins_size = size - s >5 ? 5 : size - s; + const u_int16_t ins_size = 2 + u_int16_t((max_ins_size - 2) * ::drand48()); // this insert size + lfm.journal_insert(ins_posn, ins_size); + s += ins_size; + } + } + } + + static void recover(lfid_pfid_map& lfm, lpmgr& lm, test_jrnl& jc, const bool ae, const u_int16_t ae_max_jfiles) + { + flist pfidl; + flist lfidl; + rcvdat rd; + const u_int16_t num_jfiles = lfm.size(); + + lfm.get_pfid_list(pfidl); + lfm.get_lfid_list(lfidl); + lm.finalize(); // clear all file handles before erasing old journal files + lfm.write_journal(ae, ae_max_jfiles, JFSIZE_SBLKS); + + lpmgr_test_helper::rcvdat_init(rd, pfidl, ae, ae_max_jfiles); + lm.recover(rd, &jc, &jc.new_fcntl); + BOOST_CHECK_EQUAL(lm.is_init(), true); + BOOST_CHECK_EQUAL(lm.is_ae(), ae); + BOOST_CHECK_EQUAL(lm.ae_max_jfiles(), ae_max_jfiles); + BOOST_CHECK_EQUAL(lm.num_jfiles(), num_jfiles); + if (num_jfiles) + check_pfids_lfids(lm, pfidl, lfidl); + else + BOOST_CHECK_EQUAL(lm.get_fcntlp(0), (void*)0); + } + + static void finalize(lpmgr& lm) + { + lm.finalize(); + BOOST_CHECK_EQUAL(lm.is_init(), false); + BOOST_CHECK_EQUAL(lm.is_ae(), false); + BOOST_CHECK_EQUAL(lm.ae_max_jfiles(), u_int16_t(0)); + BOOST_CHECK_EQUAL(lm.num_jfiles(), u_int16_t(0)); + BOOST_CHECK_EQUAL(lm.get_fcntlp(0), (void*)0); + vector<u_int16_t> res; + lm.get_pfid_list(res); + BOOST_CHECK_EQUAL(res.size(), u_int16_t(0)); + lm.get_lfid_list(res); + BOOST_CHECK_EQUAL(res.size(), u_int16_t(0)); + } + + static void insert(lfid_pfid_map& lfm, lpmgr& lm, test_jrnl& jc, const u_int16_t after_lfid, const u_int16_t incr = 1) + { + flist pfidl; + flist lfidl; + const u_int16_t num_jfiles = lm.num_jfiles(); + lfm.journal_insert(after_lfid, incr); + lfm.get_pfid_list(pfidl); + lfm.get_lfid_list(lfidl); + lm.insert(after_lfid, &jc, &jc.new_fcntl, incr); + BOOST_CHECK_EQUAL(lm.num_jfiles(), num_jfiles + incr); + lpmgr_test_helper::check_pfids_lfids(lm, pfidl, lfidl); + } + + static void check_ae_max_jfiles(lpmgr& lm, const u_int16_t num_jfiles, const u_int16_t ae_max_jfiles) + { + bool legal = ae_max_jfiles > num_jfiles || ae_max_jfiles == 0; + + lm.set_ae(false); + BOOST_CHECK(!lm.is_ae()); + if (legal) + { + lm.set_ae_max_jfiles(ae_max_jfiles); + BOOST_CHECK_EQUAL(lm.ae_max_jfiles(), ae_max_jfiles); + lm.set_ae(true); + BOOST_CHECK(lm.is_ae()); + BOOST_CHECK_EQUAL(lm.ae_jfiles_rem(), ae_max_jfiles + ? ae_max_jfiles - num_jfiles + : JRNL_MAX_NUM_FILES - num_jfiles); + } + else + { + lm.set_ae_max_jfiles(ae_max_jfiles); + BOOST_CHECK_EQUAL(lm.ae_max_jfiles(), ae_max_jfiles); + try + { + lm.set_ae(true); // should raise exception + BOOST_ERROR("Auto-expand enabled with out-of-range ae_max_jfiles"); + } + catch (const jexception& e) { BOOST_CHECK_EQUAL(e.err_code(), jerrno::JERR_LFMGR_BADAEFNUMLIM); } + BOOST_CHECK(!lm.is_ae()); + BOOST_CHECK_EQUAL(lm.ae_jfiles_rem(), 0); + } + BOOST_CHECK_EQUAL(lm.ae_max_jfiles(), ae_max_jfiles); + } + + static void check_multiple_initialization_recover(lfid_pfid_map& lfm, test_jrnl& jc, + const u_int16_t num_jfiles_arr[][2], const bool init_flag_0, const bool finalize_flag, + const bool init_flag_1) + { + unsigned i_njf = 0; + while (num_jfiles_arr[i_njf][0] && num_jfiles_arr[i_njf][1]) // cycle through each entry in num_jfiles_arr + { + for (unsigned i1_njf = 0; i1_njf <= 1; i1_njf++) // cycle through the two numbers in each entry of num_jfiles_arr + { + const u_int16_t num_jfiles_0 = num_jfiles_arr[i_njf][i1_njf == 0]; // first number in pair + const u_int16_t num_jfiles_1 = num_jfiles_arr[i_njf][i1_njf != 0]; // second number in pair + + for (unsigned i_ae = 0; i_ae < 4; i_ae++) // cycle through combinations of enabling AE + { + const bool ae_0 = i_ae & 0x1; // first bit: enable AE on first init + const bool ae_1 = i_ae & 0x2; // second bit: enable AE on second init + for (unsigned i_aemjf = 0; i_aemjf < 4; i_aemjf++) // cycle through combinations of enabling/disabling ae limit + { + const u_int16_t ae_max_jfiles_0 = i_aemjf & 0x1 ? 3 * num_jfiles_0 : 0; // max ae files, 0 = disable max + const u_int16_t ae_max_jfiles_1 = i_aemjf & 0x2 ? 4 * num_jfiles_1 : 0; // max ae files, 0 = disable max + + lpmgr lm; // DUT + + if (init_flag_0) + initialize(lm, jc, num_jfiles_0, ae_0, ae_max_jfiles_0); + else + { + prepare_recover(lfm, num_jfiles_0); + recover(lfm, lm, jc, ae_1, ae_max_jfiles_0); + lfm.destroy_journal(); + } + + if (finalize_flag) finalize(lm); + + if (init_flag_1) + initialize(lm, jc, num_jfiles_1, ae_1, ae_max_jfiles_1); + else + { + prepare_recover(lfm, num_jfiles_1); + recover(lfm, lm, jc, ae_1, ae_max_jfiles_1); + lfm.destroy_journal(); + } + } + } + } + i_njf++; + } + } + + static void check_insert(lfid_pfid_map& lfm, lpmgr& lm, test_jrnl& jc, const u_int16_t after_lfid, + const u_int16_t incr = 1) + { + const u_int16_t num_jfiles = lm.num_jfiles(); + const u_int16_t ae_max_jfiles = lm.ae_max_jfiles(); + const u_int16_t effective_ae_max_jfiles = ae_max_jfiles ? ae_max_jfiles : JRNL_MAX_NUM_FILES; + BOOST_CHECK_EQUAL(lm.ae_jfiles_rem(), effective_ae_max_jfiles - num_jfiles); + bool legal = lm.is_ae() && num_jfiles + incr <= effective_ae_max_jfiles; + if (legal) + { + insert(lfm, lm, jc, after_lfid, incr); + BOOST_CHECK_EQUAL(lm.num_jfiles(), num_jfiles + incr); + BOOST_CHECK_EQUAL(lm.ae_jfiles_rem(), effective_ae_max_jfiles - num_jfiles - incr); + } + else + { + try + { + insert(lfm, lm, jc, after_lfid, incr); + if (lm.is_ae()) + BOOST_ERROR("lpmgr::insert() succeeded and exceeded limit"); + else + BOOST_ERROR("lpmgr::insert() succeeded with auto-expand disabled"); + } + catch (const jexception& e) + { + if (lm.is_ae()) + BOOST_CHECK_EQUAL(e.err_code(), jerrno::JERR_LFMGR_AEFNUMLIMIT); + else + BOOST_CHECK_EQUAL(e.err_code(), jerrno::JERR_LFMGR_AEDISABLED); + } + BOOST_CHECK_EQUAL(lm.num_jfiles(), num_jfiles); + BOOST_CHECK_EQUAL(lm.ae_jfiles_rem(), effective_ae_max_jfiles - num_jfiles); + } + } + + static void check_limit(lfid_pfid_map& lfm, test_jrnl& jc, const bool ae, const u_int16_t num_jfiles, + const u_int16_t ae_max_jfiles) + { + lpmgr lm; + + for (unsigned i = 0; i < 2; i++) + { + if (i) + initialize(lfm, lm, jc, num_jfiles, ae, ae_max_jfiles); + else + { + prepare_recover(lfm, num_jfiles); + recover(lfm, lm, jc, ae, ae_max_jfiles); + } + + // use up all available files + unsigned j = ae_max_jfiles ? ae_max_jfiles : JRNL_MAX_NUM_FILES; + while (ae && j > num_jfiles) + { + const u_int16_t posn = static_cast<u_int16_t>((lm.num_jfiles() - 1) * ::drand48()); + const u_int16_t incr = 1 + static_cast<u_int16_t>((lm.ae_jfiles_rem() > 4 + ? 3 : lm.ae_jfiles_rem() - 1) * ::drand48()); + check_insert(lfm, lm, jc, posn, incr); + j -= incr; + } + // these should be over the limit or illegal + check_insert(lfm, lm, jc, 0); + check_insert(lfm, lm, jc, 2, 2); + lfm.destroy_journal(); + } + } + +private: + static void load_vector(const u_int16_t a[], const size_t n, flist& v) + { + for (size_t i = 0; i < n; i++) + v.push_back(a[i]); + } + + static void load_vector(const flist& a, flist& b) + { + for (flist_citr i = a.begin(); i < a.end(); i++) + b.push_back(*i); + } + + static void vectors_equal(const lpmgr& lm, const u_int16_t a[], const size_t n, const flist& b, + const bool pfid_check) + { + BOOST_CHECK_EQUAL(n, b.size()); + for (size_t i = 0; i < n; i++) + { + BOOST_CHECK_EQUAL(a[i], b[i]); + fcntl* fp = lm.get_fcntlp(i); + BOOST_CHECK_MESSAGE(fp != (void*)0, "Unexpected void pointer returned by lpmgr::get_fcntlp()"); + if (fp) BOOST_CHECK_EQUAL(pfid_check ? fp->pfid() : fp->lfid(), pfid_check ? a[i] : i); + } + } + + static void vectors_equal(const lpmgr& lm, const flist& a, const flist& b, const bool pfid_check) + { + BOOST_CHECK_EQUAL(a.size(), b.size()); + for (size_t i = 0; i < a.size(); i++) + { + BOOST_CHECK_EQUAL(a[i], b[i]); + fcntl* fp = lm.get_fcntlp(i); + BOOST_CHECK_MESSAGE(fp != (void*)0, "Unexpected void pointer returned by lpmgr::get_fcntlp()"); + if (fp) BOOST_CHECK_EQUAL(pfid_check ? fp->pfid() : fp->lfid(), pfid_check ? a[i] : i); + } + } + + static void linear_vectors_equal(const lpmgr& lm, const size_t n, const flist& f, const bool pfid_check) + { + BOOST_CHECK_EQUAL(n, f.size()); + for (size_t i = 0; i < n; i++) + { + BOOST_CHECK_EQUAL(i, f[i]); + fcntl* fp = lm.get_fcntlp(i); + BOOST_CHECK_MESSAGE(fp != (void*)0, "Unexpected void pointer returned by lpmgr::get_fcntlp()"); + if (fp) BOOST_CHECK_EQUAL(pfid_check ? fp->pfid() : fp->lfid(), i); + } + } +}; + +// === Tests === + +#ifndef LONG_TEST +/* + * ============================================== + * NORMAL TESTS + * This section contains normal "make check" tests + * for building/packaging. These are built when + * LONG_TEST is _not_ defined. + * ============================================== + */ + +/* + * Check that after construction, the fcntl array _fcntl_arr is empty and the is_init() function returns false. + */ +QPID_AUTO_TEST_CASE(default_constructor) +{ + string test_name = get_test_name(test_filename, "default_constructor"); + try + { + lpmgr lm; + BOOST_CHECK_EQUAL(lm.is_init(), false); + BOOST_CHECK_EQUAL(lm.is_ae(), false); + BOOST_CHECK_EQUAL(lm.ae_max_jfiles(), u_int16_t(0)); + BOOST_CHECK_EQUAL(lm.num_jfiles(), u_int16_t(0)); + BOOST_CHECK_EQUAL(lm.get_fcntlp(0), (void*)0); + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "done" << endl; +} + +/* + * Check that initialize() correctly creates an ordered fcntl array _fcntl_arr. + */ +QPID_AUTO_TEST_CASE(initialize) +{ + string test_name = get_test_name(test_filename, "initialize"); + const u_int16_t num_jfiles = 8; + try + { + jdir::create_dir(test_dir); // Check test dir exists; create it if not + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + { + lpmgr lm; + lpmgr_test_helper::initialize(lm, jc, num_jfiles, false, 0); + } + { + lpmgr lm; + lpmgr_test_helper::initialize(lm, jc, num_jfiles, true, 0); + } + { + lpmgr lm; + lpmgr_test_helper::initialize(lm, jc, num_jfiles, true, 5 * num_jfiles); + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "done" << endl; +} + +/* + * Check that recover() correctly sets up the specified pfid list order. + */ +QPID_AUTO_TEST_CASE(recover) +{ + string test_name = get_test_name(test_filename, "recover"); + ::srand48(1); // init random gen for repeatable tests when using lpmgr_test_helper::prepare_recover() + try + { + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + lfid_pfid_map lfm(test_name, test_name); + + { + lpmgr lm; + lpmgr_test_helper::prepare_recover(lfm, 8); + lpmgr_test_helper::recover(lfm, lm, jc, false, 0); + lfm.destroy_journal(); + } + { + lpmgr lm; + lpmgr_test_helper::prepare_recover(lfm, 8); + lpmgr_test_helper::recover(lfm, lm, jc, true, 0); + lfm.destroy_journal(); + } + { + lpmgr lm; + lpmgr_test_helper::prepare_recover(lfm, 8); + lpmgr_test_helper::recover(lfm, lm, jc, true, 5 * lfm.size()); + lfm.destroy_journal(); + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "done" << endl; +} + +/* + * Check that finalize() after an initialize() empties _fcntl_arr and that afterwards is_init() returns false. + */ +QPID_AUTO_TEST_CASE(initialize_finalize) +{ + string test_name = get_test_name(test_filename, "initialize_finalize"); + const u_int16_t num_jfiles = 8; + try + { + jdir::create_dir(test_dir); // Check test dir exists; create it if not + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + { + lpmgr lm; + lpmgr_test_helper::initialize(lm, jc, num_jfiles, false, 0); + lpmgr_test_helper::finalize(lm); + } + { + lpmgr lm; + lpmgr_test_helper::initialize(lm, jc, num_jfiles, true, 0); + lpmgr_test_helper::finalize(lm); + } + { + lpmgr lm; + lpmgr_test_helper::initialize(lm, jc, num_jfiles, true, 5 * num_jfiles); + lpmgr_test_helper::finalize(lm); + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "done" << endl; +} + +/* + * Check that finalize() after a recover() empties _fcntl_arr and that afterwards is_init() returns false. + */ +QPID_AUTO_TEST_CASE(recover_finalize) +{ + string test_name = get_test_name(test_filename, "recover_finalize"); + const u_int16_t num_jfiles = 8; + ::srand48(1); // init random gen for repeatable tests when using lpmgr_test_helper::prepare_recover() + try + { + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + lfid_pfid_map lfm(test_name, test_name); + + { + lpmgr lm; + lpmgr_test_helper::prepare_recover(lfm, num_jfiles); + lpmgr_test_helper::recover(lfm, lm, jc, false, 0); + lpmgr_test_helper::finalize(lm); + lfm.destroy_journal(); + } + { + lpmgr lm; + lpmgr_test_helper::prepare_recover(lfm, num_jfiles); + lpmgr_test_helper::recover(lfm, lm, jc, true, 0); + lpmgr_test_helper::finalize(lm); + lfm.destroy_journal(); + } + { + lpmgr lm; + lpmgr_test_helper::prepare_recover(lfm, num_jfiles); + lpmgr_test_helper::recover(lfm, lm, jc, true, 5 * lfm.size()); + lpmgr_test_helper::finalize(lm); + lfm.destroy_journal(); + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "done" << endl; +} + +/* + * Check that 0 and/or null and other extreme/boundary parameters behave as expected. + */ +QPID_AUTO_TEST_CASE(zero_null_params) +{ + string test_name = get_test_name(test_filename, "zero_null_params"); + const u_int16_t num_jfiles = 8; + try + { + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + lfid_pfid_map lfm(test_name, test_name); + lpmgr lm; + lpmgr_test_helper::initialize(lfm, lm, jc, num_jfiles, true, 0); + + // Check that inserting 0 files works ok + lpmgr_test_helper::insert(lfm, lm, jc, 0, 0); + lpmgr_test_helper::insert(lfm, lm, jc, 2, 0); + lpmgr_test_helper::insert(lfm, lm, jc, num_jfiles - 1, 0); + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "done" << endl; +} + +/* + * Check that initialize()/recover() works correctly after a previous initialize()/recover() with/without an intervening + * finalize(). + */ +QPID_AUTO_TEST_CASE(multiple_initialization_recover) +{ + string test_name = get_test_name(test_filename, "multiple_initialization_recover"); + ::srand48(1); // init random gen for repeatable tests when using lpmgr_test_helper::prepare_recover() + + // Set combinations of value pairs to be used for number of journal files in first and second init + u_int16_t num_jfiles_arr[][2] = {{8, 12}, {4, 7}, {0, 0}}; // end with zeros + try + { + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + lfid_pfid_map lfm(test_name, test_name); + for (unsigned p = 0; p < 8; p++) + { + const bool i_0 = p & 0x01; // first bit + const bool i_1 = p & 0x02; // second bit + const bool f = p & 0x04; // third bit + lpmgr_test_helper::check_multiple_initialization_recover(lfm, jc, num_jfiles_arr, i_0, f, i_1); + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "done" << endl; +} + +/* + * Check that insert() works correctly after initialize() and shifts the pfid sequence beyond the insert point correctly: + * + * The following sequence is tested: + * initialize 4 pfids=[0,1,2,3] lfids=[0,1,2,3] + * insert 1 after lfid 0 pfids=[0,4,1,2,3] lfids=[0,2,3,4,1] + * insert 2 after lfid 2 pfids=[0,4,1,5,6,2,3] lfids=[0,2,5,6,1,3,4] + * insert 1 after lfid 6 pfids=[0,4,1,5,6,2,3,7] lfids=[0,2,5,6,1,3,4,7] + * issert 1 after lfid 3 pfids=[0,4,1,5,8,6,2,3,7] lfids=[0,2,6,7,1,3,5,8,4] + */ +QPID_AUTO_TEST_CASE(initialize_insert) +{ + string test_name = get_test_name(test_filename, "initialize_insert"); + const u_int16_t initial_num_jfiles = 8; + try + { + jdir::create_dir(test_dir); // Check test dir exists; create it if not + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + lfid_pfid_map lfm(test_name, test_name); + lpmgr lm; + lpmgr_test_helper::initialize(lfm, lm, jc, initial_num_jfiles, true, 0); + + lpmgr_test_helper::insert(lfm, lm, jc, 0); + lpmgr_test_helper::insert(lfm, lm, jc, 2, 2); + lpmgr_test_helper::insert(lfm, lm, jc, 6); + lpmgr_test_helper::insert(lfm, lm, jc, 3); + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "done" << endl; +} + +/* + * Check that insert() works correctly after recover() and shifts the pfid sequence beyond the insert point correctly: + * + * The following sequence is tested: + * recover 4 pfids=[0,2,3,1] lfids=[0,3,1,2] + * insert 1 after lfid 0 pfids=[0,4,2,3,1] lfids=[0,4,2,3,1] + * insert 2 after lfid 2 pfids=[0,4,2,5,6,3,1] lfids=[0,6,2,5,1,3,4] + * insert 1 after lfid 6 pfids=[0,4,2,5,6,3,1,7] lfids=[0,6,2,5,1,3,4,7] + * issert 1 after lfid 3 pfids=[0,4,2,5,8,6,3,1,7] lfids=[0,7,2,6,1,3,5,8,4] + */ +QPID_AUTO_TEST_CASE(recover_insert) +{ + string test_name = get_test_name(test_filename, "recover_insert"); + const u_int16_t initial_num_jfiles = 4; + ::srand48(1); // init random gen for repeatable tests when using lpmgr_test_helper::prepare_recover() + try + { + jdir::create_dir(test_dir); // Check test dir exists; create it if not + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + lfid_pfid_map lfm(test_name, test_name); + lpmgr lm; + lpmgr_test_helper::prepare_recover(lfm, initial_num_jfiles); + lpmgr_test_helper::recover(lfm, lm, jc, true, 0); + + lpmgr_test_helper::insert(lfm, lm, jc, 0); + lpmgr_test_helper::insert(lfm, lm, jc, 2, 2); + lpmgr_test_helper::insert(lfm, lm, jc, 6); + lpmgr_test_helper::insert(lfm, lm, jc, 3); + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "done" << endl; +} + +/* + * Check that illegal ae parameter combinations are caught and result in an exception being thrown. + */ +QPID_AUTO_TEST_CASE(ae_parameters) +{ + string test_name = get_test_name(test_filename, "ae_parameters"); + ::srand48(1); // init random gen for repeatable tests when using lpmgr_test_helper::prepare_recover() + try + { + jdir::create_dir(test_dir); // Check test dir exists; create it if not + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + lfid_pfid_map lfm(test_name, test_name); + const u_int16_t num_jfiles = 8; + lpmgr lm; + + for (unsigned i = 0; i < 2; i++) + { + if (i) + lpmgr_test_helper::initialize(lfm, lm, jc, num_jfiles, false, 0); + else + { + lpmgr_test_helper::prepare_recover(lfm, num_jfiles); + lpmgr_test_helper::recover(lfm, lm, jc, false, 0); + } + + lpmgr_test_helper::check_ae_max_jfiles(lm, num_jfiles, num_jfiles - 2); + lpmgr_test_helper::check_ae_max_jfiles(lm, num_jfiles, 0); + lpmgr_test_helper::check_ae_max_jfiles(lm, num_jfiles, 2 * num_jfiles); + lpmgr_test_helper::check_ae_max_jfiles(lm, num_jfiles, num_jfiles); + lfm.destroy_journal(); + } + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "done" << endl; +} + +/* + * Check that initialized or recovered journals with auto-expand disabled will not allow either inserts or appends. + */ +QPID_AUTO_TEST_CASE(ae_disabled) +{ + string test_name = get_test_name(test_filename, "ae_disabled"); + ::srand48(1); // init random gen for repeatable tests when using lpmgr_test_helper::prepare_recover() + try + { + jdir::create_dir(test_dir); // Check test dir exists; create it if not + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + lfid_pfid_map lfm(test_name, test_name); + lpmgr_test_helper::check_limit(lfm, jc, false, 8, 0); + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "done" << endl; +} + +/* + * Check that initialized or recovered journals with auto-expand enabled and a file limit set will enforce the correct + * limits on inserts and appends. + */ +QPID_AUTO_TEST_CASE(ae_enabled_limit) +{ + string test_name = get_test_name(test_filename, "ae_enabled_limit"); + ::srand48(1); // init random gen for repeatable tests when using lpmgr_test_helper::prepare_recover() + try + { + jdir::create_dir(test_dir); // Check test dir exists; create it if not + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + lfid_pfid_map lfm(test_name, test_name); + lpmgr_test_helper::check_limit(lfm, jc, true, 8, 32); + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "done" << endl; +} + +/* + * Check that initialized or recovered journals with auto-expand enabled and no file limit set (0) will allow inserts and + * appends up to the file limit JRNL_MAX_NUM_FILES. + */ +QPID_AUTO_TEST_CASE(ae_enabled_unlimited) +{ + string test_name = get_test_name(test_filename, "ae_enabled_unlimited"); + ::srand48(1); // init random gen for repeatable tests when using lpmgr_test_helper::prepare_recover() + try + { + jdir::create_dir(test_dir); // Check test dir exists; create it if not + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + lfid_pfid_map lfm(test_name, test_name); + lpmgr_test_helper::check_limit(lfm, jc, true, 8, 0); + } + catch(const exception& e) { BOOST_FAIL(e.what()); } + cout << "done" << endl; +} + +#else +/* + * ============================================== + * LONG TESTS + * This section contains long tests and soak tests, + * and are run using target check-long (ie "make + * check-long"). These are built when LONG_TEST is + * defined. + * ============================================== + */ + +/* + * Tests randomized combinations of initialization/recovery, initial size, number, size and location of inserts. + * + * To reproduce a specific test, comment out the get_seed() statement and uncomment the literal below, adjusting the seed + * value to that required. + */ +QPID_AUTO_TEST_CASE(randomized_tests) +{ + string test_name = get_test_name(test_filename, "randomized_tests"); + const long seed = get_seed(); + // const long seed = 0x2d9b69d32; + cout << "seed=0x" << hex << seed << dec << " " << flush; + ::srand48(seed); + + lfid_pfid_map lfm(test_name, test_name); + flist pfidl; + flist lfidl; + rcvdat rd; + u_int16_t curr_ae_max_jfiles = 0; + jdir::create_dir(test_dir); // Check test dir exists; create it if not + + for (int test_num = 0; test_num < 250; test_num++) + { + test_jrnl_cb cb; + test_jrnl jc(test_name, test_dir, test_name, cb); + lpmgr lm; + // 50% chance of recovery except first run and if there is still ae space left + const bool recover_flag = test_num > 0 && + curr_ae_max_jfiles > lfm.size() && + 2.0 * ::drand48() < 1.0; + if (recover_flag) + { + // Recover from previous iteration + lfm.get_pfid_list(pfidl); + lfm.get_lfid_list(lfidl); + lfm.write_journal(true, curr_ae_max_jfiles, JFSIZE_SBLKS); + lpmgr_test_helper::rcvdat_init(rd, pfidl, true, curr_ae_max_jfiles); + lm.recover(rd, &jc, &jc.new_fcntl); + lpmgr_test_helper::check_pfids_lfids(lm, pfidl, lfidl); + } + else + { + // Initialize from scratch + const u_int16_t num_jfiles = 4 + u_int16_t(21.0 * ::drand48()); // size: 4 - 25 files + curr_ae_max_jfiles = u_int16_t(4 * num_jfiles * ::drand48()); // size: 0 - 100 files + if (curr_ae_max_jfiles > JRNL_MAX_NUM_FILES) curr_ae_max_jfiles = JRNL_MAX_NUM_FILES; + else if (curr_ae_max_jfiles <= num_jfiles) curr_ae_max_jfiles = 0; + lfm.destroy_journal(); + lfm.journal_create(num_jfiles, num_jfiles); + lfm.get_pfid_list(pfidl); + lfm.get_lfid_list(lfidl); + lm.initialize(num_jfiles, true, curr_ae_max_jfiles, &jc, &jc.new_fcntl); + lpmgr_test_helper::check_linear_pfids_lfids(lm, num_jfiles); + } + + // Loop to insert pfids + const int num_inserts = 1 + int(lfm.size() * ::drand48()); + for (int i = 0; i < num_inserts; i++) + { + const u_int16_t size = lm.num_jfiles(); + const u_int16_t after_lfid = u_int16_t(1.0 * size * ::drand48()); + const u_int16_t num_jfiles = 1 + u_int16_t(4.0 * ::drand48()); + const bool legal = lm.ae_max_jfiles() + ? size + num_jfiles <= lm.ae_max_jfiles() + : size + num_jfiles <= JRNL_MAX_NUM_FILES; + if (legal) + { + lfm.journal_insert(after_lfid, num_jfiles); + lfm.get_pfid_list(pfidl); + lfm.get_lfid_list(lfidl); + + lm.insert(after_lfid, &jc, &jc.new_fcntl, num_jfiles); + lpmgr_test_helper::check_pfids_lfids(lm, pfidl, lfidl); + } + else + { + try + { + lm.insert(after_lfid, &jc, &jc.new_fcntl, num_jfiles); + BOOST_FAIL("lpmgr::insert() succeeded and exceeded limit"); + } + catch (const jexception& e) + { + BOOST_CHECK_EQUAL(e.err_code(), jerrno::JERR_LFMGR_AEFNUMLIMIT); + break; // no more inserts... + } + } + } + lm.finalize(); + BOOST_CHECK_EQUAL(lm.is_init(), false); + BOOST_CHECK_EQUAL(lm.num_jfiles(), u_int16_t(0)); + BOOST_CHECK_EQUAL(lm.get_fcntlp(0), (void*)0); + } + cout << "done" << endl; +} + +#endif + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_ut_rec_hdr.cpp b/qpid/cpp/src/tests/legacystore/jrnl/_ut_rec_hdr.cpp new file mode 100644 index 0000000000..099e576bbd --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/_ut_rec_hdr.cpp @@ -0,0 +1,438 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "../unit_test.h" + +#include <ctime> +#include <iostream> +#include "qpid/legacystore/jrnl/deq_hdr.h" +#include "qpid/legacystore/jrnl/enq_hdr.h" +#include "qpid/legacystore/jrnl/file_hdr.h" +#include "qpid/legacystore/jrnl/jcfg.h" +#include "qpid/legacystore/jrnl/rec_tail.h" +#include "qpid/legacystore/jrnl/txn_hdr.h" + +using namespace boost::unit_test; +using namespace mrg::journal; +using namespace std; + +QPID_AUTO_TEST_SUITE(rec_hdr_suite) + +const string test_filename("_ut_rec_hdr"); + +QPID_AUTO_TEST_CASE(hdr_class) +{ + cout << test_filename << ".hdr_class: " << flush; + rec_hdr h1; + BOOST_CHECK_EQUAL(h1._magic, 0UL); + BOOST_CHECK_EQUAL(h1._version, 0); + BOOST_CHECK_EQUAL(h1._eflag, 0); + BOOST_CHECK_EQUAL(h1._uflag, 0); + BOOST_CHECK_EQUAL(h1._rid, 0ULL); + BOOST_CHECK(!h1.get_owi()); + + const u_int32_t magic = 0x89abcdefUL; + const u_int16_t uflag = 0x5537; + const u_int8_t version = 0xef; + const u_int64_t rid = 0x123456789abcdef0ULL; + const bool owi = true; + + rec_hdr h2(magic, version, rid, owi); + BOOST_CHECK_EQUAL(h2._magic, magic); + BOOST_CHECK_EQUAL(h2._version, version); +#ifdef JRNL_LITTLE_ENDIAN + BOOST_CHECK_EQUAL(h2._eflag, RHM_LENDIAN_FLAG); +#else + BOOST_CHECK_EQUAL(h2._eflag, RHM_BENDIAN_FLAG); +#endif + BOOST_CHECK_EQUAL(h2._uflag, (const u_int16_t)rec_hdr::HDR_OVERWRITE_INDICATOR_MASK); + BOOST_CHECK_EQUAL(h2._rid, rid); + BOOST_CHECK_EQUAL(h2.get_owi(), owi); + h2._uflag = uflag; + BOOST_CHECK(h2.get_owi()); + h2.set_owi(true); + BOOST_CHECK(h2.get_owi()); + BOOST_CHECK_EQUAL(h2._uflag, uflag); + h2.set_owi(false); + BOOST_CHECK(!h2.get_owi()); + BOOST_CHECK_EQUAL(h2._uflag, (uflag & ~(const u_int16_t)rec_hdr::HDR_OVERWRITE_INDICATOR_MASK)); + h2.set_owi(true); + BOOST_CHECK(h2.get_owi()); + BOOST_CHECK_EQUAL(h2._uflag, uflag); + + h1.hdr_copy(h2); + BOOST_CHECK_EQUAL(h1._magic, magic); + BOOST_CHECK_EQUAL(h1._version, version); +#ifdef JRNL_LITTLE_ENDIAN + BOOST_CHECK_EQUAL(h1._eflag, RHM_LENDIAN_FLAG); +#else + BOOST_CHECK_EQUAL(h1._eflag, RHM_BENDIAN_FLAG); +#endif + BOOST_CHECK_EQUAL(h1._uflag, uflag); + BOOST_CHECK_EQUAL(h1._rid, rid); + BOOST_CHECK(h1.get_owi()); + BOOST_CHECK_EQUAL(h1._uflag, uflag); + + h1.reset(); + BOOST_CHECK_EQUAL(h1._magic, 0UL); + BOOST_CHECK_EQUAL(h1._version, 0); + BOOST_CHECK_EQUAL(h1._eflag, 0); + BOOST_CHECK_EQUAL(h1._uflag, 0); + BOOST_CHECK_EQUAL(h1._rid, 0ULL); + BOOST_CHECK(!h1.get_owi()); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(rec_tail_class) +{ + cout << test_filename << ".rec_tail_class: " << flush; + const u_int32_t magic = 0xfedcba98; + const u_int64_t rid = 0xfedcba9876543210ULL; + const u_int32_t xmagic = ~magic; + + { + rec_tail rt1; + BOOST_CHECK_EQUAL(rt1._xmagic, 0xffffffffUL); + BOOST_CHECK_EQUAL(rt1._rid, 0ULL); + } + + { + rec_tail rt2(magic, rid); + BOOST_CHECK_EQUAL(rt2._xmagic, magic); + BOOST_CHECK_EQUAL(rt2._rid, rid); + } + + { + rec_hdr h(magic, RHM_JDAT_VERSION, rid, true); + rec_tail rt3(h); + BOOST_CHECK_EQUAL(rt3._xmagic, xmagic); + BOOST_CHECK_EQUAL(rt3._rid, rid); + } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(file_hdr_class) +{ + cout << test_filename << ".file_hdr_class: " << flush; + const u_int32_t magic = 0xfedcba98UL; + const u_int8_t version = 0xa5; + const u_int16_t uflag = 0x5537; + const u_int64_t rid = 0xfedcba9876543210ULL; + const u_int16_t pfid = 0xfedcU; + const u_int16_t lfid = 0xf0e1U; +#ifdef JRNL_32_BIT + const std::size_t fro = 0xfedcba98UL; +#else + const std::size_t fro = 0xfedcba9876543210ULL; +#endif + timespec ts; + const bool owi = true; + + { + file_hdr fh1; + BOOST_CHECK_EQUAL(fh1._magic, 0UL); + BOOST_CHECK_EQUAL(fh1._version, 0); + BOOST_CHECK_EQUAL(fh1._eflag, 0); + BOOST_CHECK_EQUAL(fh1._uflag, 0); + BOOST_CHECK_EQUAL(fh1._rid, 0ULL); + BOOST_CHECK_EQUAL(fh1._pfid, 0UL); + BOOST_CHECK_EQUAL(fh1._lfid, 0U); + BOOST_CHECK_EQUAL(fh1._fro, std::size_t(0)); + BOOST_CHECK_EQUAL(fh1._ts_sec, std::time_t(0)); + BOOST_CHECK_EQUAL(fh1._ts_nsec, u_int32_t(0)); + BOOST_CHECK(!fh1.get_owi()); + } + + { + file_hdr fh2(magic, version, rid, pfid, lfid, fro, owi, false); + BOOST_CHECK_EQUAL(fh2._magic, magic); + BOOST_CHECK_EQUAL(fh2._version, version); +#ifdef JRNL_LITTLE_ENDIAN + BOOST_CHECK_EQUAL(fh2._eflag, RHM_LENDIAN_FLAG); +#else + BOOST_CHECK_EQUAL(fh2._eflag, RHM_BENDIAN_FLAG); +#endif + BOOST_CHECK_EQUAL(fh2._uflag, (const u_int16_t)rec_hdr::HDR_OVERWRITE_INDICATOR_MASK); + BOOST_CHECK_EQUAL(fh2._rid, rid); + BOOST_CHECK_EQUAL(fh2._pfid, pfid ); + BOOST_CHECK_EQUAL(fh2._lfid, lfid); + BOOST_CHECK_EQUAL(fh2._fro, fro); + BOOST_CHECK_EQUAL(fh2._ts_sec, std::time_t(0)); + BOOST_CHECK_EQUAL(fh2._ts_nsec, u_int32_t(0)); + ::clock_gettime(CLOCK_REALTIME, &ts); + fh2.set_time(ts); + BOOST_CHECK_EQUAL(fh2._ts_sec, ts.tv_sec); + BOOST_CHECK_EQUAL(fh2._ts_nsec, u_int32_t(ts.tv_nsec)); + BOOST_CHECK(fh2.get_owi()); + + fh2._uflag = uflag; + BOOST_CHECK(fh2.get_owi()); + + fh2.set_owi(false); + BOOST_CHECK(!fh2.get_owi()); + BOOST_CHECK_EQUAL(fh2._uflag, + (uflag & ~(const u_int16_t)rec_hdr::HDR_OVERWRITE_INDICATOR_MASK)); + + fh2.set_owi(true); + BOOST_CHECK(fh2.get_owi()); + BOOST_CHECK_EQUAL(fh2._uflag, uflag); + } + + { + file_hdr fh3(magic, version, rid, pfid, lfid, fro, owi, true); + BOOST_CHECK_EQUAL(fh3._magic, magic); + BOOST_CHECK_EQUAL(fh3._version, version); +#ifdef JRNL_LITTLE_ENDIAN + BOOST_CHECK_EQUAL(fh3._eflag, RHM_LENDIAN_FLAG); +#else + BOOST_CHECK_EQUAL(fh3._eflag, RHM_BENDIAN_FLAG); +#endif + BOOST_CHECK_EQUAL(fh3._uflag, (const u_int16_t)rec_hdr::HDR_OVERWRITE_INDICATOR_MASK); + BOOST_CHECK_EQUAL(fh3._rid, rid); + BOOST_CHECK_EQUAL(fh3._pfid, pfid); + BOOST_CHECK_EQUAL(fh3._lfid, lfid); + BOOST_CHECK_EQUAL(fh3._fro, fro); + BOOST_CHECK(fh3._ts_sec - ts.tv_sec <= 1); // No more than 1 sec difference + } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(enq_hdr_class) +{ + cout << test_filename << ".enq_hdr_class: " << flush; + const u_int32_t magic = 0xfedcba98UL; + const u_int8_t version = 0xa5; + const u_int64_t rid = 0xfedcba9876543210ULL; + const u_int16_t uflag = 0x5537; +#ifdef JRNL_32_BIT + const std::size_t xidsize = 0xfedcba98UL; + const std::size_t dsize = 0x76543210UL; +#else + const std::size_t xidsize = 0xfedcba9876543210ULL; + const std::size_t dsize = 0x76543210fedcba98ULL; +#endif + const bool owi = true; + + { + enq_hdr eh1; + BOOST_CHECK_EQUAL(eh1._magic, 0UL); + BOOST_CHECK_EQUAL(eh1._version, 0); + BOOST_CHECK_EQUAL(eh1._eflag, 0); + BOOST_CHECK_EQUAL(eh1._uflag, 0); + BOOST_CHECK_EQUAL(eh1._rid, 0ULL); + BOOST_CHECK_EQUAL(eh1._xidsize, std::size_t(0)); + BOOST_CHECK_EQUAL(eh1._dsize, std::size_t(0)); + BOOST_CHECK(!eh1.get_owi()); + } + + { + enq_hdr eh2(magic, version, rid, xidsize, dsize, owi, false); + BOOST_CHECK_EQUAL(eh2._magic, magic); + BOOST_CHECK_EQUAL(eh2._version, version); +#ifdef JRNL_LITTLE_ENDIAN + BOOST_CHECK_EQUAL(eh2._eflag, RHM_LENDIAN_FLAG); +#else + BOOST_CHECK_EQUAL(eh2._eflag, RHM_BENDIAN_FLAG); +#endif + BOOST_CHECK_EQUAL(eh2._uflag, (const u_int16_t)rec_hdr::HDR_OVERWRITE_INDICATOR_MASK); + BOOST_CHECK_EQUAL(eh2._rid, rid); + BOOST_CHECK_EQUAL(eh2._xidsize, xidsize); + BOOST_CHECK_EQUAL(eh2._dsize, dsize); + BOOST_CHECK(eh2.get_owi()); + BOOST_CHECK(!eh2.is_transient()); + BOOST_CHECK(!eh2.is_external()); + + eh2._uflag = uflag; + BOOST_CHECK(eh2.get_owi()); + BOOST_CHECK(eh2.is_transient()); + BOOST_CHECK(eh2.is_external()); + + eh2.set_owi(false); + BOOST_CHECK(!eh2.get_owi()); + BOOST_CHECK(eh2.is_transient()); + BOOST_CHECK(eh2.is_external()); + BOOST_CHECK_EQUAL(eh2._uflag, + (uflag & ~(const u_int16_t)rec_hdr::HDR_OVERWRITE_INDICATOR_MASK)); + + eh2.set_owi(true); + BOOST_CHECK(eh2.get_owi()); + BOOST_CHECK(eh2.is_transient()); + BOOST_CHECK(eh2.is_external()); + BOOST_CHECK_EQUAL(eh2._uflag, uflag); + + eh2.set_transient(false); + BOOST_CHECK(eh2.get_owi()); + BOOST_CHECK(!eh2.is_transient()); + BOOST_CHECK(eh2.is_external()); + BOOST_CHECK_EQUAL(eh2._uflag, uflag & ~(const u_int16_t)enq_hdr::ENQ_HDR_TRANSIENT_MASK); + + eh2.set_transient(true); + BOOST_CHECK(eh2.get_owi()); + BOOST_CHECK(eh2.is_transient()); + BOOST_CHECK(eh2.is_external()); + BOOST_CHECK_EQUAL(eh2._uflag, uflag); + + eh2.set_external(false); + BOOST_CHECK(eh2.get_owi()); + BOOST_CHECK(eh2.is_transient()); + BOOST_CHECK(!eh2.is_external()); + BOOST_CHECK_EQUAL(eh2._uflag, uflag & ~(const u_int16_t)enq_hdr::ENQ_HDR_EXTERNAL_MASK); + + eh2.set_external(true); + BOOST_CHECK(eh2.get_owi()); + BOOST_CHECK(eh2.is_transient()); + BOOST_CHECK(eh2.is_external()); + BOOST_CHECK_EQUAL(eh2._uflag, uflag); + } + + { + enq_hdr eh3(magic, version, rid, xidsize, dsize, owi, true); + BOOST_CHECK_EQUAL(eh3._magic, magic); + BOOST_CHECK_EQUAL(eh3._version, version); +#ifdef JRNL_LITTLE_ENDIAN + BOOST_CHECK_EQUAL(eh3._eflag, RHM_LENDIAN_FLAG); +#else + BOOST_CHECK_EQUAL(eh3._eflag, RHM_BENDIAN_FLAG); +#endif + BOOST_CHECK_EQUAL(eh3._uflag, (const u_int16_t)enq_hdr::ENQ_HDR_TRANSIENT_MASK | + (const u_int16_t)rec_hdr::HDR_OVERWRITE_INDICATOR_MASK); + BOOST_CHECK_EQUAL(eh3._rid, rid); + BOOST_CHECK_EQUAL(eh3._xidsize, xidsize); + BOOST_CHECK_EQUAL(eh3._dsize, dsize); + BOOST_CHECK(eh3.get_owi()); + BOOST_CHECK(eh3.is_transient()); + BOOST_CHECK(!eh3.is_external()); + } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(deq_hdr_class) +{ + cout << test_filename << ".deq_hdr_class: " << flush; + const u_int32_t magic = 0xfedcba98UL; + const u_int8_t version = 0xa5; + const u_int16_t uflag = 0x5537; + const u_int64_t rid = 0xfedcba9876543210ULL; + const u_int64_t drid = 0x76543210fedcba98ULL; +#ifdef JRNL_32_BIT + const std::size_t xidsize = 0xfedcba98UL; +#else + const std::size_t xidsize = 0xfedcba9876543210ULL; +#endif + const bool owi = true; + + { + deq_hdr dh1; + BOOST_CHECK_EQUAL(dh1._magic, 0UL); + BOOST_CHECK_EQUAL(dh1._version, 0); + BOOST_CHECK_EQUAL(dh1._eflag, 0); + BOOST_CHECK_EQUAL(dh1._uflag, 0); + BOOST_CHECK_EQUAL(dh1._rid, 0ULL); + BOOST_CHECK_EQUAL(dh1._deq_rid, 0ULL); + BOOST_CHECK_EQUAL(dh1._xidsize, std::size_t(0)); + BOOST_CHECK(!dh1.get_owi()); + } + + { + deq_hdr dh2(magic, version, rid, drid, xidsize, owi); + BOOST_CHECK_EQUAL(dh2._magic, magic); + BOOST_CHECK_EQUAL(dh2._version, version); +#ifdef JRNL_LITTLE_ENDIAN + BOOST_CHECK_EQUAL(dh2._eflag, RHM_LENDIAN_FLAG); +#else + BOOST_CHECK_EQUAL(dh2._eflag, RHM_BENDIAN_FLAG); +#endif + BOOST_CHECK_EQUAL(dh2._uflag, (const u_int16_t)rec_hdr::HDR_OVERWRITE_INDICATOR_MASK); + BOOST_CHECK_EQUAL(dh2._rid, rid); + BOOST_CHECK_EQUAL(dh2._deq_rid, drid); + BOOST_CHECK_EQUAL(dh2._xidsize, xidsize); + BOOST_CHECK(dh2.get_owi()); + + dh2._uflag = uflag; + BOOST_CHECK(dh2.get_owi()); + + dh2.set_owi(false); + BOOST_CHECK(!dh2.get_owi()); + BOOST_CHECK_EQUAL(dh2._uflag, + (uflag & ~(const u_int16_t)rec_hdr::HDR_OVERWRITE_INDICATOR_MASK)); + + dh2.set_owi(true); + BOOST_CHECK(dh2.get_owi()); + BOOST_CHECK_EQUAL(dh2._uflag, uflag); + } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(txn_hdr_class) +{ + cout << test_filename << ".txn_hdr_class: " << flush; + const u_int32_t magic = 0xfedcba98UL; + const u_int8_t version = 0xa5; + const u_int16_t uflag = 0x5537; + const u_int64_t rid = 0xfedcba9876543210ULL; +#ifdef JRNL_32_BIT + const std::size_t xidsize = 0xfedcba98UL; +#else + const std::size_t xidsize = 0xfedcba9876543210ULL; +#endif + const bool owi = true; + + { + txn_hdr th1; + BOOST_CHECK_EQUAL(th1._magic, 0UL); + BOOST_CHECK_EQUAL(th1._version, 0); + BOOST_CHECK_EQUAL(th1._eflag, 0); + BOOST_CHECK_EQUAL(th1._uflag, 0); + BOOST_CHECK_EQUAL(th1._rid, 0ULL); + BOOST_CHECK_EQUAL(th1._xidsize, std::size_t(0)); + BOOST_CHECK(!th1.get_owi()); + } + + { + txn_hdr th2(magic, version, rid, xidsize, owi); + BOOST_CHECK_EQUAL(th2._magic, magic); + BOOST_CHECK_EQUAL(th2._version, version); +#ifdef JRNL_LITTLE_ENDIAN + BOOST_CHECK_EQUAL(th2._eflag, RHM_LENDIAN_FLAG); +#else + BOOST_CHECK_EQUAL(th2._eflag, RHM_BENDIAN_FLAG); +#endif + BOOST_CHECK_EQUAL(th2._uflag, (const u_int16_t)rec_hdr::HDR_OVERWRITE_INDICATOR_MASK); + BOOST_CHECK_EQUAL(th2._rid, rid); + BOOST_CHECK_EQUAL(th2._xidsize, xidsize); + BOOST_CHECK(th2.get_owi()); + + th2._uflag = uflag; + BOOST_CHECK(th2.get_owi()); + + th2.set_owi(false); + BOOST_CHECK(!th2.get_owi()); + BOOST_CHECK_EQUAL(th2._uflag, + (uflag & ~(const u_int16_t)rec_hdr::HDR_OVERWRITE_INDICATOR_MASK)); + + th2.set_owi(true); + BOOST_CHECK(th2.get_owi()); + BOOST_CHECK_EQUAL(th2._uflag, uflag); + } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_ut_time_ns.cpp b/qpid/cpp/src/tests/legacystore/jrnl/_ut_time_ns.cpp new file mode 100644 index 0000000000..f1b53bb97b --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/_ut_time_ns.cpp @@ -0,0 +1,163 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "../unit_test.h" + +#include <ctime> +#include <iostream> +#include "qpid/legacystore/jrnl/time_ns.h" + +using namespace boost::unit_test; +using namespace mrg::journal; +using namespace std; + +QPID_AUTO_TEST_SUITE(time_ns_suite) + +const string test_filename("_ut_time_ns"); + +QPID_AUTO_TEST_CASE(constructors) +{ + cout << test_filename << ".constructors: " << flush; + const std::time_t sec = 123; + const long nsec = 123456789; + + time_ns t1; + BOOST_CHECK_EQUAL(t1.tv_sec, 0); + BOOST_CHECK_EQUAL(t1.tv_nsec, 0); + BOOST_CHECK_EQUAL(t1.is_zero(), true); + time_ns t2(sec, nsec); + BOOST_CHECK_EQUAL(t2.tv_sec, sec); + BOOST_CHECK_EQUAL(t2.tv_nsec, nsec); + BOOST_CHECK_EQUAL(t2.is_zero(), false); + time_ns t3(t1); + BOOST_CHECK_EQUAL(t3.tv_sec, 0); + BOOST_CHECK_EQUAL(t3.tv_nsec, 0); + BOOST_CHECK_EQUAL(t3.is_zero(), true); + time_ns t4(t2); + BOOST_CHECK_EQUAL(t4.tv_sec, sec); + BOOST_CHECK_EQUAL(t4.tv_nsec, nsec); + BOOST_CHECK_EQUAL(t4.is_zero(), false); + t4.set_zero(); + BOOST_CHECK_EQUAL(t4.tv_sec, 0); + BOOST_CHECK_EQUAL(t4.tv_nsec, 0); + BOOST_CHECK_EQUAL(t4.is_zero(), true); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(operators) +{ + cout << test_filename << ".operators: " << flush; + const std::time_t sec1 = 123; + const long nsec1 = 123456789; + const std::time_t sec2 = 1; + const long nsec2 = 999999999; + const std::time_t sec_sum = sec1 + sec2 + 1; + const long nsec_sum = nsec1 + nsec2 - 1000000000; + const std::time_t sec_1_minus_2 = sec1 - sec2 - 1; + const long nsec_1_minus_2 = nsec1 - nsec2 + 1000000000; + const std::time_t sec_2_minus_1 = sec2 - sec1; + const long nsec_2_minus_1 = nsec2 - nsec1; + time_ns z; + time_ns t1(sec1, nsec1); + time_ns t2(sec2, nsec2); + + time_ns t3 = z; + BOOST_CHECK_EQUAL(t3.tv_sec, 0); + BOOST_CHECK_EQUAL(t3.tv_nsec, 0); + BOOST_CHECK_EQUAL(t3 == z, true); + BOOST_CHECK_EQUAL(t3 != z, false); + BOOST_CHECK_EQUAL(t3 > z, false); + BOOST_CHECK_EQUAL(t3 >= z, true); + BOOST_CHECK_EQUAL(t3 < z, false); + BOOST_CHECK_EQUAL(t3 <= z, true); + + t3 = t1; + BOOST_CHECK_EQUAL(t3.tv_sec, sec1); + BOOST_CHECK_EQUAL(t3.tv_nsec, nsec1); + BOOST_CHECK_EQUAL(t3 == t1, true); + BOOST_CHECK_EQUAL(t3 != t1, false); + BOOST_CHECK_EQUAL(t3 > t1, false); + BOOST_CHECK_EQUAL(t3 >= t1, true); + BOOST_CHECK_EQUAL(t3 < t1, false); + BOOST_CHECK_EQUAL(t3 <= t1, true); + + t3 += z; + BOOST_CHECK_EQUAL(t3.tv_sec, sec1); + BOOST_CHECK_EQUAL(t3.tv_nsec, nsec1); + + t3 = t2; + BOOST_CHECK_EQUAL(t3.tv_sec, sec2); + BOOST_CHECK_EQUAL(t3.tv_nsec, nsec2); + BOOST_CHECK_EQUAL(t3 == t2, true); + BOOST_CHECK_EQUAL(t3 != t2, false); + BOOST_CHECK_EQUAL(t3 > t2, false); + BOOST_CHECK_EQUAL(t3 >= t2, true); + BOOST_CHECK_EQUAL(t3 < t2, false); + BOOST_CHECK_EQUAL(t3 <= t2, true); + + t3 += z; + BOOST_CHECK_EQUAL(t3.tv_sec, sec2); + BOOST_CHECK_EQUAL(t3.tv_nsec, nsec2); + + t3 = t1; + t3 += t2; + BOOST_CHECK_EQUAL(t3.tv_sec, sec_sum); + BOOST_CHECK_EQUAL(t3.tv_nsec, nsec_sum); + + t3 = t1; + t3 -= t2; + BOOST_CHECK_EQUAL(t3.tv_sec, sec_1_minus_2); + BOOST_CHECK_EQUAL(t3.tv_nsec, nsec_1_minus_2); + + t3 = t2; + t3 -= t1; + BOOST_CHECK_EQUAL(t3.tv_sec, sec_2_minus_1); + BOOST_CHECK_EQUAL(t3.tv_nsec, nsec_2_minus_1); + + t3 = t1 + t2; + BOOST_CHECK_EQUAL(t3.tv_sec, sec_sum); + BOOST_CHECK_EQUAL(t3.tv_nsec, nsec_sum); + + t3 = t1 - t2; + BOOST_CHECK_EQUAL(t3.tv_sec, sec_1_minus_2); + BOOST_CHECK_EQUAL(t3.tv_nsec, nsec_1_minus_2); + + t3 = t2 - t1; + BOOST_CHECK_EQUAL(t3.tv_sec, sec_2_minus_1); + BOOST_CHECK_EQUAL(t3.tv_nsec, nsec_2_minus_1); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(str) +{ + cout << test_filename << ".str: " << flush; + time_ns t1(123, 123456789); + BOOST_CHECK_EQUAL(t1.str(), "123.123457"); + BOOST_CHECK_EQUAL(t1.str(9), "123.123456789"); + BOOST_CHECK_EQUAL(t1.str(0), "123"); + time_ns t2(1, 1); + BOOST_CHECK_EQUAL(t2.str(9), "1.000000001"); + time_ns t3(-12, 345); + BOOST_CHECK_EQUAL(t3.str(9), "-11.999999655"); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_ut_txn_map.cpp b/qpid/cpp/src/tests/legacystore/jrnl/_ut_txn_map.cpp new file mode 100644 index 0000000000..595ce0f6c6 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/_ut_txn_map.cpp @@ -0,0 +1,106 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "../unit_test.h" + +#include <iomanip> +#include <iostream> +#include "qpid/legacystore/jrnl/txn_map.h" +#include <sstream> + +using namespace boost::unit_test; +using namespace mrg::journal; +using namespace std; + +QPID_AUTO_TEST_SUITE(txn_map_suite) + +const string test_filename("_ut_txn_map"); + +// === Helper functions === + +const string make_xid(u_int64_t rid) +{ + stringstream ss; + ss << "XID-" << setfill('0') << setw(16) << hex << rid; + ss << "-0123456789abcdef"; + return ss.str(); +} + +void check_td_equal(txn_data& td1, txn_data& td2) +{ + BOOST_CHECK_EQUAL(td1._rid, td2._rid); + BOOST_CHECK_EQUAL(td1._drid, td2._drid); + BOOST_CHECK_EQUAL(td1._pfid, td2._pfid); + BOOST_CHECK_EQUAL(td1._enq_flag, td2._enq_flag); + BOOST_CHECK_EQUAL(td1._aio_compl, td2._aio_compl); +} + +// === Test suite === + +QPID_AUTO_TEST_CASE(constructor) +{ + cout << test_filename << ".constructor: " << flush; + const u_int64_t rid = 0x123456789abcdef0ULL; + const u_int64_t drid = 0xfedcba9876543210ULL; + const u_int16_t pfid = 0xfedcU; + const bool enq_flag = true; + txn_data td(rid, drid, pfid, enq_flag); + BOOST_CHECK_EQUAL(td._rid, rid); + BOOST_CHECK_EQUAL(td._drid, drid); + BOOST_CHECK_EQUAL(td._pfid, pfid); + BOOST_CHECK_EQUAL(td._enq_flag, enq_flag); + BOOST_CHECK_EQUAL(td._aio_compl, false); + + txn_map t1; + BOOST_CHECK(t1.empty()); + BOOST_CHECK_EQUAL(t1.size(), u_int32_t(0)); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(insert_get) +{ + cout << test_filename << ".insert_get: " << flush; + u_int16_t fid; + u_int64_t rid; + u_int16_t pfid_start = 0x2000U; + u_int64_t rid_begin = 0xffffffff00000000ULL; + u_int64_t rid_end = 0xffffffff00000200ULL; + + // insert with no dups + u_int64_t rid_incr_1 = 4ULL; + txn_map t2; + t2.set_num_jfiles(pfid_start + (rid_end - rid_begin)/rid_incr_1); + for (rid = rid_begin, fid = pfid_start; rid < rid_end; rid += rid_incr_1, fid++) + t2.insert_txn_data(make_xid(rid), txn_data(rid, ~rid, fid, false)); + BOOST_CHECK(!t2.empty()); + BOOST_CHECK_EQUAL(t2.size(), u_int32_t(128)); + + // get + u_int64_t rid_incr_2 = 6ULL; + for (u_int64_t rid = rid_begin; rid < rid_end; rid += rid_incr_2) + { + string xid = make_xid(rid); + BOOST_CHECK_EQUAL(t2.in_map(xid), (rid%rid_incr_1 ? false : true)); + } + cout << "ok" << endl; +} + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/legacystore/jrnl/chk_jdata b/qpid/cpp/src/tests/legacystore/jrnl/chk_jdata new file mode 100755 index 0000000000..2ac87d91b9 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/chk_jdata @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + + +JRNL_BLK_SIZE=512 # Block size in bytes +JRNL_PAGE_SIZE=256 # Journal page size in blocks +JRNL_FILE_SIZE=12 # Journal file size in pages +let END_OFFSET=${JRNL_BLK_SIZE}*${JRNL_PAGE_SIZE}*${JRNL_FILE_SIZE} +for f in jdata/test.*.jdat; do + echo $f + hexdump -C -n 1024 $f + hexdump -C -s ${END_OFFSET} $f + echo "============" +done diff --git a/qpid/cpp/src/tests/legacystore/jrnl/cp_rtest_jrnl b/qpid/cpp/src/tests/legacystore/jrnl/cp_rtest_jrnl new file mode 100755 index 0000000000..e21f991788 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/cp_rtest_jrnl @@ -0,0 +1,59 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +JDATA_DIR=jdata +TAR_DIR=rd_test_jrnls + +function get_filename +{ + local prefix=$1 + local file_num=$2 + local suffix=$3 + + if (( file_num < 10 )); then + local num="000${file_num}" + elif (( file_num < 100 )); then + local num="00${file_num}" + elif (( file_num < 1000 )); then + local num="0${file_num}" + else + local num="${file_num}" + fi + FILENAME=${prefix}${num}${suffix} + return 0 +} + +if (( $# != 1 )); then + echo "Incorrect args, expected 1 arg (usage: \"prep <testnum>\")" + exit +fi + +get_filename "t" $1 ".tar.gz" +if [[ -d ${JDATA_DIR} ]]; then + rm -rf ${JDATA_DIR}/* +else + mkdir -p ${JDATA_DIR} +fi +if [[ -f "${TAR_DIR}/${FILENAME}" ]]; then + tar -C ${JDATA_DIR} -xzf "${TAR_DIR}/${FILENAME}" +else + echo "Error: file \"${TAR_DIR}/${FILENAME}\" not found." +fi diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jhexdump b/qpid/cpp/src/tests/legacystore/jrnl/jhexdump new file mode 100755 index 0000000000..b013914441 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jhexdump @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +if [ -z "$1" ]; then + echo "No directory specified." + exit +fi + +JDIR=$1 +echo "Target directory: ${JDIR}" + +rm -f j*.txt + +if [ -d "${JDIR}" ]; then + n=0 + for f in "${JDIR}"/*.jdat; do + echo "$f -> j$n.txt" + hexdump -C "$f" > j$n.txt + (( n += 1 )) + done +else + echo "This directory does not exist." +fi diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_data_src.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_data_src.cpp new file mode 100644 index 0000000000..e4656ef83f --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_data_src.cpp @@ -0,0 +1,207 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "../../unit_test.h" +#include <cstddef> +#include "data_src.h" +#include <iomanip> +#include <iostream> + +using namespace boost::unit_test; +using namespace mrg::jtt; +using namespace std; + +QPID_AUTO_TEST_SUITE(jtt_data_src) + +const string test_filename("_ut_data_src"); + +long +get_seed() +{ + timespec ts; + if (::clock_gettime(CLOCK_REALTIME, &ts)) + BOOST_FAIL("Unable to read clock to generate seed."); + long tenths = ts.tv_nsec / 100000000; + return long(10 * ts.tv_sec + tenths); // time in tenths of a second +} + +#ifndef LONG_TEST +/* + * ============================================== + * NORMAL TESTS + * This section contains normal "make check" tests + * for building/packaging. These are built when + * LONG_TEST is _not_ defined. + * ============================================== + */ + +QPID_AUTO_TEST_CASE(data) +{ + cout << test_filename << ".data: " << flush; + BOOST_CHECK(data_src::max_dsize > 0); + for (std::size_t i=0; i<1024; i++) + { + const char* dp = data_src::get_data(i); + BOOST_CHECK_EQUAL(*dp, static_cast<char>('0' + ((i + 1) % 10))); + } + for (std::size_t i=data_src::max_dsize-1024; i<data_src::max_dsize; i++) + { + const char* dp = data_src::get_data(i); + BOOST_CHECK_EQUAL(*dp, static_cast<char>('0' + ((i + 1) % 10))); + } + const char* dp1 = data_src::get_data(data_src::max_dsize); + BOOST_CHECK_EQUAL(dp1,(char*) 0); + const char* dp2 = data_src::get_data(data_src::max_dsize + 0x1000); + BOOST_CHECK_EQUAL(dp2, (char*)0); + cout << "ok" << endl; +} + +// There is a long version of this test in _ut_long_data_src.cpp +QPID_AUTO_TEST_CASE(xid_data_xid) +{ + const std::size_t num = 64; + cout << test_filename << ".xid_data_xid: " << flush; + BOOST_CHECK_EQUAL(data_src::get_xid(1), "0"); + BOOST_CHECK_EQUAL(data_src::get_xid(2), "01"); + BOOST_CHECK_EQUAL(data_src::get_xid(3), "002"); + BOOST_CHECK_EQUAL(data_src::get_xid(4), "0003"); + BOOST_CHECK_EQUAL(data_src::get_xid(5), "00004"); + BOOST_CHECK_EQUAL(data_src::get_xid(6), "000005"); + BOOST_CHECK_EQUAL(data_src::get_xid(7), "0000006"); + BOOST_CHECK_EQUAL(data_src::get_xid(8), "00000007"); + BOOST_CHECK_EQUAL(data_src::get_xid(9), "xid:00008"); + BOOST_CHECK_EQUAL(data_src::get_xid(10), "xid:000009"); + BOOST_CHECK_EQUAL(data_src::get_xid(11), "xid:0000010"); + BOOST_CHECK_EQUAL(data_src::get_xid(12), "xid:00000011"); + BOOST_CHECK_EQUAL(data_src::get_xid(13), "xid:00000012:"); + BOOST_CHECK_EQUAL(data_src::get_xid(14), "xid:00000013:n"); + BOOST_CHECK_EQUAL(data_src::get_xid(15), "xid:00000014:no"); + std::size_t i = 15; + for (; i<num; i++) + { + string xid(data_src::get_xid(i)); + + ostringstream oss; + oss << setfill('0') << "xid:" << setw(8) << i << ":"; + + BOOST_CHECK_EQUAL(xid.size(), i); + BOOST_CHECK_EQUAL(xid.substr(0, 13), oss.str()); + BOOST_CHECK_EQUAL(xid[13], 'n'); + BOOST_CHECK_EQUAL(xid[i-1], (char)('a' + ((i-1)%26))); + } + for (std::size_t j=data_src::max_xsize-num; j<data_src::max_xsize; j++,i++) + { + string xid(data_src::get_xid(j)); + + ostringstream oss; + oss << setfill('0') << "xid:" << setw(8) << i << ":"; + + BOOST_CHECK_EQUAL(xid.size(), j); + BOOST_CHECK_EQUAL(xid.substr(0, 13), oss.str()); + BOOST_CHECK_EQUAL(xid[13], 'n'); + BOOST_CHECK_EQUAL(xid[j-1], (char)('a' + ((j-1)%26))); + } + cout << "ok" << endl; +} + +#else +/* + * ============================================== + * LONG TESTS + * This section contains long tests and soak tests, + * and are run using target check-long (ie "make + * check-long"). These are built when LONG_TEST is + * defined. + * ============================================== + */ + +/* + * To reproduce a specific test, comment out the get_seed() statement and uncomment the literal below, adjusting the seed + * value to that required. + */ +QPID_AUTO_TEST_CASE(xid_data_xid) +{ + const long seed = get_seed(); + // const long seed = 0x2d9b69d32; + ::srand48(seed); + + const std::size_t num = 1024; + cout << test_filename << ".xid_data_xid seed=0x" << std::hex << seed << std::dec << ": " << flush; + BOOST_CHECK_EQUAL(data_src::get_xid(1), "0"); + BOOST_CHECK_EQUAL(data_src::get_xid(2), "01"); + BOOST_CHECK_EQUAL(data_src::get_xid(3), "002"); + BOOST_CHECK_EQUAL(data_src::get_xid(4), "0003"); + BOOST_CHECK_EQUAL(data_src::get_xid(5), "00004"); + BOOST_CHECK_EQUAL(data_src::get_xid(6), "000005"); + BOOST_CHECK_EQUAL(data_src::get_xid(7), "0000006"); + BOOST_CHECK_EQUAL(data_src::get_xid(8), "00000007"); + BOOST_CHECK_EQUAL(data_src::get_xid(9), "xid:00008"); + BOOST_CHECK_EQUAL(data_src::get_xid(10), "xid:000009"); + BOOST_CHECK_EQUAL(data_src::get_xid(11), "xid:0000010"); + BOOST_CHECK_EQUAL(data_src::get_xid(12), "xid:00000011"); + BOOST_CHECK_EQUAL(data_src::get_xid(13), "xid:00000012:"); + BOOST_CHECK_EQUAL(data_src::get_xid(14), "xid:00000013:n"); + BOOST_CHECK_EQUAL(data_src::get_xid(15), "xid:00000014:no"); + std::size_t i = 15; + for (; i<num; i++) + { + string xid(data_src::get_xid(i)); + + ostringstream oss; + oss << setfill('0') << "xid:" << setw(8) << i << ":"; + + BOOST_CHECK_EQUAL(xid.size(), i); + BOOST_CHECK_EQUAL(xid.substr(0, 13), oss.str()); + BOOST_CHECK_EQUAL(xid[13], 'n'); + BOOST_CHECK_EQUAL(xid[i-1], (char)('a' + ((i-1)%26))); + } + for (std::size_t j=data_src::max_xsize-num; j<data_src::max_xsize; j++,i++) + { + string xid(data_src::get_xid(j)); + + ostringstream oss; + oss << setfill('0') << "xid:" << setw(8) << i << ":"; + + BOOST_CHECK_EQUAL(xid.size(), j); + BOOST_CHECK_EQUAL(xid.substr(0, 13), oss.str()); + BOOST_CHECK_EQUAL(xid[13], 'n'); + BOOST_CHECK_EQUAL(xid[j-1], (char)('a' + ((j-1)%26))); + } + std::srand(seed); + for (int cnt=0; cnt<1000; cnt++,i++) + { + std::size_t k = 1 + ::lrand48() % (data_src::max_xsize - 1); + string xid(data_src::get_xid(k)); + + ostringstream oss; + oss << setfill('0') << "xid:" << setw(8) << i << ":"; + + BOOST_CHECK_EQUAL(xid.size(), k); + BOOST_CHECK_EQUAL(xid.substr(0, 13), oss.str()); + BOOST_CHECK_EQUAL(xid[13], 'n'); + BOOST_CHECK_EQUAL(xid[k-1], (char)('a' + ((k-1)%26))); + } + cout << "ok" << endl; +} + +#endif + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_jrnl_init_params.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_jrnl_init_params.cpp new file mode 100644 index 0000000000..9fefe25105 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_jrnl_init_params.cpp @@ -0,0 +1,100 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "../../unit_test.h" +#include "jrnl_init_params.h" +#include <iostream> + +using namespace boost::unit_test; +using namespace mrg::jtt; +using namespace std; + +QPID_AUTO_TEST_SUITE(jtt_jrnl_init_params) + +const string test_filename("_ut_jrnl_init_params"); + +QPID_AUTO_TEST_CASE(constructor) +{ + cout << test_filename << ".constructor: " << flush; + const string jid = "jid"; + const string jdir = "jdir"; + const string bfn = "base filename"; + const u_int16_t num_jfiles = 123; + const bool ae = false; + const u_int16_t ae_max_jfiles = 456; + const u_int32_t jfsize_sblks = 789; + jrnl_init_params jip(jid, jdir, bfn, num_jfiles, ae, ae_max_jfiles, jfsize_sblks); + BOOST_CHECK_EQUAL(jip.jid(), jid); + BOOST_CHECK_EQUAL(jip.jdir(), jdir); + BOOST_CHECK_EQUAL(jip.base_filename(), bfn); + BOOST_CHECK_EQUAL(jip.num_jfiles(), num_jfiles); + BOOST_CHECK_EQUAL(jip.is_ae(), ae); + BOOST_CHECK_EQUAL(jip.ae_max_jfiles(), ae_max_jfiles); + BOOST_CHECK_EQUAL(jip.jfsize_sblks(), jfsize_sblks); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(copy_constructor_1) +{ + cout << test_filename << ".copy_constructor_1: " << flush; + const string jid = "jid"; + const string jdir = "jdir"; + const string bfn = "base filename"; + const u_int16_t num_jfiles = 123; + const bool ae = false; + const u_int16_t ae_max_jfiles = 456; + const u_int32_t jfsize_sblks = 789; + jrnl_init_params jip1(jid, jdir, bfn, num_jfiles, ae, ae_max_jfiles, jfsize_sblks); + jrnl_init_params jip2(jip1); + BOOST_CHECK_EQUAL(jip2.jid(), jid); + BOOST_CHECK_EQUAL(jip2.jdir(), jdir); + BOOST_CHECK_EQUAL(jip2.base_filename(), bfn); + BOOST_CHECK_EQUAL(jip2.num_jfiles(), num_jfiles); + BOOST_CHECK_EQUAL(jip2.is_ae(), ae); + BOOST_CHECK_EQUAL(jip2.ae_max_jfiles(), ae_max_jfiles); + BOOST_CHECK_EQUAL(jip2.jfsize_sblks(), jfsize_sblks); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(copy_constructor_2) +{ + cout << test_filename << ".copy_constructor_2: " << flush; + const string jid = "jid"; + const string jdir = "jdir"; + const string bfn = "base filename"; + const u_int16_t num_jfiles = 123; + const bool ae = false; + const u_int16_t ae_max_jfiles = 456; + const u_int32_t jfsize_sblks = 789; + jrnl_init_params::shared_ptr p(new jrnl_init_params(jid, jdir, bfn, num_jfiles, ae, ae_max_jfiles, jfsize_sblks)); + jrnl_init_params jip2(p.get()); + BOOST_CHECK_EQUAL(jip2.jid(), jid); + BOOST_CHECK_EQUAL(jip2.jdir(), jdir); + BOOST_CHECK_EQUAL(jip2.base_filename(), bfn); + BOOST_CHECK_EQUAL(jip2.num_jfiles(), num_jfiles); + BOOST_CHECK_EQUAL(jip2.is_ae(), ae); + BOOST_CHECK_EQUAL(jip2.ae_max_jfiles(), ae_max_jfiles); + BOOST_CHECK_EQUAL(jip2.jfsize_sblks(), jfsize_sblks); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_SUITE_END() + diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_jrnl_instance.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_jrnl_instance.cpp new file mode 100644 index 0000000000..12f1c542d6 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_jrnl_instance.cpp @@ -0,0 +1,178 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "../../unit_test.h" + +#include <iostream> +#include "jrnl_init_params.h" +#include "jrnl_instance.h" +#include "qpid/legacystore/jrnl/jdir.h" +#include "qpid/legacystore/jrnl/jerrno.h" + +using namespace boost::unit_test; +using namespace mrg::journal; +using namespace mrg::jtt; +using namespace std; + +QPID_AUTO_TEST_SUITE(jtt_jrnl_instance) + +const string test_filename("_ut_jrnl_instance"); +const char* tdp = getenv("TMP_DATA_DIR"); +const string test_dir(tdp && strlen(tdp) > 0 ? tdp : "/var/tmp/JttTest"); + +QPID_AUTO_TEST_CASE(constructor_1) +{ + cout << test_filename << ".constructor_1: " << flush; + const string jid = "jid1"; + const string jdir = test_dir + "/test1"; + const string bfn = "test"; + const u_int16_t num_jfiles = 20; + const bool ae = false; + const u_int16_t ae_max_jfiles = 45; + const u_int32_t jfsize_sblks = 128; + + args a("a1"); + using mrg::jtt::test_case; + test_case::shared_ptr p(new test_case(1, 0, 0, 0, false, 0, 0, test_case::JTT_PERSISTNET, test_case::JDL_INTERNAL, + "t1")); + jrnl_instance ji(jid, jdir, bfn, num_jfiles, ae, ae_max_jfiles, jfsize_sblks); + ji.init_tc(p, &a); + ji.run_tc(); + ji.tc_wait_compl(); + try { jdir::verify_dir(jdir, bfn); } + catch (const jexception& e) { BOOST_ERROR(e.what()); } + jdir::delete_dir(jdir); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(constructor_2) +{ + cout << test_filename << ".constructor_2: " << flush; + const string jid = "jid2"; + const string jdir = test_dir + "/test2"; + const string bfn = "test"; + const u_int16_t num_jfiles = 20; + const bool ae = false; + const u_int16_t ae_max_jfiles = 45; + const u_int32_t jfsize_sblks = 128; + + args a("a2"); + using mrg::jtt::test_case; + test_case::shared_ptr p(new test_case(2, 0, 0, 0, false, 0, 0, test_case::JTT_PERSISTNET, test_case::JDL_INTERNAL, + "t2")); + jrnl_init_params::shared_ptr jpp(new jrnl_init_params(jid, jdir, bfn, num_jfiles, ae, ae_max_jfiles, jfsize_sblks)); + jrnl_instance ji(jpp); + ji.init_tc(p, &a); + ji.run_tc(); + ji.tc_wait_compl(); + try { jdir::verify_dir(jdir, bfn); } + catch (const jexception& e) { BOOST_ERROR(e.what()); } + jdir::delete_dir(jdir); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(constructor_3) +{ + cout << test_filename << ".constructor_3: " << flush; + const string jid = "jid3"; + const string jdir = test_dir + "/test3"; + const string bfn = "test"; + const u_int16_t num_jfiles = 20; + const bool ae = false; + const u_int16_t ae_max_jfiles = 45; + const u_int32_t jfsize_sblks = 128; + + args a("a3"); + using mrg::jtt::test_case; + test_case::shared_ptr p(new test_case(3, 0, 0, 0, false, 0, 0, test_case::JTT_PERSISTNET, test_case::JDL_INTERNAL, + "t3")); + jrnl_init_params::shared_ptr jpp(new jrnl_init_params(jid, jdir, bfn, num_jfiles, ae, ae_max_jfiles, jfsize_sblks)); + jrnl_instance ji(jpp); + ji.init_tc(p, &a); + ji.run_tc(); + ji.tc_wait_compl(); + try { jdir::verify_dir(jdir, bfn); } + catch (const jexception& e) { BOOST_ERROR(e.what()); } + jdir::delete_dir(jdir); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(recover) +{ + cout << test_filename << ".recover: " << flush; + const string jid = "jid5"; + const string jdir = test_dir + "/test5"; + const string bfn = "test"; + const u_int16_t num_jfiles = 20; + const bool ae = false; + const u_int16_t ae_max_jfiles = 0; + const u_int32_t jfsize_sblks = 128; + + args a("a4"); + using mrg::jtt::test_case; + test_case::shared_ptr p(new test_case(5, 0, 0, 0, false, 0, 0, test_case::JTT_PERSISTNET, test_case::JDL_INTERNAL, + "t5")); + jrnl_init_params::shared_ptr jpp(new jrnl_init_params(jid, jdir, bfn, num_jfiles, ae, ae_max_jfiles, jfsize_sblks)); + jrnl_instance ji(jpp); + ji.init_tc(p, &a); + ji.run_tc(); + ji.tc_wait_compl(); + try { jdir::verify_dir(jdir, bfn); } + catch (const jexception& e) { BOOST_ERROR(e.what()); } + a.recover_mode = true; + ji.init_tc(p, &a); + ji.run_tc(); + ji.tc_wait_compl(); + try { jdir::verify_dir(jdir, bfn); } + catch (const jexception& e) { BOOST_ERROR(e.what()); } + jdir::delete_dir(jdir); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(recover_no_files) +{ + cout << test_filename << ".recover_no_files: " << flush; + const string jid = "jid6"; + const string jdir = test_dir + "/test6"; + const string bfn = "test"; + const u_int16_t num_jfiles = 20; + const bool ae = false; + const u_int16_t ae_max_jfiles = 0; + const u_int32_t jfsize_sblks = 128; + + args a("a5"); + a.recover_mode = true; + using mrg::jtt::test_case; + test_case::shared_ptr p(new test_case(6, 0, 0, 0, false, 0, 0, test_case::JTT_PERSISTNET, test_case::JDL_INTERNAL, + "t6")); + jrnl_init_params::shared_ptr jpp(new jrnl_init_params(jid, jdir, bfn, num_jfiles, ae, ae_max_jfiles, jfsize_sblks)); + jrnl_instance ji(jpp); + ji.init_tc(p, &a); + ji.run_tc(); + ji.tc_wait_compl(); + try { jdir::verify_dir(jdir, bfn); } + catch (const jexception& e) { BOOST_ERROR(e.what()); } + jdir::delete_dir(jdir); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_SUITE_END() + diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_read_arg.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_read_arg.cpp new file mode 100644 index 0000000000..0d2025270d --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_read_arg.cpp @@ -0,0 +1,146 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "../../unit_test.h" +#include <boost/test/unit_test_log.hpp> +#include "read_arg.h" +#include <iostream> + +#include <boost/program_options.hpp> +namespace po = boost::program_options; +using namespace mrg::jtt; +using namespace boost::unit_test; +using namespace std; + +QPID_AUTO_TEST_SUITE(jtt_read_arg) + +const string test_filename("_ut_read_arg"); + +QPID_AUTO_TEST_CASE(constructor) +{ + cout << test_filename << ".constructor: " << flush; + read_arg ra1; + BOOST_CHECK_EQUAL(ra1.val(), read_arg::NONE); + BOOST_CHECK_EQUAL(ra1.str(), "NONE"); + read_arg ra2(read_arg::NONE); + BOOST_CHECK_EQUAL(ra2.val(), read_arg::NONE); + BOOST_CHECK_EQUAL(ra2.str(), "NONE"); + read_arg ra3(read_arg::ALL); + BOOST_CHECK_EQUAL(ra3.val(), read_arg::ALL); + BOOST_CHECK_EQUAL(ra3.str(), "ALL"); + read_arg ra4(read_arg::RANDOM); + BOOST_CHECK_EQUAL(ra4.val(), read_arg::RANDOM); + BOOST_CHECK_EQUAL(ra4.str(), "RANDOM"); + read_arg ra5(read_arg::LAZYLOAD); + BOOST_CHECK_EQUAL(ra5.val(), read_arg::LAZYLOAD); + BOOST_CHECK_EQUAL(ra5.str(), "LAZYLOAD"); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(set_val) +{ + cout << test_filename << ".set_val: " << flush; + read_arg ra; + BOOST_CHECK_EQUAL(ra.val(), read_arg::NONE); + BOOST_CHECK_EQUAL(ra.str(), "NONE"); + ra.set_val(read_arg::ALL); + BOOST_CHECK_EQUAL(ra.val(), read_arg::ALL); + BOOST_CHECK_EQUAL(ra.str(), "ALL"); + ra.set_val(read_arg::RANDOM); + BOOST_CHECK_EQUAL(ra.val(), read_arg::RANDOM); + BOOST_CHECK_EQUAL(ra.str(), "RANDOM"); + ra.set_val(read_arg::LAZYLOAD); + BOOST_CHECK_EQUAL(ra.val(), read_arg::LAZYLOAD); + BOOST_CHECK_EQUAL(ra.str(), "LAZYLOAD"); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(parse) +{ + cout << test_filename << ".parse: " << flush; + read_arg ra; + ra.parse("LAZYLOAD"); + BOOST_CHECK_EQUAL(ra.val(), read_arg::LAZYLOAD); + BOOST_CHECK_EQUAL(ra.str(), "LAZYLOAD"); + ra.parse("ALL"); + BOOST_CHECK_EQUAL(ra.val(), read_arg::ALL); + BOOST_CHECK_EQUAL(ra.str(), "ALL"); + BOOST_CHECK_THROW(ra.parse(""), po::invalid_option_value) + BOOST_CHECK_EQUAL(ra.val(), read_arg::ALL); + BOOST_CHECK_EQUAL(ra.str(), "ALL"); + BOOST_CHECK_THROW(ra.parse("abc123"), po::invalid_option_value) + BOOST_CHECK_EQUAL(ra.val(), read_arg::ALL); + BOOST_CHECK_EQUAL(ra.str(), "ALL"); + ra.parse("NONE"); + BOOST_CHECK_EQUAL(ra.val(), read_arg::NONE); + BOOST_CHECK_EQUAL(ra.str(), "NONE"); + ra.parse("RANDOM"); + BOOST_CHECK_EQUAL(ra.val(), read_arg::RANDOM); + BOOST_CHECK_EQUAL(ra.str(), "RANDOM"); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(istream_) +{ + cout << test_filename << ".istream_: " << flush; + read_arg ra; + istringstream ss1("LAZYLOAD", ios::in); + ss1 >> ra; + BOOST_CHECK_EQUAL(ra.val(), read_arg::LAZYLOAD); + BOOST_CHECK_EQUAL(ra.str(), "LAZYLOAD"); + istringstream ss2("ALL", ios::in); + ss2 >> ra; + BOOST_CHECK_EQUAL(ra.val(), read_arg::ALL); + BOOST_CHECK_EQUAL(ra.str(), "ALL"); + istringstream ss3("NONE", ios::in); + ss3 >> ra; + BOOST_CHECK_EQUAL(ra.val(), read_arg::NONE); + BOOST_CHECK_EQUAL(ra.str(), "NONE"); + istringstream ss4("RANDOM", ios::in); + ss4 >> ra; + BOOST_CHECK_EQUAL(ra.val(), read_arg::RANDOM); + BOOST_CHECK_EQUAL(ra.str(), "RANDOM"); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(ostream_) +{ + cout << test_filename << ".ostream_: " << flush; + ostringstream s1; + read_arg ra(read_arg::LAZYLOAD); + s1 << ra; + BOOST_CHECK_EQUAL(s1.str(), "LAZYLOAD"); + ra.set_val(read_arg::ALL); + ostringstream s2; + s2 << ra; + BOOST_CHECK_EQUAL(s2.str(), "ALL"); + ra.set_val(read_arg::NONE); + ostringstream s3; + s3 << ra; + BOOST_CHECK_EQUAL(s3.str(), "NONE"); + ra.set_val(read_arg::RANDOM); + ostringstream s4; + s4 << ra; + BOOST_CHECK_EQUAL(s4.str(), "RANDOM"); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case.cpp new file mode 100644 index 0000000000..3a7d0f951c --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case.cpp @@ -0,0 +1,113 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "../../unit_test.h" +#include <cstddef> +#include <iomanip> +#include <iostream> +#include "test_case.h" +#include "test_case_result.h" + +using namespace boost::unit_test; +using namespace mrg::jtt; +using namespace std; + +QPID_AUTO_TEST_SUITE(jtt_test_case) + +const string test_filename("_ut_test_case"); + +QPID_AUTO_TEST_CASE(constructor) +{ + cout << test_filename << ".constructor: " << flush; + const unsigned test_case_num = 0x12345; + const u_int32_t num_msgs = 0x100; + const std::size_t min_data_size = 0x1000; + const std::size_t max_data_size = 0; + const bool auto_deq = true; + const std::size_t min_xid_size = 0x200; + const std::size_t max_xid_size = 0x200; + using mrg::jtt::test_case; + const test_case::transient_t transient = test_case::JTT_PERSISTNET; + const test_case::external_t external = test_case::JDL_INTERNAL; + const string comment = "This is a test"; + + test_case tc(test_case_num, num_msgs, min_data_size, max_data_size, auto_deq, + min_xid_size, max_xid_size, transient, external, comment); + BOOST_CHECK_EQUAL(tc.test_case_num(), test_case_num); + BOOST_CHECK_EQUAL(tc.num_msgs(), num_msgs); + BOOST_CHECK_EQUAL(tc.min_data_size(), min_data_size); + BOOST_CHECK_EQUAL(tc.max_data_size(), max_data_size); + BOOST_CHECK_EQUAL(tc.auto_deq(), auto_deq); + BOOST_CHECK_EQUAL(tc.min_xid_size(), min_xid_size); + BOOST_CHECK_EQUAL(tc.max_xid_size(), max_xid_size); + BOOST_CHECK_EQUAL(tc.transient(), transient); + BOOST_CHECK_EQUAL(tc.external(), external); + BOOST_CHECK_EQUAL(tc.comment(), comment); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(results) +{ + cout << test_filename << ".results: " << flush; + const unsigned test_case_num = 0x12345; + const u_int32_t num_msgs = 0x100; + const std::size_t min_data_size = 0x1000; + const std::size_t max_data_size = 0; + const bool auto_deq = true; + const std::size_t min_xid_size = 0x200; + const std::size_t max_xid_size = 0x200; + using mrg::jtt::test_case; + const test_case::transient_t transient = test_case::JTT_PERSISTNET; + const test_case::external_t external = test_case::JDL_INTERNAL; + const string comment = "This is a test"; + const unsigned num_results = 20; + + test_case tc(test_case_num, num_msgs, min_data_size, max_data_size, auto_deq, + min_xid_size, max_xid_size, transient, external, comment); + for (unsigned i=0; i<num_results; i++) + { + ostringstream oss; + oss << "JID_" << setfill('0') << setw(2) << i; + test_case_result::shared_ptr p(new test_case_result(oss.str())); + tc.add_result(p); + } + BOOST_CHECK_EQUAL(tc.num_results(), num_results); + test_case_result_agregation ave = tc.average(); + unsigned i=0; + for (test_case_result_agregation::tcrp_list_citr j=ave.rlist_begin(); j!=ave.rlist_end(); + i++,j++) + { + ostringstream oss; + oss << "JID_" << setfill('0') << setw(2) << i; + BOOST_CHECK_EQUAL((*j)->jid(), oss.str()); + } + for (unsigned i=0; i<num_results; i++) + { + ostringstream oss; + oss << "JID_" << setfill('0') << setw(2) << i; + BOOST_CHECK_EQUAL(ave[i]->jid(), oss.str()); + } + tc.clear(); + BOOST_CHECK_EQUAL(tc.num_results(), unsigned(0)); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case_result.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case_result.cpp new file mode 100644 index 0000000000..dd83dbee69 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case_result.cpp @@ -0,0 +1,206 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "../../unit_test.h" + +#include <iostream> +#include "qpid/legacystore/jrnl/jexception.h" +#include "test_case_result.h" + +using namespace boost::unit_test; +using namespace mrg::journal; +using namespace mrg::jtt; +using namespace std; + +QPID_AUTO_TEST_SUITE(jtt_test_case_result) + +const string test_filename("_ut_test_case_result"); + +QPID_AUTO_TEST_CASE(constructor) +{ + cout << test_filename << ".constructor: " << flush; + const string jid("journal id 1"); + test_case_result tcr(jid); + BOOST_CHECK_EQUAL(tcr.jid(), jid); + BOOST_CHECK_EQUAL(tcr.exception(), false); + BOOST_CHECK_EQUAL(tcr.exception_count(), 0U); + const time_ns& ts1 = tcr.start_time(); + BOOST_CHECK(ts1.is_zero()); + const time_ns& ts2 = tcr.stop_time(); + BOOST_CHECK(ts2.is_zero()); + const time_ns& ts3 = tcr.test_time(); + BOOST_CHECK(ts3.is_zero()); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(start_stop) +{ + cout << test_filename << ".start_stop: " << flush; + const string jid("journal id 2"); + test_case_result tcr(jid); + BOOST_CHECK_EQUAL(tcr.exception(), false); + BOOST_CHECK_EQUAL(tcr.exception_count(), 0U); + const time_ns& ts1 = tcr.start_time(); + BOOST_CHECK(ts1.is_zero()); + const time_ns& ts2 = tcr.stop_time(); + BOOST_CHECK(ts2.is_zero()); + const time_ns& ts3 = tcr.test_time(); + BOOST_CHECK(ts3.is_zero()); + + tcr.set_start_time(); + BOOST_CHECK_EQUAL(tcr.exception(), false); + BOOST_CHECK_EQUAL(tcr.exception_count(), 0U); + const time_ns& ts4 = tcr.start_time(); + BOOST_CHECK(!ts4.is_zero()); + const time_ns& ts5 = tcr.stop_time(); + BOOST_CHECK(ts5.is_zero()); + const time_ns& ts6 = tcr.test_time(); + BOOST_CHECK(ts6.is_zero()); + + ::usleep(1100000); // 1.1 sec in microseconds + tcr.set_stop_time(); + BOOST_CHECK_EQUAL(tcr.exception(), false); + BOOST_CHECK_EQUAL(tcr.exception_count(), 0U); + const time_ns& ts7 = tcr.stop_time(); + BOOST_CHECK(!ts7.is_zero()); + const time_ns& ts8 = tcr.test_time(); + BOOST_CHECK(ts8.tv_sec == 1); + BOOST_CHECK(ts8.tv_nsec > 100000000); // 0.1 sec in nanoseconds + BOOST_CHECK(ts8.tv_nsec < 200000000); // 0.2 sec in nanoseconds + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(start_exception_stop_1) +{ + cout << test_filename << ".start_exception_stop_1: " << flush; + const string jid("journal id 3"); + test_case_result tcr(jid); + const u_int32_t err_code = 0x321; + const string err_msg = "exception message"; + const jexception e(err_code, err_msg); + tcr.set_start_time(); + ::usleep(1100000); // 1.1 sec in microseconds + tcr.add_exception(e); + BOOST_CHECK_EQUAL(tcr.exception(), true); + BOOST_CHECK_EQUAL(tcr.exception_count(), 1U); + BOOST_CHECK_EQUAL(tcr[0], e.what()); + const time_ns& ts1 = tcr.stop_time(); + BOOST_CHECK(!ts1.is_zero()); + const time_ns& ts2 = tcr.test_time(); + BOOST_CHECK(ts2.tv_sec == 1); + BOOST_CHECK(ts2.tv_nsec > 100000000); // 0.1 sec in nanoseconds + BOOST_CHECK(ts2.tv_nsec < 200000000); // 0.2 sec in nanoseconds + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(start_exception_stop_2) +{ + cout << test_filename << ".start_exception_stop_2: " << flush; + const string jid("journal id 4"); + test_case_result tcr(jid); + const string err_msg = "exception message"; + tcr.set_start_time(); + ::usleep(1100000); // 1.1 sec in microseconds + tcr.add_exception(err_msg); + BOOST_CHECK_EQUAL(tcr.exception(), true); + BOOST_CHECK_EQUAL(tcr.exception_count(), 1U); + BOOST_CHECK_EQUAL(tcr[0], err_msg); + const time_ns& ts1 = tcr.stop_time(); + BOOST_CHECK(!ts1.is_zero()); + const time_ns& ts2 = tcr.test_time(); + BOOST_CHECK(ts2.tv_sec == 1); + BOOST_CHECK(ts2.tv_nsec > 100000000); // 0.1 sec in nanoseconds + BOOST_CHECK(ts2.tv_nsec < 200000000); // 0.2 sec in nanoseconds + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(start_exception_stop_3) +{ + cout << test_filename << ".start_exception_stop_3: " << flush; + const string jid("journal id 5"); + test_case_result tcr(jid); + const char* err_msg = "exception message"; + tcr.set_start_time(); + ::usleep(1100000); // 1.1 sec in microseconds + tcr.add_exception(err_msg); + BOOST_CHECK_EQUAL(tcr.exception(), true); + BOOST_CHECK_EQUAL(tcr.exception_count(), 1U); + BOOST_CHECK_EQUAL(tcr[0], err_msg); + const time_ns& ts1 = tcr.stop_time(); + BOOST_CHECK(!ts1.is_zero()); + const time_ns& ts2 = tcr.test_time(); + BOOST_CHECK(ts2.tv_sec == 1); + BOOST_CHECK(ts2.tv_nsec > 100000000); // 0.1 sec in nanoseconds + BOOST_CHECK(ts2.tv_nsec < 200000000); // 0.2 sec in nanoseconds + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(start_exception) +{ + cout << test_filename << ".start_exception: " << flush; + const string jid("journal id 6"); + test_case_result tcr(jid); + u_int32_t err_code = 0x654; + const string err_msg = "exception message"; + const jexception e(err_code, err_msg); + tcr.set_start_time(); + ::usleep(1100000); // 1.1 sec in microseconds + tcr.add_exception(e, false); + BOOST_CHECK_EQUAL(tcr.exception(), true); + BOOST_CHECK_EQUAL(tcr.exception_count(), 1U); + BOOST_CHECK_EQUAL(tcr[0], e.what()); + const time_ns& ts1 = tcr.stop_time(); + BOOST_CHECK(ts1.is_zero()); + const time_ns& ts2 = tcr.test_time(); + BOOST_CHECK(ts2.is_zero()); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(counters) +{ + cout << test_filename << ".counters: " << flush; + const u_int32_t num_enq = 125; + const u_int32_t num_deq = 64; + const u_int32_t num_read = 22; + const string jid("journal id 7"); + test_case_result tcr(jid); + BOOST_CHECK_EQUAL(tcr.num_enq(), u_int32_t(0)); + BOOST_CHECK_EQUAL(tcr.num_deq(), u_int32_t(0)); + BOOST_CHECK_EQUAL(tcr.num_read(), u_int32_t(0)); + for (unsigned i=0; i<num_enq; i++) + tcr.incr_num_enq(); + BOOST_CHECK_EQUAL(tcr.num_enq(), num_enq); + BOOST_CHECK_EQUAL(tcr.num_deq(), u_int32_t(0)); + BOOST_CHECK_EQUAL(tcr.num_read(), u_int32_t(0)); + for (unsigned j=0; j<num_deq; j++) + tcr.incr_num_deq(); + BOOST_CHECK_EQUAL(tcr.num_enq(), num_enq); + BOOST_CHECK_EQUAL(tcr.num_deq(), num_deq); + BOOST_CHECK_EQUAL(tcr.num_read(), u_int32_t(0)); + for (unsigned k=0; k<num_read; k++) + tcr.incr_num_read(); + BOOST_CHECK_EQUAL(tcr.num_enq(), num_enq); + BOOST_CHECK_EQUAL(tcr.num_deq(), num_deq); + BOOST_CHECK_EQUAL(tcr.num_read(), num_read); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case_result_agregation.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case_result_agregation.cpp new file mode 100644 index 0000000000..aa01bf833d --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case_result_agregation.cpp @@ -0,0 +1,178 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "../../unit_test.h" +#include <ctime> +#include <iostream> +#include "test_case_result_agregation.h" + +using namespace boost::unit_test; +using namespace mrg::journal; +using namespace mrg::jtt; +using namespace std; + +QPID_AUTO_TEST_SUITE(jtt_test_case_result_agregation) + +const string test_filename("_ut_test_case_result_agregation"); + +// === Helper functions === + +void check_agregate(const test_case_result_agregation& tcra, const u_int32_t num_enq, + const u_int32_t num_deq, const u_int32_t num_reads, const u_int32_t num_results, + const u_int32_t num_exceptions, const std::time_t secs, const long nsec) +{ + BOOST_CHECK_EQUAL(tcra.num_enq(), num_enq); + BOOST_CHECK_EQUAL(tcra.num_deq(), num_deq); + BOOST_CHECK_EQUAL(tcra.num_read(), num_reads); + BOOST_CHECK_EQUAL(tcra.num_results(), num_results); + BOOST_CHECK_EQUAL(tcra.exception_count(), num_exceptions); + BOOST_CHECK_EQUAL(tcra.exception(), num_exceptions > 0); + const time_ns& ts1 = tcra.test_time(); + BOOST_CHECK_EQUAL(ts1.tv_sec, secs); + BOOST_CHECK_EQUAL(ts1.tv_nsec, nsec); +} + +test_case_result::shared_ptr make_result(const string& jid, const u_int32_t num_enq, + const u_int32_t num_deq, const u_int32_t num_reads, const std::time_t secs, const long nsec) +{ + test_case_result::shared_ptr tcrp(new test_case_result(jid)); + for (unsigned i=0; i<num_enq; i++) + tcrp->incr_num_enq(); + for (unsigned i=0; i<num_deq; i++) + tcrp->incr_num_deq(); + for (unsigned i=0; i<num_reads; i++) + tcrp->incr_num_read(); + time_ns ts(secs, nsec); + tcrp->set_test_time(ts); + return tcrp; +} + +// === Test suite === + +QPID_AUTO_TEST_CASE(constructor_1) +{ + cout << test_filename << ".constructor_1: " << flush; + test_case_result_agregation tcra; + BOOST_CHECK_EQUAL(tcra.tc_average_mode(), true); + BOOST_CHECK_EQUAL(tcra.jid(), "Average"); + BOOST_CHECK_EQUAL(tcra.exception(), false); + BOOST_CHECK_EQUAL(tcra.exception_count(), 0U); + const time_ns& ts1 = tcra.start_time(); + BOOST_CHECK(ts1.is_zero()); + const time_ns& ts2 = tcra.stop_time(); + BOOST_CHECK(ts2.is_zero()); + const time_ns& ts3 = tcra.test_time(); + BOOST_CHECK(ts3.is_zero()); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(constructor_2) +{ + cout << test_filename << ".constructor_2: " << flush; + string jid("journal id"); + test_case_result_agregation tcra(jid); + BOOST_CHECK_EQUAL(tcra.tc_average_mode(), false); + BOOST_CHECK_EQUAL(tcra.jid(), jid); + BOOST_CHECK_EQUAL(tcra.exception(), false); + BOOST_CHECK_EQUAL(tcra.exception_count(), 0U); + const time_ns& ts1 = tcra.start_time(); + BOOST_CHECK(ts1.is_zero()); + const time_ns& ts2 = tcra.stop_time(); + BOOST_CHECK(ts2.is_zero()); + const time_ns& ts3 = tcra.test_time(); + BOOST_CHECK(ts3.is_zero()); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(add_test_case) +{ + cout << test_filename << ".add_test_case: " << flush; + string jid("jid1"); + test_case_result::shared_ptr tcrp1 = make_result("jid1", 10, 10, 0, 1, 101010101L); + test_case_result::shared_ptr tcrp2 = make_result("jid1", 25, 0, 35, 10, 20202020L); + test_case_result::shared_ptr tcrp3 = make_result("jid1", 0, 15, 5, 2, 555555555L); + test_case_result::shared_ptr tcrp4 = make_result("jid2", 100, 100, 100, 100, 323232324L); + test_case_result::shared_ptr tcrp5 = make_result("jid1", 5, 0, 0, 0, 100L); + tcrp5->add_exception(string("error 1"), false); + test_case_result::shared_ptr tcrp6 = make_result("jid3", 0, 5, 0, 0, 100L); + jexception e(0x123, "exception 2"); + tcrp6->add_exception(e, false); + test_case_result::shared_ptr tcrp7 = make_result("jid1", 0, 0, 0, 0, 0L); + test_case_result::shared_ptr tcrp8 = make_result("jid1", 200, 100, 300, 12, 323232224L); + + test_case_result_agregation tcra(jid); + check_agregate(tcra, 0, 0, 0, 0, 0, 0, 0L); + tcra.add_test_result(tcrp1); + check_agregate(tcra, 10, 10, 0, 1, 0, 1, 101010101L); + tcra.add_test_result(tcrp2); + check_agregate(tcra, 35, 10, 35, 2, 0, 11, 121212121L); + tcra.add_test_result(tcrp3); + check_agregate(tcra, 35, 25, 40, 3, 0, 13, 676767676L); + tcra.add_test_result(tcrp4); + check_agregate(tcra, 35, 25, 40, 3, 0, 13, 676767676L); + tcra.add_test_result(tcrp5); + check_agregate(tcra, 40, 25, 40, 4, 1, 13, 676767776L); + tcra.add_test_result(tcrp6); + check_agregate(tcra, 40, 25, 40, 4, 1, 13, 676767776L); + tcra.add_test_result(tcrp7); + check_agregate(tcra, 40, 25, 40, 5, 1, 13, 676767776L); + tcra.add_test_result(tcrp8); + check_agregate(tcra, 240, 125, 340, 6, 1, 26, 0L); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(add_test_case_average) +{ + cout << test_filename << ".add_test_case_average: " << flush; + test_case_result::shared_ptr tcrp1 = make_result("jid1", 10, 10, 0, 1, 101010101L); + test_case_result::shared_ptr tcrp2 = make_result("jid2", 25, 0, 35, 10, 20202020L); + test_case_result::shared_ptr tcrp3 = make_result("jid3", 0, 15, 5, 2, 555555555L); + test_case_result::shared_ptr tcrp4 = make_result("jid4", 100, 100, 100, 100, 323232324L); + test_case_result::shared_ptr tcrp5 = make_result("jid5", 5, 0, 0, 0, 100L); + tcrp5->add_exception(string("error 1"), false); + test_case_result::shared_ptr tcrp6 = make_result("jid6", 0, 5, 0, 0, 100L); + jexception e(0x123, "exception 2"); + tcrp6->add_exception(e, false); + test_case_result::shared_ptr tcrp7 = make_result("jid7", 0, 0, 0, 0, 0L); + test_case_result::shared_ptr tcrp8 = make_result("jid8", 200, 100, 300, 12, 222222022L); + + test_case_result_agregation tcra; + check_agregate(tcra, 0, 0, 0, 0, 0, 0, 0L); + tcra.add_test_result(tcrp1); + check_agregate(tcra, 10, 10, 0, 1, 0, 1, 101010101L); + tcra.add_test_result(tcrp2); + check_agregate(tcra, 35, 10, 35, 2, 0, 11, 121212121L); + tcra.add_test_result(tcrp3); + check_agregate(tcra, 35, 25, 40, 3, 0, 13, 676767676L); + tcra.add_test_result(tcrp4); + check_agregate(tcra, 135, 125, 140, 4, 0, 114, 0L); + tcra.add_test_result(tcrp5); + check_agregate(tcra, 140, 125, 140, 5, 1, 114, 100L); + tcra.add_test_result(tcrp6); + check_agregate(tcra, 140, 130, 140, 6, 2, 114, 200L); + tcra.add_test_result(tcrp7); + check_agregate(tcra, 140, 130, 140, 7, 2, 114, 200L); + tcra.add_test_result(tcrp8); + check_agregate(tcra, 340, 230, 440, 8, 2, 126, 222222222L); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case_set.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case_set.cpp new file mode 100644 index 0000000000..adbdf6884b --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case_set.cpp @@ -0,0 +1,147 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "../../unit_test.h" +#include <cstddef> +#include <iostream> +#include <sys/stat.h> +#include "test_case.h" +#include "test_case_set.h" + +using namespace boost::unit_test; +using namespace mrg::jtt; +using namespace std; + +QPID_AUTO_TEST_SUITE(jtt_test_case_set) + +const string csv_file("_ut_test_case_set.csv"); +const string test_filename("_ut_test_case_set"); + +// === Helper functions === + +bool check_csv_file(const char* filename) +{ + struct stat s; + if (::stat(filename, &s)) + return false; + if (S_ISREG(s.st_mode)) + return true; + return false; +} + +// === Test suite === + +QPID_AUTO_TEST_CASE(constructor) +{ + cout << test_filename << ".constructor: " << flush; + test_case_set tcs; + BOOST_CHECK(tcs.empty()); + BOOST_CHECK_EQUAL(tcs.size(), unsigned(0)); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(append_1) +{ + cout << test_filename << ".append_1: " << flush; + const unsigned test_case_num = 0x12345; + const u_int32_t num_msgs = 0x100; + const std::size_t min_data_size = 0x1000; + const std::size_t max_data_size = 0; + const bool auto_deq = true; + const std::size_t min_xid_size = 0x200; + const std::size_t max_xid_size = 0x200; + using mrg::jtt::test_case; + const test_case::transient_t transient = test_case::JTT_PERSISTNET; + const test_case::external_t external = test_case::JDL_INTERNAL; + const string comment = "This is a test"; + + test_case_set tcs; + tcs.append(test_case_num, num_msgs, min_data_size, max_data_size, auto_deq, min_xid_size, + max_xid_size, transient, external, comment); + BOOST_CHECK(!tcs.empty()); + BOOST_CHECK_EQUAL(tcs.size(), unsigned(1)); + test_case::shared_ptr tcp = tcs[0]; + BOOST_CHECK_EQUAL(tcp->test_case_num(), test_case_num); + BOOST_CHECK_EQUAL(tcp->num_msgs(), num_msgs); + BOOST_CHECK_EQUAL(tcp->min_data_size(), min_data_size); + BOOST_CHECK_EQUAL(tcp->max_data_size(), max_data_size); + BOOST_CHECK_EQUAL(tcp->min_xid_size(), min_xid_size); + BOOST_CHECK_EQUAL(tcp->max_xid_size(), max_xid_size); + BOOST_CHECK_EQUAL(tcp->transient(), transient); + BOOST_CHECK_EQUAL(tcp->external(), external); + BOOST_CHECK_EQUAL(tcp->comment(), comment); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(append_2) +{ + cout << test_filename << ".append_2: " << flush; + const unsigned test_case_num = 0x12345; + const u_int32_t num_msgs = 0x100; + const std::size_t min_data_size = 0x1000; + const std::size_t max_data_size = 0; + const bool auto_deq = true; + const std::size_t min_xid_size = 0x200; + const std::size_t max_xid_size = 0x200; + using mrg::jtt::test_case; + const test_case::transient_t transient = test_case::JTT_PERSISTNET; + const test_case::external_t external = test_case::JDL_INTERNAL; + const string comment = "This is a test"; + + test_case::shared_ptr tcp(new test_case(test_case_num, num_msgs, min_data_size, max_data_size, + auto_deq, min_xid_size, max_xid_size, transient, external, comment)); + test_case_set tcs; + tcs.append(tcp); + BOOST_CHECK(!tcs.empty()); + BOOST_CHECK_EQUAL(tcs.size(), unsigned(1)); + tcp = tcs[0]; + BOOST_CHECK_EQUAL(tcp->test_case_num(), test_case_num); + BOOST_CHECK_EQUAL(tcp->num_msgs(), num_msgs); + BOOST_CHECK_EQUAL(tcp->min_data_size(), min_data_size); + BOOST_CHECK_EQUAL(tcp->max_data_size(), max_data_size); + BOOST_CHECK_EQUAL(tcp->min_xid_size(), min_xid_size); + BOOST_CHECK_EQUAL(tcp->max_xid_size(), max_xid_size); + BOOST_CHECK_EQUAL(tcp->transient(), transient); + BOOST_CHECK_EQUAL(tcp->external(), external); + BOOST_CHECK_EQUAL(tcp->comment(), comment); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_CASE(append_from_csv) +{ + cout << test_filename << ".append_from_csv: " << flush; + test_case_set tcs; + BOOST_REQUIRE_MESSAGE(check_csv_file(csv_file.c_str()), "Test CSV file \"" << csv_file << + "\" is missing."); + tcs.append_from_csv(csv_file, false); + BOOST_CHECK(!tcs.empty()); + BOOST_CHECK_EQUAL(tcs.size(), unsigned(44)); + BOOST_CHECK_EQUAL(tcs.ignored(), unsigned(0)); + tcs.clear(); + BOOST_CHECK(tcs.empty()); + tcs.append_from_csv(csv_file, true); + BOOST_CHECK(!tcs.empty()); + BOOST_CHECK_EQUAL(tcs.size(), unsigned(18)); + BOOST_CHECK_EQUAL(tcs.ignored(), unsigned(26)); + cout << "ok" << endl; +} + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case_set.csv b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case_set.csv new file mode 100644 index 0000000000..f886186275 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case_set.csv @@ -0,0 +1,74 @@ +# +# 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. +# + +,,,,,,,"Msg size",,"Xid size",,,,,"enq-size",,"deq-size",,"txn-size",, +"Col. 0","1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20" +"Test #","tf","pf","amn","mn incr","#msgs","ms incr","Min","Max","Min","Max","auto-deq","transient","extern","bytes","dblks","bytes","dblks","bytes","dblks","comment" +,,,,,,,,,,,,,,,,,,,, +"Initialize only",,,,,,,,,,,,,,,,,,,, +0,"L",0,0,0,0,0,0,0,0,0,FALSE,FALSE,FALSE,44,1,0,0,0,0,"No messages - journal creation/initialization only" +,,,,,,,,,,,,,,,,,,,, +"Simple message combinations of persistent/deq transientueued/non-dequeued, transactional/non-transactional",,,,,,,,,,,,,,,,,,,, +1,"L",1,1,0,1,0,10,10,0,0,FALSE,FALSE,FALSE,54,1,0,0,0,0,"1 * 10-byte message" +2,"L",1,10,0,10,0,10,10,0,0,FALSE,FALSE,FALSE,54,1,0,0,0,0,"10 * 10-byte message" +3,"L",1,1,0,1,0,10,10,0,0,FALSE,TRUE,FALSE,54,1,0,0,0,0,"1 * 10-byte message [transient]" +4,"L",1,10,0,10,0,10,10,0,0,FALSE,TRUE,FALSE,54,1,0,0,0,0,"10 * 10-byte message [transient]" +5,"L",1,1,0,1,0,10,10,10,10,FALSE,FALSE,FALSE,64,1,0,0,0,0,"1 * 10-byte message [txn]" +6,"L",1,10,0,10,0,10,10,10,10,FALSE,FALSE,FALSE,64,1,0,0,0,0,"10 * 10-byte message [txn]" +7,"L",1,1,0,1,0,10,10,10,10,FALSE,TRUE,FALSE,64,1,0,0,0,0,"1 * 10-byte message [txn transient]" +8,"L",1,10,0,10,0,10,10,10,10,FALSE,TRUE,FALSE,64,1,0,0,0,0,"10 * 10-byte message [txn transient]" +9,"L",1,1,0,1,0,10,10,0,0,TRUE,FALSE,FALSE,54,1,32,1,0,0,"1 * 10-byte message [deq]" +10,"L",1,10,0,10,0,10,10,0,0,TRUE,FALSE,FALSE,54,1,32,1,0,0,"10 * 10-byte message [deq]" +11,"L",1,1,0,1,0,10,10,0,0,TRUE,TRUE,FALSE,54,1,32,1,0,0,"1 * 10-byte message [deq transient]" +12,"L",1,10,0,10,0,10,10,0,0,TRUE,TRUE,FALSE,54,1,32,1,0,0,"10 * 10-byte message [deq transient]" +13,"L",1,1,0,1,0,10,10,10,10,TRUE,FALSE,FALSE,64,1,54,1,46,1,"1 * 10-byte message [deq txn]" +14,"L",1,10,0,10,0,10,10,10,10,TRUE,FALSE,FALSE,64,1,54,1,46,1,"10 * 10-byte message [deq txn]" +15,"L",1,1,0,1,0,10,10,10,10,TRUE,TRUE,FALSE,64,1,54,1,46,1,"1 * 10-byte message [txn deq transient]" +16,"L",1,10,0,10,0,10,10,10,10,TRUE,TRUE,FALSE,64,1,54,1,46,1,"10 * 10-byte message [txn deq transient]" +17,"L",1,1,0,1,0,10,10,0,0,FALSE,FALSE,TRUE,54,1,0,0,0,0,"1 * 10-byte message [extern]" +18,"L",1,10,0,10,0,10,10,0,0,FALSE,FALSE,TRUE,54,1,0,0,0,0,"10 * 10-byte message [extern]" +19,"L",1,1,0,1,0,10,10,0,0,FALSE,TRUE,TRUE,54,1,0,0,0,0,"1 * 10-byte message [transient extern]" +20,"L",1,10,0,10,0,10,10,0,0,FALSE,TRUE,TRUE,54,1,0,0,0,0,"10 * 10-byte message [transient extern]" +21,"L",1,1,0,1,0,10,10,10,10,FALSE,FALSE,TRUE,64,1,0,0,0,0,"1 * 10-byte message [txn extern]" +22,"L",1,10,0,10,0,10,10,10,10,FALSE,FALSE,TRUE,64,1,0,0,0,0,"10 * 10-byte message [txn extern]" +23,"L",1,1,0,1,0,10,10,10,10,FALSE,TRUE,TRUE,64,1,0,0,0,0,"1 * 10-byte message [txn transient extern]" +24,"L",1,10,0,10,0,10,10,10,10,FALSE,TRUE,TRUE,64,1,0,0,0,0,"10 * 10-byte message [txn transient extern]" +25,"L",1,1,0,1,0,10,10,0,0,TRUE,FALSE,TRUE,54,1,32,1,0,0,"1 * 10-byte message [deq extern]" +26,"L",1,10,0,10,0,10,10,0,0,TRUE,FALSE,TRUE,54,1,32,1,0,0,"10 * 10-byte message [deq extern]" +27,"L",1,1,0,1,0,10,10,0,0,TRUE,TRUE,TRUE,54,1,32,1,0,0,"1 * 10-byte message [deq transient extern]" +28,"L",1,10,0,10,0,10,10,0,0,TRUE,TRUE,TRUE,54,1,32,1,0,0,"10 * 10-byte message [deq transient extern]" +29,"L",1,1,0,1,0,10,10,10,10,TRUE,FALSE,TRUE,64,1,54,1,46,1,"1 * 10-byte message [deq txn extern]" +30,"L",1,10,0,10,0,10,10,10,10,TRUE,FALSE,TRUE,64,1,54,1,46,1,"10 * 10-byte message [deq txn extern]" +31,"L",1,1,0,1,0,10,10,10,10,TRUE,TRUE,TRUE,64,1,54,1,46,1,"1 * 10-byte message [txn deq transient extern]" +32,"L",1,10,0,10,0,10,10,10,10,TRUE,TRUE,TRUE,64,1,54,1,46,1,"10 * 10-byte message [txn deq transient extern]" +,,,,,,,,,,,,,,,,,,,, +"High volume tests of random message lengths - RHM_WRONLY req'd for auto-dequeue == FALSE",,,,,,,,,,,,,,,,,,,, +33,"M",1,5000000,0,5000000,0,0,100,1,100,FALSE,RANDOM,RANDOM,244,2,0,0,0,0,"100 bytes xid max + 100 bytes data max [txn]" +34,"M",3,3000000,0,3000000,0,0,300,1,300,FALSE,RANDOM,RANDOM,644,6,0,0,0,0,"300 bytes xid max + 300 bytes data max [txn]" +35,"M",10,1600000,0,1600000,0,0,1000,1,1000,FALSE,RANDOM,RANDOM,2044,16,0,0,0,0,"1000 bytes xid max + 1000 bytes data max [txn]" +36,"M",30,600000,0,600000,0,0,3000,1,3000,FALSE,RANDOM,RANDOM,6044,48,0,0,0,0,"3000 bytes xid max + 3000 bytes data max [txn]" +37,"M",100,200000,0,200000,0,0,10000,1,10000,FALSE,RANDOM,RANDOM,20044,157,0,0,0,0,"10000 bytes xid max + 10000 bytes data max [txn]" +38,"M",300,60000,0,60000,0,0,30000,1,30000,FALSE,RANDOM,RANDOM,60044,470,0,0,0,0,"30000 bytes xid max + 30000 bytes data max [txn]" +39,"M",1000,20000,0,20000,0,0,100000,1,100000,FALSE,RANDOM,RANDOM,200044,1563,0,0,0,0,"100000 bytes xid max + 100000 bytes data max [txn]" +,,,,,,,,,,,,,,,,,,,, +"STANDARD PERFORMANCE BENCHMARK: 10,000,000 writes, data=212b (2 dblks)",,,,,,,,,,,,,,,,,,,, +40,"M",1,10000000,0,10000000,0,212,212,0,0,FALSE,FALSE,FALSE,256,2,0,0,0,0,"212 bytes data (2 dblks enq)" +41,"M",1,10000000,0,10000000,0,148,148,64,64,FALSE,FALSE,FALSE,256,2,0,0,0,0,"148 bytes data + 64 bytes xid (2 dblks enq)" +42,"M",1,10000000,0,10000000,0,212,212,0,0,TRUE,FALSE,FALSE,256,2,32,1,0,0,"212 bytes data (2 dblks enq + 1 dblk deq)" +43,"M",1,10000000,0,10000000,0,148,148,64,64,TRUE,FALSE,FALSE,256,2,108,1,100,1,"148 bytes data + 64 bytes xid (2 dblks enq + 1 dblks deq + 1 dblks txn)" diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/args.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/args.cpp new file mode 100644 index 0000000000..0f041c380e --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/args.cpp @@ -0,0 +1,226 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "args.h" + +#include <cstddef> +#include <iostream> + +namespace po = boost::program_options; + +namespace mrg +{ +namespace jtt +{ + +args::args(std::string opt_title): + _options_descr(opt_title), + format_chk(false), + keep_jrnls(false), + lld_rd_num(10), + lld_skip_num(100), + num_jrnls(1), + pause_secs(0), + randomize(false), + read_mode(), + read_prob(50), + recover_mode(false), + repeat_flag(false), + reuse_instance(false), + seed(0) +{ + _options_descr.add_options() + ("csv-file,c", + po::value<std::string>(&test_case_csv_file_name)->default_value("jtt.csv"), + "CSV file containing test cases.") + + ("format-chk", + po::value<bool>(&format_chk)->zero_tokens(), + "Check the format of each journal file.") + + ("help,h", "This help message.") + + ("jrnl-dir", + po::value<std::string>(&journal_dir)->default_value("/var/tmp/jtt"), + "Directory in which journal files will be placed.") + + ("keep-jrnls", + po::value<bool>(&keep_jrnls)->zero_tokens(), + "Keep all test journals.") + + ("lld-rd-num", + po::value<unsigned>(&lld_rd_num)->default_value(10), + "Number of consecutive messages to read after only dequeueing lld-skip-num " + "messages during lazy-loading. Ignored if read-mode is not set to LAZYLOAD.") + + ("lld-skip-num", + po::value<unsigned>(&lld_skip_num)->default_value(100), + "Number of consecutive messages to dequeue only (without reading) prior to " + "reading lld-rd-num messages. Ignored if read-mode is not set to LAZYLOAD.") + + ("num-jrnls", + po::value<unsigned>(&num_jrnls)->default_value(1), + "Number of simultaneous journal instances to test.") + + ("pause", + po::value<unsigned>(&pause_secs)->default_value(0), + "Pause in seconds between test cases (allows disk to catch up).") + + ("randomize", + po::value<bool>(&randomize)->zero_tokens(), + "Randomize the order of the tests.") + + ("read-mode", + po::value<read_arg>(&read_mode)->default_value(read_arg::NONE), + read_arg::descr().c_str()) + + ("read-prob", + po::value<unsigned>(&read_prob)->default_value(50), + "Read probability (percent) for each message when read-mode is set to RANDOM.") + + ("recover-mode", + po::value<bool>(&recover_mode)->zero_tokens(), + "Recover journal from the previous test for each test case.") + + ("repeat", + po::value<bool>(&repeat_flag)->zero_tokens(), + "Repeat all test cases indefinitely.") + + ("reuse-instance", + po::value<bool>(&reuse_instance)->zero_tokens(), + "Reuse journal instance for all test cases.") + + ("seed", + po::value<unsigned>(&seed)->default_value(0), + "Seed for use in random number generator.") + + ("analyzer", + po::value<std::string>(&jfile_analyzer)->default_value("./file_chk.py"), + "Journal file analyzer program to use when the --format-chk option is used, ignored otherwise.") + + ; +} + +bool +args::parse(int argc, char** argv) // return true if error, false if ok +{ + try + { + po::store(po::parse_command_line(argc, argv, _options_descr), _vmap); + po::notify(_vmap); + } + catch (const std::exception& e) + { + std::cout << "ERROR: " << e.what() << std::endl; + return usage(); + } + if (_vmap.count("help")) + return usage(); + if (num_jrnls == 0) + { + std::cout << "ERROR: num-jrnls must be 1 or more." << std::endl; + return usage(); + } + if (read_prob > 100) // read_prob is unsigned, so no need to check < 0 + { + std::cout << "ERROR: read-prob must be between 0 and 100 inclusive." << std::endl; + return usage(); + } + if (repeat_flag && keep_jrnls) + { + std::string resp; + std::cout << "WARNING: repeat and keep-jrnls: Monitor disk usage as test journals will" + " accumulate." << std::endl; + std::cout << "Continue? <y/n> "; + std::cin >> resp; + if (resp.size() == 1) + { + if (resp[0] != 'y' && resp[0] != 'Y') + return true; + } + else if (resp.size() == 3) // any combo of lower- and upper-case + { + if (resp[0] != 'y' && resp[0] != 'Y') + return true; + if (resp[1] != 'e' && resp[1] != 'E') + return true; + if (resp[2] != 's' && resp[2] != 'S') + return true; + } + else + return true; + } + return false; +} + +bool +args::usage() const +{ + std::cout << _options_descr << std::endl; + return true; +} + +void +args::print_args() const +{ + std::cout << "Number of journals: " << num_jrnls << std::endl; + std::cout << "Read mode: " << read_mode << std::endl; + if (read_mode.val() == read_arg::RANDOM) + std::cout << "Read probability: " << read_prob << " %" << std::endl; + if (read_mode.val() == read_arg::LAZYLOAD) + { + std::cout << "Lazy-load skips: " << lld_skip_num << std::endl; + std::cout << "Lazy-load reads: " << lld_rd_num << std::endl; + } + if (pause_secs) + std::cout << "Pause between test cases: " << pause_secs << " sec." << std::endl; + if (seed) + std::cout << "Randomize seed: " << seed << std::endl; + print_flags(); +} + +void +args::print_flags() const +{ + if (format_chk || keep_jrnls || randomize || recover_mode || repeat_flag || + reuse_instance) + { + std::cout << "Flag options:"; + // TODO: Get flag args and their strings directly from _options_descr. + if (format_chk) + std::cout << " format-chk"; + if (keep_jrnls) + std::cout << " keep-jrnls"; + if (randomize) + std::cout << " randomize"; + if (recover_mode) + std::cout << " recover-mode"; + if (repeat_flag) + std::cout << " repeat-flag"; + if (reuse_instance) + std::cout << " reuse-instance"; + std::cout << std::endl; + } + std::cout << std::endl; +} + +} // namespace jtt +} // namespace mrg diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/args.h b/qpid/cpp/src/tests/legacystore/jrnl/jtt/args.h new file mode 100644 index 0000000000..b6f7fb4a79 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/args.h @@ -0,0 +1,66 @@ +/* + * + * 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. + * + */ + +#ifndef mrg_jtt_args_hpp +#define mrg_jtt_args_hpp + +#include <boost/program_options.hpp> +#include "read_arg.h" + +namespace mrg +{ +namespace jtt +{ + + struct args + { + boost::program_options::options_description _options_descr; + boost::program_options::variables_map _vmap; + + // Add args here + std::string jfile_analyzer; + std::string test_case_csv_file_name; + std::string journal_dir; + bool format_chk; + bool keep_jrnls; + unsigned lld_rd_num; + unsigned lld_skip_num; + unsigned num_jrnls; + unsigned pause_secs; + bool randomize; + read_arg read_mode; + unsigned read_prob; + bool recover_mode; + bool repeat_flag; + bool reuse_instance; + unsigned seed; + + args(std::string opt_title); + bool parse(int argc, char** argv); // return true if error, false if ok + bool usage() const; // return true + void print_args() const; + void print_flags() const; + }; + +} // namespace jtt +} // namespace mrg + +#endif // ifndef mrg_jtt_args_hpp diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/data_src.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/data_src.cpp new file mode 100644 index 0000000000..3530e0b223 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/data_src.cpp @@ -0,0 +1,87 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "data_src.h" + +#include <cstddef> +#include <iomanip> +#include <sstream> + +namespace mrg +{ +namespace jtt +{ + +char data_src::_data_src[data_src::max_dsize]; +char data_src::_xid_src[data_src::max_xsize]; +bool data_src::_initialized = data_src::__init(); +u_int64_t data_src::_xid_cnt = 0ULL; +mrg::journal::smutex data_src::_sm; + +data_src::data_src() +{} + +bool +data_src::__init() +{ + for (unsigned i=0; i<max_dsize; i++) + _data_src[i] = '0' + ((i + 1) % 10); // 123456789012345... + for (unsigned j=0; j<max_xsize; j++) + _xid_src[j] = 'a' + (j % 26); // abc...xyzabc... + return true; +} + +const char* +data_src::get_data(const std::size_t offs) +{ + if (offs >= max_dsize) return 0; + return _data_src + offs; +} + +std::string +data_src::get_xid(const std::size_t xid_size) +{ + if (xid_size == 0) + return ""; + std::ostringstream oss; + oss << std::setfill('0'); + if (xid_size < 9) + oss << std::setw(xid_size) << get_xid_cnt(); + else if (xid_size < 13) + oss << "xid:" << std::setw(xid_size - 4) << get_xid_cnt(); + else + { + oss << "xid:" << std::setw(8) << get_xid_cnt() << ":"; + oss.write(get_xid_content(13), xid_size - 13); + } + return oss.str(); +} + +const char* +data_src::get_xid_content(const std::size_t offs) +{ + if (offs >= max_xsize) return 0; + return _xid_src + offs; +} + +} // namespace jtt +} // namespace mrg + diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/data_src.h b/qpid/cpp/src/tests/legacystore/jrnl/jtt/data_src.h new file mode 100644 index 0000000000..66dc613787 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/data_src.h @@ -0,0 +1,66 @@ +/* + * + * 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. + * + */ + +#ifndef mrg_jtt_data_src_hpp +#define mrg_jtt_data_src_hpp + +#include <cstddef> +#include "qpid/legacystore/jrnl/slock.h" +#include "qpid/legacystore/jrnl/smutex.h" +#include <pthread.h> +#include <string> +#include <sys/types.h> + +#define DATA_SIZE 1024 * 1024 +#define XID_SIZE 1024 * 1024 + +namespace mrg +{ +namespace jtt +{ + class data_src + { + public: + static const std::size_t max_dsize = DATA_SIZE; + static const std::size_t max_xsize = XID_SIZE; + + private: + static char _data_src[]; + static char _xid_src[]; + static u_int64_t _xid_cnt; + static bool _initialized; + static mrg::journal::smutex _sm; + + public: + static const char* get_data(const std::size_t offs); + static std::string get_xid(const std::size_t xid_size); + + private: + data_src(); + static u_int64_t get_xid_cnt() { mrg::journal::slock s(_sm); return _xid_cnt++; } + static const char* get_xid_content(const std::size_t offs); + static bool __init(); + }; + +} // namespace jtt +} // namespace mrg + +#endif // ifndef mrg_jtt_data_src_hpp diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/jfile_chk.py b/qpid/cpp/src/tests/legacystore/jrnl/jtt/jfile_chk.py new file mode 100755 index 0000000000..36ef511f5c --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/jfile_chk.py @@ -0,0 +1,838 @@ +#!/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. +# + +import sys +import getopt +import string +import xml.parsers.expat +from struct import unpack, calcsize +from time import gmtime, strftime + +dblk_size = 128 +sblk_size = 4 * dblk_size +jfsize = None +hdr_ver = 1 + +TEST_NUM_COL = 0 +NUM_MSGS_COL = 5 +MIN_MSG_SIZE_COL = 7 +MAX_MSG_SIZE_COL = 8 +MIN_XID_SIZE_COL = 9 +MAX_XID_SIZE_COL = 10 +AUTO_DEQ_COL = 11 +TRANSIENT_COL = 12 +EXTERN_COL = 13 +COMMENT_COL = 20 + +owi_mask = 0x01 +transient_mask = 0x10 +extern_mask = 0x20 + +printchars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ ' + + + +#== global functions =========================================================== + +def load(f, klass): + args = load_args(f, klass) + subclass = klass.discriminate(args) + result = subclass(*args) + if subclass != klass: + result.init(f, *load_args(f, subclass)) + result.skip(f) + return result; + +def load_args(f, klass): + size = calcsize(klass.format) + foffs = f.tell(), + bin = f.read(size) + if len(bin) != size: + raise Exception("end of file") + return foffs + unpack(klass.format, bin) + +def size_blks(size, blk_size): + return (size + blk_size - 1)/blk_size + +def rem_in_blk(f, blk_size): + foffs = f.tell() + return (size_blks(f.tell(), blk_size) * blk_size) - foffs; + +def file_full(f): + return f.tell() >= jfsize + +def isprintable(s): + return s.strip(printchars) == '' + +def print_xid(xidsize, xid): + if xid == None: + if xidsize > 0: + raise Exception('Inconsistent XID size: xidsize=%d, xid=None' % xidsize) + return '' + if isprintable(xid): + xidstr = split_str(xid) + else: + xidstr = hex_split_str(xid) + if xidsize != len(xid): + raise Exception('Inconsistent XID size: xidsize=%d, xid(%d)=\"%s\"' % (xidsize, len(xid), xidstr)) + return 'xid(%d)=\"%s\" ' % (xidsize, xidstr) + +def print_data(dsize, data): + if data == None: + return '' + if isprintable(data): + datastr = split_str(data) + else: + datastr = hex_split_str(data) + if dsize != len(data): + raise Exception('Inconsistent data size: dsize=%d, data(%d)=\"%s\"' % (dsize, len(data), datastr)) + return 'data(%d)=\"%s\" ' % (dsize, datastr) + +def hex_split_str(s, split_size = 50): + if len(s) <= split_size: + return hex_str(s, 0, len(s)) + if len(s) > split_size + 25: + return hex_str(s, 0, 10) + ' ... ' + hex_str(s, 55, 65) + ' ... ' + hex_str(s, len(s)-10, len(s)) + return hex_str(s, 0, 10) + ' ... ' + hex_str(s, len(s)-10, len(s)) + +def hex_str(s, b, e): + o = '' + for i in range(b, e): + if isprintable(s[i]): + o += s[i] + else: + o += '\\%02x' % ord(s[i]) + return o + +def split_str(s, split_size = 50): + if len(s) < split_size: + return s + return s[:25] + ' ... ' + s[-25:] + +def inv_str(s): + si = '' + for i in range(0,len(s)): + si += chr(~ord(s[i]) & 0xff) + return si + +def load_file_data(f, size, data): + if size == 0: + return (data, True) + if data == None: + loaded = 0 + else: + loaded = len(data) + foverflow = f.tell() + size - loaded > jfsize + if foverflow: + rsize = jfsize - f.tell() + else: + rsize = size - loaded + bin = f.read(rsize) + if data == None: + data = unpack('%ds' % (rsize), bin)[0] + else: + data = data + unpack('%ds' % (rsize), bin)[0] + return (data, not foverflow) + +def exit(code, qflag): + if code != 0 or not qflag: + print out.getvalue() + out.close() + sys.exit(code) + +#== class Sizeable ============================================================= + +class Sizeable: + + def size(self): + classes = [self.__class__] + + size = 0 + while classes: + cls = classes.pop() + if hasattr(cls, "format"): + size += calcsize(cls.format) + classes.extend(cls.__bases__) + + return size + + +#== class Hdr ================================================================== + +class Hdr(Sizeable): + + format = '=4sBBHQ' + + def discriminate(args): + return CLASSES.get(args[1][-1], Hdr) + discriminate = staticmethod(discriminate) + + def __init__(self, foffs, magic, ver, end, flags, rid): + self.foffs = foffs + self.magic = magic + self.ver = ver + self.end = end + self.flags = flags + self.rid = rid + if self.magic[-1] not in ['0x00', 'a', 'c', 'd', 'e', 'f', 'x']: + error = 3 + + def __str__(self): + if self.empty(): + return '0x%08x: <empty>' % (self.foffs) + if self.magic[-1] == 'x': + return '0x%08x: [\"%s\"]' % (self.foffs, self.magic) + if self.magic[-1] in ['a', 'c', 'd', 'e', 'f', 'x']: + return '0x%08x: [\"%s\" v=%d e=%d f=0x%04x rid=0x%x]' % (self.foffs, self.magic, self.ver, self.end, self.flags, self.rid) + return '0x%08x: <error, unknown magic \"%s\" (possible overwrite boundary?)>' % (self.foffs, self.magic) + + def empty(self): + return self.magic == '\x00'*4 + + def owi(self): + return self.flags & owi_mask != 0 + + def skip(self, f): + f.read(rem_in_blk(f, dblk_size)) + + def check(self): + if self.empty() or self.magic[:3] != 'RHM' or self.magic[3] not in ['a', 'c', 'd', 'e', 'f', 'x']: + return True + if self.ver != hdr_ver and self.magic[-1] != 'x': + raise Exception('%s: Invalid header version: found %d, expected %d.' % (self, self.ver, hdr_ver)) + return False + + +#== class FileHdr ============================================================== + +class FileHdr(Hdr): + + format = '=2H4x3Q' + + def init(self, f, foffs, fid, lid, fro, time_sec, time_ns): + self.fid = fid + self.lid = lid + self.fro = fro + self.time_sec = time_sec + self.time_ns = time_ns + + def __str__(self): + return '%s fid=%d lid=%d fro=0x%08x t=%s' % (Hdr.__str__(self), self.fid, self.lid, self.fro, self.timestamp_str()) + + def skip(self, f): + f.read(rem_in_blk(f, sblk_size)) + + def timestamp(self): + return (self.time_sec, self.time_ns) + + def timestamp_str(self): + ts = gmtime(self.time_sec) + fstr = '%%a %%b %%d %%H:%%M:%%S.%09d %%Y' % (self.time_ns) + return strftime(fstr, ts) + + +#== class DeqHdr =============================================================== + +class DeqHdr(Hdr): + + format = '=QQ' + + def init(self, f, foffs, deq_rid, xidsize): + self.deq_rid = deq_rid + self.xidsize = xidsize + self.xid = None + self.deq_tail = None + self.xid_complete = False + self.tail_complete = False + self.tail_bin = None + self.tail_offs = 0 + self.load(f) + + def load(self, f): + if self.xidsize == 0: + self.xid_complete = True + self.tail_complete = True + else: + if not self.xid_complete: + ret = load_file_data(f, self.xidsize, self.xid) + self.xid = ret[0] + self.xid_complete = ret[1] + if self.xid_complete and not self.tail_complete: + ret = load_file_data(f, calcsize(RecTail.format), self.tail_bin) + self.tail_bin = ret[0] + if ret[1]: + self.enq_tail = RecTail(self.tail_offs, *unpack(RecTail.format, self.tail_bin)) + if self.enq_tail.magic_inv != inv_str(self.magic) or self.enq_tail.rid != self.rid: + print " > %s" % self + raise Exception('Invalid dequeue record tail (magic=%s; rid=%d) at 0x%08x' % (self.enq_tail, self.enq_tail.rid, self.enq_tail.foffs)) + self.enq_tail.skip(f) + self.tail_complete = ret[1] + return self.complete() + + def complete(self): + return self.xid_complete and self.tail_complete + + def __str__(self): + return '%s %sdrid=0x%x' % (Hdr.__str__(self), print_xid(self.xidsize, self.xid), self.deq_rid) + + +#== class TxnHdr =============================================================== + +class TxnHdr(Hdr): + + format = '=Q' + + def init(self, f, foffs, xidsize): + self.xidsize = xidsize + self.xid = None + self.tx_tail = None + self.xid_complete = False + self.tail_complete = False + self.tail_bin = None + self.tail_offs = 0 + self.load(f) + + def load(self, f): + if not self.xid_complete: + ret = load_file_data(f, self.xidsize, self.xid) + self.xid = ret[0] + self.xid_complete = ret[1] + if self.xid_complete and not self.tail_complete: + ret = load_file_data(f, calcsize(RecTail.format), self.tail_bin) + self.tail_bin = ret[0] + if ret[1]: + self.enq_tail = RecTail(self.tail_offs, *unpack(RecTail.format, self.tail_bin)) + if self.enq_tail.magic_inv != inv_str(self.magic) or self.enq_tail.rid != self.rid: + print " > %s" % self + raise Exception('Invalid transaction record tail (magic=%s; rid=%d) at 0x%08x' % (self.enq_tail, self.enq_tail.rid, self.enq_tail.foffs)) + self.enq_tail.skip(f) + self.tail_complete = ret[1] + return self.complete() + + def complete(self): + return self.xid_complete and self.tail_complete + + def __str__(self): + return '%s %s' % (Hdr.__str__(self), print_xid(self.xidsize, self.xid)) + + +#== class RecTail ============================================================== + +class RecTail(Sizeable): + + format = '=4sQ' + + def __init__(self, foffs, magic_inv, rid): + self.foffs = foffs + self.magic_inv = magic_inv + self.rid = rid + + def __str__(self): + magic = inv_str(self.magic_inv) + return '[\"%s\" rid=0x%x]' % (magic, self.rid) + + def skip(self, f): + f.read(rem_in_blk(f, dblk_size)) + + +#== class EnqRec =============================================================== + +class EnqRec(Hdr): + + format = '=QQ' + + def init(self, f, foffs, xidsize, dsize): + self.xidsize = xidsize + self.dsize = dsize + self.transient = self.flags & transient_mask > 0 + self.extern = self.flags & extern_mask > 0 + self.xid = None + self.data = None + self.enq_tail = None + self.xid_complete = False + self.data_complete = False + self.tail_complete = False + self.tail_bin = None + self.tail_offs = 0 + self.load(f) + + def load(self, f): + if not self.xid_complete: + ret = load_file_data(f, self.xidsize, self.xid) + self.xid = ret[0] + self.xid_complete = ret[1] + if self.xid_complete and not self.data_complete: + if self.extern: + self.data_complete = True + else: + ret = load_file_data(f, self.dsize, self.data) + self.data = ret[0] + self.data_complete = ret[1] + if self.data_complete and not self.tail_complete: + ret = load_file_data(f, calcsize(RecTail.format), self.tail_bin) + self.tail_bin = ret[0] + if ret[1]: + self.enq_tail = RecTail(self.tail_offs, *unpack(RecTail.format, self.tail_bin)) + if self.enq_tail.magic_inv != inv_str(self.magic) or self.enq_tail.rid != self.rid: + print " > %s" % self + raise Exception('Invalid enqueue record tail (magic=%s; rid=%d) at 0x%08x' % (self.enq_tail, self.enq_tail.rid, self.enq_tail.foffs)) + self.enq_tail.skip(f) + self.tail_complete = ret[1] + return self.complete() + + def complete(self): + return self.xid_complete and self.data_complete and self.tail_complete + + def print_flags(self): + s = '' + if self.transient: + s = '*TRANSIENT' + if self.extern: + if len(s) > 0: + s += ',EXTERNAL' + else: + s = '*EXTERNAL' + if len(s) > 0: + s += '*' + return s + + def __str__(self): + return '%s %s%s %s %s' % (Hdr.__str__(self), print_xid(self.xidsize, self.xid), print_data(self.dsize, self.data), self.enq_tail, self.print_flags()) + + +#== class Main ================================================================= + +class Main: + def __init__(self, argv): + self.bfn = None + self.csvfn = None + self.jdir = None + self.aflag = False + self.hflag = False + self.qflag = False + self.tnum = None + self.num_jfiles = None + self.num_msgs = None + self.msg_len = None + self.auto_deq = None + self.xid_len = None + self.transient = None + self.extern = None + + self.file_start = 0 + self.file_num = 0 + self.fro = 0x200 + self.emap = {} + self.tmap = {} + self.rec_cnt = 0 + self.msg_cnt = 0 + self.txn_msg_cnt = 0 + self.fhdr = None + self.f = None + self.first_rec = False + self.last_file = False + self.last_rid = -1 + self.fhdr_owi_at_msg_start = None + + self.proc_args(argv) + self.proc_csv() + self.read_jinf() + + def run(self): + try: + start_info = self.analyze_files() + stop = self.advance_file(*start_info) + except Exception: + print 'WARNING: All journal files are empty.' + if self.num_msgs > 0: + raise Exception('All journal files are empty, but %d msgs expectd.' % self.num_msgs) + else: + stop = True + while not stop: + warn = '' + if file_full(self.f): + stop = self.advance_file() + if stop: + break + hdr = load(self.f, Hdr) + if hdr.empty(): + stop = True; + break + if hdr.check(): + stop = True; + else: + self.rec_cnt += 1 + self.fhdr_owi_at_msg_start = self.fhdr.owi() + if self.first_rec: + if self.fhdr.fro != hdr.foffs: + raise Exception('File header first record offset mismatch: fro=0x%08x; rec_offs=0x%08x' % (self.fhdr.fro, hdr.foffs)) + else: + if not self.qflag: print ' * fro ok: 0x%08x' % self.fhdr.fro + self.first_rec = False + if isinstance(hdr, EnqRec) and not stop: + while not hdr.complete(): + stop = self.advance_file() + if stop: + break + hdr.load(self.f) + if self.extern != None: + if hdr.extern: + if hdr.data != None: + raise Exception('Message data found on external record') + else: + if self.msg_len > 0 and len(hdr.data) != self.msg_len: + raise Exception('Message length (%d) incorrect; expected %d' % (len(hdr.data), self.msg_len)) + else: + if self.msg_len > 0 and len(hdr.data) != self.msg_len: + raise Exception('Message length (%d) incorrect; expected %d' % (len(hdr.data), self.msg_len)) + if self.xid_len > 0 and len(hdr.xid) != self.xid_len: + print ' ERROR: XID length (%d) incorrect; expected %d' % (len(hdr.xid), self.xid_len) + sys.exit(1) + #raise Exception('XID length (%d) incorrect; expected %d' % (len(hdr.xid), self.xid_len)) + if self.transient != None: + if self.transient: + if not hdr.transient: + raise Exception('Expected transient record, found persistent') + else: + if hdr.transient: + raise Exception('Expected persistent record, found transient') + stop = not self.check_owi(hdr) + if stop: + warn = ' (WARNING: OWI mismatch - could be overwrite boundary.)' + else: + self.msg_cnt += 1 + if self.aflag or self.auto_deq: + if hdr.xid == None: + self.emap[hdr.rid] = (self.fhdr.fid, hdr, False) + else: + self.txn_msg_cnt += 1 + if hdr.xid in self.tmap: + self.tmap[hdr.xid].append((self.fhdr.fid, hdr)) #Append tuple to existing list + else: + self.tmap[hdr.xid] = [(self.fhdr.fid, hdr)] # Create new list + elif isinstance(hdr, DeqHdr) and not stop: + while not hdr.complete(): + stop = self.advance_file() + if stop: + break + hdr.load(self.f) + stop = not self.check_owi(hdr) + if stop: + warn = ' (WARNING: OWI mismatch - could be overwrite boundary.)' + else: + if self.auto_deq != None: + if not self.auto_deq: + warn = ' WARNING: Dequeue record rid=%d found in non-dequeue test - ignoring.' % hdr.rid + if self.aflag or self.auto_deq: + if hdr.xid == None: + if hdr.deq_rid in self.emap: + if self.emap[hdr.deq_rid][2]: + warn = ' (WARNING: dequeue rid 0x%x dequeues locked enqueue record 0x%x)' % (hdr.rid, hdr.deq_rid) + del self.emap[hdr.deq_rid] + else: + warn = ' (WARNING: rid being dequeued 0x%x not found in enqueued records)' % hdr.deq_rid + else: + if hdr.deq_rid in self.emap: + t = self.emap[hdr.deq_rid] + self.emap[hdr.deq_rid] = (t[0], t[1], True) # Lock enq record + if hdr.xid in self.tmap: + self.tmap[hdr.xid].append((self.fhdr.fid, hdr)) #Append to existing list + else: + self.tmap[hdr.xid] = [(self.fhdr.fid, hdr)] # Create new list + elif isinstance(hdr, TxnHdr) and not stop: + while not hdr.complete(): + stop = self.advance_file() + if stop: + break + hdr.load(self.f) + stop = not self.check_owi(hdr) + if stop: + warn = ' (WARNING: OWI mismatch - could be overwrite boundary.)' + else: + if hdr.xid in self.tmap: + mismatched_rids = [] + if hdr.magic[-1] == 'c': # commit + for rec in self.tmap[hdr.xid]: + if isinstance(rec[1], EnqRec): + self.emap[rec[1].rid] = (rec[0], rec[1], False) # Transfer enq to emap + elif isinstance(rec[1], DeqHdr): + if rec[1].deq_rid in self.emap: + del self.emap[rec[1].deq_rid] # Delete from emap + else: + mismatched_rids.append('0x%x' % rec[1].deq_rid) + else: + raise Exception('Unknown header found in txn map: %s' % rec[1]) + elif hdr.magic[-1] == 'a': # abort + for rec in self.tmap[hdr.xid]: + if isinstance(rec[1], DeqHdr): + if rec[1].deq_rid in self.emap: + t = self.emap[rec[1].deq_rid] + self.emap[rec[1].deq_rid] = (t[0], t[1], False) # Unlock enq record + del self.tmap[hdr.xid] + if len(mismatched_rids) > 0: + warn = ' (WARNING: transactional dequeues not found in enqueue map; rids=%s)' % mismatched_rids + else: + warn = ' (WARNING: %s not found in transaction map)' % print_xid(len(hdr.xid), hdr.xid) + if not self.qflag: print ' > %s%s' % (hdr, warn) + if not stop: + stop = (self.last_file and hdr.check()) or hdr.empty() or self.fhdr.empty() + + def analyze_files(self): + fname = '' + fnum = -1 + rid = -1 + fro = -1 + tss = '' + if not self.qflag: print 'Analyzing journal files:' + owi_found = False + for i in range(0, self.num_jfiles): + jfn = self.jdir + '/' + self.bfn + '.%04d.jdat' % i + f = open(jfn) + fhdr = load(f, Hdr) + if fhdr.empty(): + if not self.qflag: + print ' %s: file empty' % jfn + break + if i == 0: + init_owi = fhdr.owi() + fname = jfn + fnum = i + rid = fhdr.rid + fro = fhdr.fro + tss = fhdr.timestamp_str() + elif fhdr.owi() != init_owi and not owi_found: + fname = jfn + fnum = i + rid = fhdr.rid + fro = fhdr.fro + tss = fhdr.timestamp_str() + owi_found = True + if not self.qflag: + print ' %s: owi=%s rid=0x%x, fro=0x%08x ts=%s' % (jfn, fhdr.owi(), fhdr.rid, fhdr.fro, fhdr.timestamp_str()) + if fnum < 0 or rid < 0 or fro < 0: + raise Exception('All journal files empty') + if not self.qflag: print ' Oldest complete file: %s: rid=%d, fro=0x%08x ts=%s' % (fname, rid, fro, tss) + return (fnum, rid, fro) + + def advance_file(self, *start_info): + seek_flag = False + if len(start_info) == 3: + self.file_start = self.file_num = start_info[0] + self.fro = start_info[2] + seek_flag = True + if self.f != None and file_full(self.f): + self.file_num = self.incr_fnum() + if self.file_num == self.file_start: + return True + if self.file_start == 0: + self.last_file = self.file_num == self.num_jfiles - 1 + else: + self.last_file = self.file_num == self.file_start - 1 + if self.file_num < 0 or self.file_num >= self.num_jfiles: + raise Exception('Bad file number %d' % self.file_num) + jfn = self.jdir + '/' + self.bfn + '.%04d.jdat' % self.file_num + self.f = open(jfn) + self.fhdr = load(self.f, Hdr) + if seek_flag and self.f.tell() != self.fro: + self.f.seek(self.fro) + self.first_rec = True + if not self.qflag: print jfn, ": ", self.fhdr + return False + + def incr_fnum(self): + self.file_num += 1 + if self.file_num >= self.num_jfiles: + self.file_num = 0; + return self.file_num + + def check_owi(self, hdr): + return self.fhdr_owi_at_msg_start == hdr.owi() + + def check_rid(self, hdr): + if self.last_rid != -1 and hdr.rid <= self.last_rid: + return False + self.last_rid = hdr.rid + return True + + def read_jinf(self): + filename = self.jdir + '/' + self.bfn + '.jinf' + try: + f = open(filename, 'r') + except IOError: + print 'ERROR: Unable to open jinf file %s' % filename + sys.exit(1) + p = xml.parsers.expat.ParserCreate() + p.StartElementHandler = self.handleStartElement + p.CharacterDataHandler = self.handleCharData + p.EndElementHandler = self.handleEndElement + p.ParseFile(f) + if self.num_jfiles == None: + print 'ERROR: number_jrnl_files not found in jinf file "%s"!' % filename + if jfsize == None: + print 'ERROR: jrnl_file_size_sblks not found in jinf file "%s"!' % filename + if self.num_jfiles == None or jfsize == None: + sys.exit(1) + + def handleStartElement(self, name, attrs): + global jfsize + if name == 'number_jrnl_files': + self.num_jfiles = int(attrs['value']) + if name == 'jrnl_file_size_sblks': + jfsize = (int(attrs['value']) + 1) * sblk_size + + def handleCharData(self, data): pass + + def handleEndElement(self, name): pass + + def proc_csv(self): + if self.csvfn != None and self.tnum != None: + tparams = self.get_test(self.csvfn, self.tnum) + if tparams == None: + print 'ERROR: Test %d not found in CSV file "%s"' % (self.tnum, self.csvfn) + sys.exit(1) + self.num_msgs = tparams['num_msgs'] + if tparams['min_size'] == tparams['max_size']: + self.msg_len = tparams['max_size'] + else: + self.msg_len = 0 + self.auto_deq = tparams['auto_deq'] + if tparams['xid_min_size'] == tparams['xid_max_size']: + self.xid_len = tparams['xid_max_size'] + else: + self.xid_len = 0 + self.transient = tparams['transient'] + self.extern = tparams['extern'] + + def get_test(self, filename, tnum): + try: + f=open(filename, 'r') + except IOError: + print 'ERROR: Unable to open CSV file "%s"' % filename + sys.exit(1) + for l in f: + sl = l.strip().split(',') + if len(sl[0]) > 0 and sl[0][0] != '"': + try: + if (int(sl[TEST_NUM_COL]) == tnum): + return { 'num_msgs':int(sl[NUM_MSGS_COL]), + 'min_size':int(sl[MIN_MSG_SIZE_COL]), + 'max_size':int(sl[MAX_MSG_SIZE_COL]), + 'auto_deq':not (sl[AUTO_DEQ_COL] == 'FALSE' or sl[AUTO_DEQ_COL] == '0'), + 'xid_min_size':int(sl[MIN_XID_SIZE_COL]), + 'xid_max_size':int(sl[MAX_XID_SIZE_COL]), + 'transient':not (sl[TRANSIENT_COL] == 'FALSE' or sl[TRANSIENT_COL] == '0'), + 'extern':not (sl[EXTERN_COL] == 'FALSE' or sl[EXTERN_COL] == '0'), + 'comment':sl[COMMENT_COL] } + except Exception: + pass + return None + + def proc_args(self, argv): + try: + opts, args = getopt.getopt(sys.argv[1:], "ab:c:d:hqt:", ["analyse", "base-filename=", "csv-filename=", "dir=", "help", "quiet", "test-num="]) + except getopt.GetoptError: + self.usage() + sys.exit(2) + for o, a in opts: + if o in ("-h", "--help"): + self.usage() + sys.exit() + if o in ("-a", "--analyze"): + self.aflag = True + if o in ("-b", "--base-filename"): + self.bfn = a + if o in ("-c", "--csv-filename"): + self.csvfn = a + if o in ("-d", "--dir"): + self.jdir = a + if o in ("-q", "--quiet"): + self.qflag = True + if o in ("-t", "--test-num"): + if not a.isdigit(): + print 'ERROR: Illegal test-num argument. Must be a non-negative number' + sys.exit(2) + self.tnum = int(a) + if self.bfn == None or self.jdir == None: + print 'ERROR: Missing requred args.' + self.usage() + sys.exit(2) + if self.tnum != None and self.csvfn == None: + print 'ERROR: Test number specified, but not CSV file' + self.usage() + sys.exit(2) + + def usage(self): + print 'Usage: %s opts' % sys.argv[0] + print ' where opts are in either short or long format (*=req\'d):' + print ' -a --analyze Analyze enqueue/dequeue records' + print ' -b --base-filename [string] * Base filename for journal files' + print ' -c --csv-filename [string] CSV filename containing test parameters' + print ' -d --dir [string] * Journal directory containing journal files' + print ' -h --help Print help' + print ' -q --quiet Quiet (reduced output)' + print ' -t --test-num [int] Test number from CSV file - only valid if CSV file named' + + def report(self): + if not self.qflag: + print + print ' === REPORT ====' + if self.num_msgs > 0 and self.msg_cnt != self.num_msgs: + print 'WARNING: Found %d messages; %d expected.' % (self.msg_cnt, self.num_msgs) + if len(self.emap) > 0: + print + print 'Remaining enqueued records (sorted by rid): ' + keys = sorted(self.emap.keys()) + for k in keys: + if self.emap[k][2] == True: # locked + locked = ' (locked)' + else: + locked = '' + print " fid=%d %s%s" % (self.emap[k][0], self.emap[k][1], locked) + print 'WARNING: Enqueue-Dequeue mismatch, %d enqueued records remain.' % len(self.emap) + if len(self.tmap) > 0: + txn_rec_cnt = 0 + print + print 'Remaining transactions: ' + for t in self.tmap: + print_xid(len(t), t) + for r in self.tmap[t]: + print " fid=%d %s" % (r[0], r[1]) + print " Total: %d records for xid %s" % (len(self.tmap[t]), t) + txn_rec_cnt += len(self.tmap[t]) + print 'WARNING: Incomplete transactions, %d xids remain containing %d records.' % (len(self.tmap), txn_rec_cnt) + print '%d enqueues, %d journal records processed.' % (self.msg_cnt, self.rec_cnt) + + +#=============================================================================== + +CLASSES = { + "a": TxnHdr, + "c": TxnHdr, + "d": DeqHdr, + "e": EnqRec, + "f": FileHdr +} + +m = Main(sys.argv) +m.run() +m.report() + +sys.exit(None) diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/jrnl_init_params.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/jrnl_init_params.cpp new file mode 100644 index 0000000000..1bc04110af --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/jrnl_init_params.cpp @@ -0,0 +1,77 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "jrnl_init_params.h" + +namespace mrg +{ +namespace jtt +{ + +jrnl_init_params::jrnl_init_params(const std::string& jid, const std::string& jdir, const std::string& base_filename, + const u_int16_t num_jfiles, const bool ae, const u_int16_t ae_max_jfiles, const u_int32_t jfsize_sblks, + const u_int16_t wcache_num_pages, const u_int32_t wcache_pgsize_sblks): + _jid(jid), + _jdir(jdir), + _base_filename(base_filename), + _num_jfiles(num_jfiles), + _ae(ae), + _ae_max_jfiles(ae_max_jfiles), + _jfsize_sblks(jfsize_sblks), + _wcache_num_pages(wcache_num_pages), + _wcache_pgsize_sblks(wcache_pgsize_sblks) +{} + +jrnl_init_params::jrnl_init_params(const jrnl_init_params& jp): + _jid(jp._jid), + _jdir(jp._jdir), + _base_filename(jp._base_filename), + _num_jfiles(jp._num_jfiles), + _ae(jp._ae), + _ae_max_jfiles(jp._ae_max_jfiles), + _jfsize_sblks(jp._jfsize_sblks), + _wcache_num_pages(jp._wcache_num_pages), + _wcache_pgsize_sblks(jp._wcache_pgsize_sblks) +{} + +jrnl_init_params::jrnl_init_params(const jrnl_init_params* const jp_ptr): + _jid(jp_ptr->_jid), + _jdir(jp_ptr->_jdir), + _base_filename(jp_ptr->_base_filename), + _num_jfiles(jp_ptr->_num_jfiles), + _ae(jp_ptr->_ae), + _ae_max_jfiles(jp_ptr->_ae_max_jfiles), + _jfsize_sblks(jp_ptr->_jfsize_sblks), + _wcache_num_pages(jp_ptr->_wcache_num_pages), + _wcache_pgsize_sblks(jp_ptr->_wcache_pgsize_sblks) +{} + +// static initializers + +const u_int16_t jrnl_init_params::def_num_jfiles = 8; +const bool jrnl_init_params::def_ae = false; +const u_int16_t jrnl_init_params::def_ae_max_jfiles = 0; +const u_int32_t jrnl_init_params::def_jfsize_sblks = 0xc00; +const u_int16_t jrnl_init_params::def_wcache_num_pages = 32; +const u_int32_t jrnl_init_params::def_wcache_pgsize_sblks = 64; + +} // namespace jtt +} // namespace mrg diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/jrnl_init_params.h b/qpid/cpp/src/tests/legacystore/jrnl/jtt/jrnl_init_params.h new file mode 100644 index 0000000000..ece87f8e03 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/jrnl_init_params.h @@ -0,0 +1,80 @@ +/* + * + * 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. + * + */ + +#ifndef mrg_jtt_jrnl_init_params_hpp +#define mrg_jtt_jrnl_init_params_hpp + +#include <boost/shared_ptr.hpp> +#include <string> +#include <sys/types.h> + +namespace mrg +{ +namespace jtt +{ + + class jrnl_init_params + { + public: + static const u_int16_t def_num_jfiles; + static const bool def_ae; + static const u_int16_t def_ae_max_jfiles; + static const u_int32_t def_jfsize_sblks; + static const u_int16_t def_wcache_num_pages; + static const u_int32_t def_wcache_pgsize_sblks; + + typedef boost::shared_ptr<jrnl_init_params> shared_ptr; + + private: + std::string _jid; + std::string _jdir; + std::string _base_filename; + u_int16_t _num_jfiles; + bool _ae; + u_int16_t _ae_max_jfiles; + u_int32_t _jfsize_sblks; + u_int16_t _wcache_num_pages; + u_int32_t _wcache_pgsize_sblks; + + public: + jrnl_init_params(const std::string& jid, const std::string& jdir, const std::string& base_filename, + const u_int16_t num_jfiles = def_num_jfiles, const bool ae = def_ae, + const u_int16_t ae_max_jfiles = def_ae_max_jfiles, const u_int32_t jfsize_sblks = def_jfsize_sblks, + const u_int16_t wcache_num_pages = def_wcache_num_pages, + const u_int32_t wcache_pgsize_sblks = def_wcache_pgsize_sblks); + jrnl_init_params(const jrnl_init_params& jp); + jrnl_init_params(const jrnl_init_params* const jp_ptr); + + inline const std::string& jid() const { return _jid; } + inline const std::string& jdir() const { return _jdir; } + inline const std::string& base_filename() const { return _base_filename; } + inline u_int16_t num_jfiles() const { return _num_jfiles; } + inline bool is_ae() const { return _ae; } + inline u_int16_t ae_max_jfiles() const { return _ae_max_jfiles; } + inline u_int32_t jfsize_sblks() const { return _jfsize_sblks; } + inline u_int16_t wcache_num_pages() const { return _wcache_num_pages; } + inline u_int32_t wcache_pgsize_sblks() const { return _wcache_pgsize_sblks; } + }; + +} // namespace jtt +} // namespace mrg + +#endif // ifndef mrg_jtt_jrnl_init_params_hpp diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/jrnl_instance.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/jrnl_instance.cpp new file mode 100644 index 0000000000..339dc1b52c --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/jrnl_instance.cpp @@ -0,0 +1,439 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "jrnl_instance.h" + +#include <cstdlib> +#include "data_src.h" +#include "qpid/legacystore/jrnl/data_tok.h" +#include "qpid/legacystore/jrnl/jerrno.h" +#include "test_case_result.h" + +#define MAX_WR_WAIT 10 // in ms +#define MAX_RD_WAIT 100 // in ms +#define MAX_ENQCAPTHRESH_CNT 1000 // 10s if MAX_WR_WAIT is 10 ms + +namespace mrg +{ +namespace jtt +{ + +jrnl_instance::jrnl_instance(const std::string& jid, const std::string& jdir, const std::string& base_filename, + const u_int16_t num_jfiles, const bool ae, const u_int16_t ae_max_jfiles, const u_int32_t jfsize_sblks, + const u_int16_t wcache_num_pages, const u_int32_t wcache_pgsize_sblks): + mrg::journal::jcntl(jid, jdir, base_filename), + _jpp(new jrnl_init_params(jid, jdir, base_filename, num_jfiles, ae, ae_max_jfiles, jfsize_sblks, + wcache_num_pages, wcache_pgsize_sblks)), + _args_ptr(0), + _dtok_master_enq_list(), + _dtok_master_txn_list(), + _dtok_rd_list(), + _dtok_deq_list(), + _rd_aio_cv(_rd_aio_mutex), + _wr_full_cv(_wr_full_mutex), + _rd_list_cv(_rd_list_mutex), + _deq_list_cv(_deq_list_mutex), + _tcp(), + _tcrp() +{} + +jrnl_instance::jrnl_instance(const jrnl_init_params::shared_ptr& p): + mrg::journal::jcntl(p->jid(), p->jdir(), p->base_filename()), + _jpp(p), + _args_ptr(0), + _dtok_master_enq_list(), + _dtok_master_txn_list(), + _dtok_rd_list(), + _dtok_deq_list(), + _rd_aio_cv(_rd_aio_mutex), + _wr_full_cv(_wr_full_mutex), + _rd_list_cv(_rd_list_mutex), + _deq_list_cv(_deq_list_mutex), + _tcp(), + _tcrp() +{} + +jrnl_instance::~jrnl_instance() {} + + +void +jrnl_instance::init_tc(test_case::shared_ptr& tcp, const args* const args_ptr) throw () +{ + test_case_result::shared_ptr p(new test_case_result(_jpp->jid())); + _tcrp = p; + _args_ptr = args_ptr; + try + { + _tcp = tcp; + _dtok_master_enq_list.clear(); + _dtok_master_txn_list.clear(); + _dtok_rd_list.clear(); + _dtok_deq_list.clear(); + + if (_args_ptr->recover_mode) + { + try + { + u_int64_t highest_rid; + recover(_jpp->num_jfiles(), _jpp->is_ae(), _jpp->ae_max_jfiles(), _jpp->jfsize_sblks(), + _jpp->wcache_num_pages(), _jpp->wcache_pgsize_sblks(), this, + 0, highest_rid); + recover_complete(); + } + catch (const mrg::journal::jexception& e) + { + if (e.err_code() == mrg::journal::jerrno::JERR_JDIR_STAT) + initialize(_jpp->num_jfiles(), _jpp->is_ae(), _jpp->ae_max_jfiles(), _jpp->jfsize_sblks(), + _jpp->wcache_num_pages(), _jpp->wcache_pgsize_sblks(), this); + else + throw; + } + } + else + initialize(_jpp->num_jfiles(), _jpp->is_ae(), _jpp->ae_max_jfiles(), _jpp->jfsize_sblks(), + _jpp->wcache_num_pages(), _jpp->wcache_pgsize_sblks(), this); + } + catch (const mrg::journal::jexception& e) { _tcrp->add_exception(e); } + catch (const std::exception& e) { _tcrp->add_exception(e.what()); } + catch (...) { _tcrp->add_exception("Unknown exception"); } +} + +void +jrnl_instance::run_tc() throw () +{ + _tcrp->set_start_time(); + ::pthread_create(&_enq_thread, 0, run_enq, this); + ::pthread_create(&_read_thread, 0, run_read, this); + ::pthread_create(&_deq_thread, 0, run_deq, this); +} + +void +jrnl_instance::tc_wait_compl() throw () +{ + try + { + ::pthread_join(_deq_thread, 0); + ::pthread_join(_read_thread, 0); + ::pthread_join(_enq_thread, 0); + stop(true); + } + catch (const mrg::journal::jexception& e) { _tcrp->add_exception(e); panic(); } + catch (const std::exception& e) { _tcrp->add_exception(e.what()); panic(); } + catch (...) { _tcrp->add_exception("Unknown exception"); panic(); } + _lpmgr.finalize(); + _tcrp->set_stop_time(); + _tcp->add_result(_tcrp); +} + +void +jrnl_instance::run_enq() throw () +{ + try + { + unsigned sleep_cnt = 0U; + while(_tcrp->num_enq() < _tcp->num_msgs() && !_tcrp->exception()) + { + dtok_ptr p(new mrg::journal::data_tok); + _dtok_master_enq_list.push_back(p); + const char* msgp = data_src::get_data(_tcrp->num_enq() % 10); + const std::size_t msg_size = _tcp->this_data_size(); + const std::size_t xid_size = _tcp->this_xid_size(); + const std::string xid(data_src::get_xid(xid_size)); + const bool external = _tcp->this_external(); + const bool transient = _tcp->this_transience(); + mrg::journal::iores res; + if (xid_size) + { + if (external) + res = enqueue_extern_txn_data_record(msg_size, p.get(), xid, transient); + else + res = enqueue_txn_data_record(msgp, msg_size, msg_size, p.get(), xid, + transient); + } + else + { + if (external) + res = enqueue_extern_data_record(msg_size, p.get(), transient); + else + res = enqueue_data_record(msgp, msg_size, msg_size, p.get(), transient); + } + switch (res) + { + case mrg::journal::RHM_IORES_SUCCESS: + sleep_cnt = 0U; + _tcrp->incr_num_enq(); + if (p->has_xid() && !_tcp->auto_deq()) + commit(p.get()); + break; + case mrg::journal::RHM_IORES_ENQCAPTHRESH: + if (++sleep_cnt > MAX_ENQCAPTHRESH_CNT) + { + _tcrp->add_exception("Timeout waiting for RHM_IORES_ENQCAPTHRESH to clear."); + panic(); + } + else if (get_wr_events(0) == 0) // *** GEV2 + { + mrg::journal::slock sl(_wr_full_mutex); + _wr_full_cv.waitintvl(MAX_WR_WAIT * 1000000); // MAX_WR_WAIT in ms + } + break; + default: + std::ostringstream oss; + oss << "ERROR: enqueue operation in journal \"" << _jid << "\" returned "; + oss << mrg::journal::iores_str(res) << "."; + _tcrp->add_exception(oss.str()); + } + } + flush(true); + } + catch (const mrg::journal::jexception& e) { _tcrp->add_exception(e); panic(); } + catch (const std::exception& e) { _tcrp->add_exception(e.what()); panic(); } + catch (...) { _tcrp->add_exception("Unknown exception"); panic(); } +} + +void +jrnl_instance::run_read() throw () +{ + try + { + read_arg::read_mode_t rd_mode = _args_ptr->read_mode.val(); + if (rd_mode != read_arg::NONE) + { + while (_tcrp->num_rproc() < _tcp->num_msgs() && !_tcrp->exception()) + { + journal::data_tok* dtokp = 0; + { + mrg::journal::slock sl(_rd_list_mutex); + if (_dtok_rd_list.empty()) + _rd_list_cv.wait(); + if (!_dtok_rd_list.empty()) + { + dtokp = _dtok_rd_list.front(); + _dtok_rd_list.pop_front(); + } + } + if (dtokp) + { + _tcrp->incr_num_rproc(); + + bool do_read = true; + if (rd_mode == read_arg::RANDOM) + do_read = 1.0 * std::rand() / RAND_MAX < _args_ptr->read_prob / 100.0; + else if (rd_mode == read_arg::LAZYLOAD) + do_read = _tcrp->num_rproc() >= _args_ptr->lld_skip_num && + _tcrp->num_read() < _args_ptr->lld_rd_num; + bool read_compl = false; + while (do_read && !read_compl && !_tcrp->exception()) + { + void* dptr = 0; + std::size_t dsize = 0; + void* xptr = 0; + std::size_t xsize = 0; + bool tr = false; + bool ext = false; + mrg::journal::iores res = read_data_record(&dptr, dsize, &xptr, xsize, tr, + ext, dtokp); + switch (res) + { + case mrg::journal::RHM_IORES_SUCCESS: + { + mrg::journal::slock sl(_deq_list_mutex); + _dtok_deq_list.push_back(dtokp); + _deq_list_cv.broadcast(); + } + read_compl = true; + _tcrp->incr_num_read(); + + // clean up + if (xsize) + std::free(xptr); + else if (dsize) + std::free(dptr); + dptr = 0; + xptr = 0; + break; + case mrg::journal::RHM_IORES_PAGE_AIOWAIT: + if (get_rd_events(0) == 0) + { + mrg::journal::slock sl(_rd_aio_mutex); + _rd_aio_cv.waitintvl(MAX_RD_WAIT * 1000000); // MAX_RD_WAIT in ms + } + break; + default: + std::ostringstream oss; + oss << "ERROR: read operation in journal \"" << _jid; + oss << "\" returned " << mrg::journal::iores_str(res) << "."; + _tcrp->add_exception(oss.str()); + { + mrg::journal::slock sl(_deq_list_mutex); + _deq_list_cv.broadcast(); // wake up deq thread + } + } + } + } + } + } + } + catch (const mrg::journal::jexception& e) { _tcrp->add_exception(e); panic(); } + catch (const std::exception& e) { _tcrp->add_exception(e.what()); panic(); } + catch (...) { _tcrp->add_exception("Unknown exception"); panic(); } +} + +void +jrnl_instance::run_deq() throw () +{ + try + { + if (_tcp->auto_deq()) + { + while(_tcrp->num_deq() < _tcp->num_msgs() && !_tcrp->exception()) + { + journal::data_tok* dtokp = 0; + { + mrg::journal::slock sl(_deq_list_mutex); + if (_dtok_deq_list.empty()) + _deq_list_cv.wait(); + if (!_dtok_deq_list.empty()) + { + dtokp = _dtok_deq_list.front(); + _dtok_deq_list.pop_front(); + } + } + if (dtokp) + { + mrg::journal::iores res; + if (dtokp->has_xid()) + res = dequeue_txn_data_record(dtokp, dtokp->xid()); + else + res = dequeue_data_record(dtokp); + if (res == mrg::journal::RHM_IORES_SUCCESS) + { + _tcrp->incr_num_deq(); + commit(dtokp); + } + else + { + std::ostringstream oss; + oss << "ERROR: dequeue operation in journal \"" << _jid; + oss << "\" returned " << mrg::journal::iores_str(res) << "."; + _tcrp->add_exception(oss.str()); + } + } + } + flush(true); + } + } + catch (const mrg::journal::jexception& e) { _tcrp->add_exception(e); panic(); } + catch (const std::exception& e) { _tcrp->add_exception(e.what()); panic(); } + catch (...) { _tcrp->add_exception("Unknown exception"); panic(); } +} + +void +jrnl_instance::abort(const mrg::journal::data_tok* dtokp) +{ + txn(dtokp, false); +} + +void +jrnl_instance::commit(const mrg::journal::data_tok* dtokp) +{ + txn(dtokp, true); +} + +void +jrnl_instance::txn(const mrg::journal::data_tok* dtokp, const bool commit) +{ + if (dtokp->has_xid()) + { + mrg::journal::data_tok* p = prep_txn_dtok(dtokp); + mrg::journal::iores res = commit ? txn_commit(p, p->xid()) : txn_abort(p, p->xid()); + if (res != mrg::journal::RHM_IORES_SUCCESS) + { + std::ostringstream oss; + oss << "ERROR: " << (commit ? "commit" : "abort") << " operation in journal \""; + oss << _jid << "\" returned " << mrg::journal::iores_str(res) << "."; + _tcrp->add_exception(oss.str()); + } + } +} + +mrg::journal::data_tok* +jrnl_instance::prep_txn_dtok(const mrg::journal::data_tok* dtokp) +{ + dtok_ptr p(new mrg::journal::data_tok); + _dtok_master_txn_list.push_back(p); + p->set_xid(dtokp->xid()); + return p.get(); +} + +void +jrnl_instance::panic() +{ + // In the event of a panic or exception condition, release all waiting CVs + _rd_aio_cv.broadcast(); + _wr_full_cv.broadcast(); + _rd_list_cv.broadcast(); + _deq_list_cv.broadcast(); +} + +// AIO callbacks + +void +jrnl_instance::wr_aio_cb(std::vector<journal::data_tok*>& dtokl) +{ + for (std::vector<journal::data_tok*>::const_iterator i=dtokl.begin(); i!=dtokl.end(); i++) + { + if ((*i)->wstate() == journal::data_tok::ENQ || (*i)->wstate() == journal::data_tok::DEQ) + { + journal::data_tok* dtokp = *i; + if (dtokp->wstate() == journal::data_tok::ENQ) + { + if (_args_ptr->read_mode.val() == read_arg::NONE) + { + mrg::journal::slock sl(_deq_list_mutex); + _dtok_deq_list.push_back(dtokp); + _deq_list_cv.broadcast(); + } + else + { + mrg::journal::slock sl(_rd_list_mutex); + _dtok_rd_list.push_back(dtokp); + _rd_list_cv.broadcast(); + } + } + else // DEQ + { + mrg::journal::slock sl(_wr_full_mutex); + _wr_full_cv.broadcast(); + } + } + } +} + +void +jrnl_instance::rd_aio_cb(std::vector<u_int16_t>& /*pil*/) +{ + mrg::journal::slock sl(_rd_aio_mutex); + _rd_aio_cv.broadcast(); +} + +} // namespace jtt +} // namespace mrg diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/jrnl_instance.h b/qpid/cpp/src/tests/legacystore/jrnl/jtt/jrnl_instance.h new file mode 100644 index 0000000000..5003f39b24 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/jrnl_instance.h @@ -0,0 +1,121 @@ +/* + * + * 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. + * + */ + +#ifndef mrg_jtt_jrnl_instance_hpp +#define mrg_jtt_jrnl_instance_hpp + +#include "args.h" +#include "jrnl_init_params.h" +#include "test_case.h" + +#include <boost/shared_ptr.hpp> +#include "qpid/legacystore/jrnl/cvar.h" +#include "qpid/legacystore/jrnl/data_tok.h" +#include "qpid/legacystore/jrnl/jcntl.h" +#include "qpid/legacystore/jrnl/slock.h" +#include "qpid/legacystore/jrnl/smutex.h" +#include <list> +#include <vector> + +namespace mrg +{ +namespace jtt +{ + + class jrnl_instance : public mrg::journal::jcntl, public virtual mrg::journal::aio_callback + { + public: + typedef boost::shared_ptr<jrnl_instance> shared_ptr; + typedef boost::shared_ptr<journal::data_tok> dtok_ptr; + + private: + jrnl_init_params::shared_ptr _jpp; + const args* _args_ptr; + std::vector<dtok_ptr> _dtok_master_enq_list; + std::vector<dtok_ptr> _dtok_master_txn_list; + std::list<journal::data_tok*> _dtok_rd_list; + std::list<journal::data_tok*> _dtok_deq_list; + mrg::journal::smutex _rd_aio_mutex; ///< Mutex for read aio wait conditions + mrg::journal::cvar _rd_aio_cv; ///< Condition var for read aio wait conditions + mrg::journal::smutex _wr_full_mutex; ///< Mutex for write full conditions + mrg::journal::cvar _wr_full_cv; ///< Condition var for write full conditions + mrg::journal::smutex _rd_list_mutex; ///< Mutex for _dtok_rd_list + mrg::journal::cvar _rd_list_cv; ///< Condition var for _dtok_rd_list + mrg::journal::smutex _deq_list_mutex; ///< Mutex for _dtok_deq_list + mrg::journal::cvar _deq_list_cv; ///< Condition var for _dtok_deq_list + pthread_t _enq_thread; + pthread_t _deq_thread; + pthread_t _read_thread; + test_case::shared_ptr _tcp; + test_case_result::shared_ptr _tcrp; + + public: + jrnl_instance(const std::string& jid, const std::string& jdir, + const std::string& base_filename, + const u_int16_t num_jfiles = jrnl_init_params::def_num_jfiles, + const bool ae = jrnl_init_params::def_ae, + const u_int16_t ae_max_jfiles = jrnl_init_params::def_ae_max_jfiles, + const u_int32_t jfsize_sblks = jrnl_init_params::def_jfsize_sblks, + const u_int16_t wcache_num_pages = jrnl_init_params::def_wcache_num_pages, + const u_int32_t wcache_pgsize_sblks = jrnl_init_params::def_wcache_pgsize_sblks); + jrnl_instance(const jrnl_init_params::shared_ptr& params); + virtual ~jrnl_instance(); + + inline const jrnl_init_params::shared_ptr& params() const { return _jpp; } + inline const std::string& jid() const { return _jpp->jid(); } + + void init_tc(test_case::shared_ptr& tcp, const args* const args_ptr) throw (); + void run_tc() throw (); + void tc_wait_compl() throw (); + + // AIO callbacks + virtual void wr_aio_cb(std::vector<journal::data_tok*>& dtokl); + virtual void rd_aio_cb(std::vector<u_int16_t>& pil); + + private: + void run_enq() throw (); + inline static void* run_enq(void* p) + { static_cast<jrnl_instance*>(p)->run_enq(); return 0; } + + void run_read() throw (); + inline static void* run_read(void* p) + { static_cast<jrnl_instance*>(p)->run_read(); return 0; } + + void run_deq() throw (); + inline static void* run_deq(void* p) + { static_cast<jrnl_instance*>(p)->run_deq(); return 0; } + + void abort(const mrg::journal::data_tok* dtokp); + void commit(const mrg::journal::data_tok* dtokp); + void txn(const mrg::journal::data_tok* dtokp, const bool commit); + mrg::journal::data_tok* prep_txn_dtok(const mrg::journal::data_tok* dtokp); + + void panic(); + +// // static callbacks +// static void aio_rd_callback(jcntl* journal, std::vector<u_int16_t>& pil); +// static void aio_wr_callback(jcntl* journal, std::vector<journal::data_tok*>& dtokl); + }; + +} // namespace jtt +} // namespace mrg + +#endif // ifndef mrg_jtt_jrnl_instance_hpp diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/jtt.csv b/qpid/cpp/src/tests/legacystore/jrnl/jtt/jtt.csv new file mode 100644 index 0000000000..df523e3f97 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/jtt.csv @@ -0,0 +1,234 @@ +# +# 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. +# + +,,,,,,,"Msg size",,"Xid size",,,,,"enq-size",,"deq-size",,"txn-size",, +"Test #","tf","pf","amn","mn incr","#msgs","ms incr","Min","Max","Min","Max","auto-deq","transient","extern","bytes","dblks","bytes","dblks","bytes","dblks","comment" +,,,,,,,,,,,,,,,,,,,, +"Initialize only",,,,,,,,,,,,,,,,,,,, +0,"L",0,0,0,0,0,0,0,0,0,FALSE,FALSE,FALSE,44,1,0,0,0,0,"No messages - journal creation/initialization only" +,,,,,,,,,,,,,,,,,,,, +"Simple message combinations of persistent/deq transientueued/non-dequeued, transactional/non-transactional",,,,,,,,,,,,,,,,,,,, +1,"L",1,1,0,1,0,10,10,0,0,FALSE,FALSE,FALSE,54,1,0,0,0,0,"1 * 10-byte message" +2,"L",1,10,0,10,0,10,10,0,0,FALSE,FALSE,FALSE,54,1,0,0,0,0,"10 * 10-byte message" +3,"L",1,1,0,1,0,10,10,0,0,FALSE,TRUE,FALSE,54,1,0,0,0,0,"1 * 10-byte message [transient]" +4,"L",1,10,0,10,0,10,10,0,0,FALSE,TRUE,FALSE,54,1,0,0,0,0,"10 * 10-byte message [transient]" +5,"L",1,1,0,1,0,10,10,10,10,FALSE,FALSE,FALSE,64,1,0,0,0,0,"1 * 10-byte message [txn]" +6,"L",1,10,0,10,0,10,10,10,10,FALSE,FALSE,FALSE,64,1,0,0,0,0,"10 * 10-byte message [txn]" +7,"L",1,1,0,1,0,10,10,10,10,FALSE,TRUE,FALSE,64,1,0,0,0,0,"1 * 10-byte message [txn transient]" +8,"L",1,10,0,10,0,10,10,10,10,FALSE,TRUE,FALSE,64,1,0,0,0,0,"10 * 10-byte message [txn transient]" +9,"L",1,1,0,1,0,10,10,0,0,TRUE,FALSE,FALSE,54,1,32,1,0,0,"1 * 10-byte message [deq]" +10,"L",1,10,0,10,0,10,10,0,0,TRUE,FALSE,FALSE,54,1,32,1,0,0,"10 * 10-byte message [deq]" +11,"L",1,1,0,1,0,10,10,0,0,TRUE,TRUE,FALSE,54,1,32,1,0,0,"1 * 10-byte message [deq transient]" +12,"L",1,10,0,10,0,10,10,0,0,TRUE,TRUE,FALSE,54,1,32,1,0,0,"10 * 10-byte message [deq transient]" +13,"L",1,1,0,1,0,10,10,10,10,TRUE,FALSE,FALSE,64,1,54,1,46,1,"1 * 10-byte message [deq txn]" +14,"L",1,10,0,10,0,10,10,10,10,TRUE,FALSE,FALSE,64,1,54,1,46,1,"10 * 10-byte message [deq txn]" +15,"L",1,1,0,1,0,10,10,10,10,TRUE,TRUE,FALSE,64,1,54,1,46,1,"1 * 10-byte message [txn deq transient]" +16,"L",1,10,0,10,0,10,10,10,10,TRUE,TRUE,FALSE,64,1,54,1,46,1,"10 * 10-byte message [txn deq transient]" +17,"L",1,1,0,1,0,10,10,0,0,FALSE,FALSE,TRUE,54,1,0,0,0,0,"1 * 10-byte message [extern]" +18,"L",1,10,0,10,0,10,10,0,0,FALSE,FALSE,TRUE,54,1,0,0,0,0,"10 * 10-byte message [extern]" +19,"L",1,1,0,1,0,10,10,0,0,FALSE,TRUE,TRUE,54,1,0,0,0,0,"1 * 10-byte message [transient extern]" +20,"L",1,10,0,10,0,10,10,0,0,FALSE,TRUE,TRUE,54,1,0,0,0,0,"10 * 10-byte message [transient extern]" +21,"L",1,1,0,1,0,10,10,10,10,FALSE,FALSE,TRUE,64,1,0,0,0,0,"1 * 10-byte message [txn extern]" +22,"L",1,10,0,10,0,10,10,10,10,FALSE,FALSE,TRUE,64,1,0,0,0,0,"10 * 10-byte message [txn extern]" +23,"L",1,1,0,1,0,10,10,10,10,FALSE,TRUE,TRUE,64,1,0,0,0,0,"1 * 10-byte message [txn transient extern]" +24,"L",1,10,0,10,0,10,10,10,10,FALSE,TRUE,TRUE,64,1,0,0,0,0,"10 * 10-byte message [txn transient extern]" +25,"L",1,1,0,1,0,10,10,0,0,TRUE,FALSE,TRUE,54,1,32,1,0,0,"1 * 10-byte message [deq extern]" +26,"L",1,10,0,10,0,10,10,0,0,TRUE,FALSE,TRUE,54,1,32,1,0,0,"10 * 10-byte message [deq extern]" +27,"L",1,1,0,1,0,10,10,0,0,TRUE,TRUE,TRUE,54,1,32,1,0,0,"1 * 10-byte message [deq transient extern]" +28,"L",1,10,0,10,0,10,10,0,0,TRUE,TRUE,TRUE,54,1,32,1,0,0,"10 * 10-byte message [deq transient extern]" +29,"L",1,1,0,1,0,10,10,10,10,TRUE,FALSE,TRUE,64,1,54,1,46,1,"1 * 10-byte message [deq txn extern]" +30,"L",1,10,0,10,0,10,10,10,10,TRUE,FALSE,TRUE,64,1,54,1,46,1,"10 * 10-byte message [deq txn extern]" +31,"L",1,1,0,1,0,10,10,10,10,TRUE,TRUE,TRUE,64,1,54,1,46,1,"1 * 10-byte message [txn deq transient extern]" +32,"L",1,10,0,10,0,10,10,10,10,TRUE,TRUE,TRUE,64,1,54,1,46,1,"10 * 10-byte message [txn deq transient extern]" +,,,,,,,,,,,,,,,,,,,, +"Transition from one d-block to two per message",,,,,,,,,,,,,,,,,,,, +33,"L",1,10,0,10,0,84,84,0,0,FALSE,FALSE,FALSE,128,1,0,0,0,0,"1 dblk exact fit" +34,"L",1,10,0,10,1,85,85,0,0,FALSE,FALSE,FALSE,129,2,0,0,0,0,"1 dblk + 1 byte" +35,"L",1,10,0,10,0,58,58,26,26,FALSE,FALSE,FALSE,128,1,0,0,0,0,"1 dblk exact fit [txn]" +36,"L",1,10,0,10,1,59,59,26,26,FALSE,FALSE,FALSE,129,2,0,0,0,0,"1 dblk + 1 byte [txn]" +,,,,,,,,,,,,,,,,,,,, +"Transition from one s-block to two per message",,,,,,,,,,,,,,,,,,,, +37,"L",1,10,0,10,0,468,468,0,0,FALSE,FALSE,FALSE,512,4,0,0,0,0,"1 sblk exact fit" +38,"L",1,10,0,10,1,469,469,0,0,FALSE,FALSE,FALSE,513,5,0,0,0,0,"1 sblk + 1 byte" +39,"L",1,10,0,10,0,442,442,26,26,FALSE,FALSE,FALSE,512,4,0,0,0,0,"1 sblk exact fit [txn]" +40,"L",1,10,0,10,1,443,443,26,26,FALSE,FALSE,FALSE,513,5,0,0,0,0,"1 sblk + 1 byte [txn]" +,,,,,,,,,,,,,,,,,,,, +"Transition from first page to second",,,,,,,,,,,,,,,,,,,, +41,"L",1,8,0,8,0,4052,4052,0,0,FALSE,FALSE,FALSE,4096,32,0,0,0,0,"1/8 page" +42,"L",1,8,1,9,0,4052,4052,0,0,FALSE,FALSE,FALSE,4096,32,0,0,0,0,"1/8 page" +43,"L",1,8,0,8,1,4053,4053,0,0,FALSE,FALSE,FALSE,4097,33,0,0,0,0,"1/8 page + 1 byte" +44,"L",1,8,0,8,0,3796,3796,256,256,FALSE,FALSE,FALSE,4096,32,0,0,0,0,"1/8 page [txn]" +45,"L",1,8,1,9,0,3796,3796,256,256,FALSE,FALSE,FALSE,4096,32,0,0,0,0,"1/8 page [txn]" +46,"L",1,8,0,8,1,3797,3797,256,256,FALSE,FALSE,FALSE,4097,33,0,0,0,0,"1/8 page + 1 byte [txn]" +47,"L",1,8,0,8,0,3924,3924,0,0,TRUE,FALSE,FALSE,3968,31,32,1,0,0,"1/8 page incl deq [deq]" +48,"L",1,8,1,9,0,3924,3924,0,0,TRUE,FALSE,FALSE,3968,31,32,1,0,0,"1/8 page incl deq [deq]" +49,"L",1,8,0,8,1,3925,3925,0,0,TRUE,FALSE,FALSE,3969,32,32,1,0,0,"1/8 page incl deq + 1 byte [deq]" +50,"L",1,8,0,8,0,3028,3028,256,256,TRUE,FALSE,FALSE,3328,26,300,3,292,3,"1/8 page incl deq & txn [deq txn]" +51,"L",1,8,1,9,0,3028,3028,256,256,TRUE,FALSE,FALSE,3328,26,300,3,292,3,"1/8 page incl deq & txn [deq txn]" +52,"L",1,8,0,8,1,3029,3029,256,256,TRUE,FALSE,FALSE,3329,27,300,3,292,3,"1/8 page incl deq & txn + 1 byte [deq txn]" +,,,,,,,,,,,,,,,,,,,, +"Page cache rollover (from page 32 back to page 0)",,,,,,,,,,,,,,,,,,,, +53,"L",1,32,0,32,0,32724,32724,0,0,FALSE,FALSE,FALSE,32768,256,0,0,0,0,"1 page" +54,"L",1,32,1,33,0,32724,32724,0,0,FALSE,FALSE,FALSE,32768,256,0,0,0,0,"1 page" +55,"L",1,32,0,32,1,32725,32725,0,0,FALSE,FALSE,FALSE,32769,257,0,0,0,0,"1 page + 1 byte" +56,"L",1.5,22,0,22,0,49108,49108,0,0,FALSE,FALSE,FALSE,49152,384,0,0,0,0,"1.5 pages" +57,"L",1,32,0,32,0,32468,32468,256,256,FALSE,FALSE,FALSE,32768,256,0,0,0,0,"1 page [txn]" +58,"L",1,32,1,33,0,32468,32468,256,256,FALSE,FALSE,FALSE,32768,256,0,0,0,0,"1 page [txn]" +59,"L",1,32,0,32,1,32469,32469,256,256,FALSE,FALSE,FALSE,32769,257,0,0,0,0,"1 page + 1 byte [txn]" +60,"L",1.5,22,0,22,0,48852,48852,256,256,FALSE,FALSE,FALSE,49152,384,0,0,0,0,"1.5 pages [txn]" +61,"L",1,32,0,32,0,32596,32596,0,0,TRUE,FALSE,FALSE,32640,255,32,1,0,0,"1 page incl deq [deq]" +62,"L",1,32,1,33,0,32596,32596,0,0,TRUE,FALSE,FALSE,32640,255,32,1,0,0,"1 page incl deq [deq]" +63,"L",1,32,0,32,1,32597,32597,0,0,TRUE,FALSE,FALSE,32641,256,32,1,0,0,"1 page incl deq + 1 byte [deq]" +64,"L",1.5,22,0,22,0,48980,48980,0,0,TRUE,FALSE,FALSE,49024,383,32,1,0,0,"1.5 pages incl deq [deq]" +65,"L",1,32,0,32,0,31700,31700,256,256,TRUE,FALSE,FALSE,32000,250,300,3,292,3,"1 page incl deq & txn [deq txn]" +66,"L",1,32,1,33,0,31700,31700,256,256,TRUE,FALSE,FALSE,32000,250,300,3,292,3,"1 page incl deq & txn [deq txn]" +67,"L",1,32,0,32,1,31701,31701,256,256,TRUE,FALSE,FALSE,32001,251,300,3,292,3,"1 page incl deq & txn + 1 byte [deq txn]" +68,"L",1.5,22,0,22,0,48084,48084,256,256,TRUE,FALSE,FALSE,48384,378,300,3,292,3,"1.5 pages incl deq & txn [deq txn]" +,,,,,,,,,,,,,,,,,,,, +"File transition (from file 0000 to 0001)",,,,,,,,,,,,,,,,,,,, +69,"L",1,48,0,48,0,32724,32724,0,0,FALSE,FALSE,FALSE,32768,256,0,0,0,0,"1 page" +70,"L",1,48,1,49,0,32724,32724,0,0,FALSE,FALSE,FALSE,32768,256,0,0,0,0,"1 page" +71,"L",1,48,0,48,1,32725,32725,0,0,FALSE,FALSE,FALSE,32769,257,0,0,0,0,"1 page + 1 byte" +72,"L",2.5,20,0,20,0,81876,81876,0,0,FALSE,FALSE,FALSE,81920,640,0,0,0,0,"2.5 pages" +73,"L",1,48,0,48,0,32468,32468,256,256,FALSE,FALSE,FALSE,32768,256,0,0,0,0,"1 page [txn]" +74,"L",1,48,1,49,0,32468,32468,256,256,FALSE,FALSE,FALSE,32768,256,0,0,0,0,"1 page [txn]" +75,"L",1,48,0,48,1,32469,32469,256,256,FALSE,FALSE,FALSE,32769,257,0,0,0,0,"1 page + 1 byte [txn]" +76,"L",2.5,20,0,20,0,81620,81620,256,256,FALSE,FALSE,FALSE,81920,640,0,0,0,0,"2.5 pages [txn]" +77,"L",1,48,0,48,0,32596,32596,0,0,TRUE,FALSE,FALSE,32640,255,32,1,0,0,"1 page incl deq [deq]" +78,"L",1,48,1,49,0,32596,32596,0,0,TRUE,FALSE,FALSE,32640,255,32,1,0,0,"1 page incl deq [deq]" +79,"L",1,48,0,48,1,32597,32597,0,0,TRUE,FALSE,FALSE,32641,256,32,1,0,0,"1 page incl deq + 1 byte [deq]" +80,"L",2.5,20,0,20,0,81748,81748,0,0,TRUE,FALSE,FALSE,81792,639,32,1,0,0,"2.5 pages incl deq [deq]" +81,"L",1,48,0,48,0,31700,31700,256,256,TRUE,FALSE,FALSE,32000,250,300,3,292,3,"1 page incl deq & txn [deq txn]" +82,"L",1,48,1,49,0,31700,31700,256,256,TRUE,FALSE,FALSE,32000,250,300,3,292,3,"1 page incl deq & txn [deq txn]" +83,"L",1,48,0,48,1,31701,31701,256,256,TRUE,FALSE,FALSE,32001,251,300,3,292,3,"1 page incl deq & txn + 1 byte [deq txn]" +84,"L",2.5,20,0,20,0,80852,80852,256,256,TRUE,FALSE,FALSE,81152,634,300,3,292,3,"2.5 pages incl deq & txn [deq txn]" +,,,,,,,,,,,,,,,,,,,, +"File rollover (from file 0007 to 0000) - RHM_WRONLY req'd for auto-dequeue == FALSE",,,,,,,,,,,,,,,,,,,, +85,"L",0.5,16,0,16,0,786260,786260,0,0,TRUE,FALSE,FALSE,786304,6143,32,1,0,0,"24 pages incl deq = ½ file [deq]" +86,"L",0.5,16,1,17,0,786260,786260,0,0,TRUE,FALSE,FALSE,786304,6143,32,1,0,0,"24 pages incl deq = ½ file [deq]" +87,"L",0.5,16,0,16,1,786261,786261,0,0,TRUE,FALSE,FALSE,786305,6144,32,1,0,0,"24 pages incl deq + 1 byte [deq]" +88,"L",0.5,16,0,16,0,785364,785364,256,256,TRUE,FALSE,FALSE,785664,6138,300,3,292,3,"24 pages incl deq & txn = ½ file [deq txn]" +89,"L",0.5,16,1,17,0,785364,785364,256,256,TRUE,FALSE,FALSE,785664,6138,300,3,292,3,"24 pages incl deq & txn = ½ file [deq txn]" +90,"L",0.5,16,0,16,1,785365,785365,256,256,TRUE,FALSE,FALSE,785665,6139,300,3,292,3,"24 pages incl deq & txn + 1 byte [deq txn]" +91,"L",0.25,32,0,32,0,786260,786260,0,0,TRUE,FALSE,FALSE,786304,6143,32,1,0,0,"24 pages incl deq = ½ file [deq]" +92,"L",0.25,32,1,33,0,786260,786260,0,0,TRUE,FALSE,FALSE,786304,6143,32,1,0,0,"24 pages incl deq = ½ file [deq]" +93,"L",0.25,32,0,32,1,786261,786261,0,0,TRUE,FALSE,FALSE,786305,6144,32,1,0,0,"24 pages incl deq + 1 byte [deq]" +94,"L",0.25,32,0,32,0,785364,785364,256,256,TRUE,FALSE,FALSE,785664,6138,300,3,292,3,"24 pages incl deq & txn = ½ file [deq txn]" +95,"L",0.25,32,1,33,0,785364,785364,256,256,TRUE,FALSE,FALSE,785664,6138,300,3,292,3,"24 pages incl deq & txn = ½ file [deq txn]" +96,"L",0.25,32,0,32,1,785365,785365,256,256,TRUE,FALSE,FALSE,785665,6139,300,3,292,3,"24 pages incl deq & txn + 1 byte [deq txn]" +,,,,,,,,,,,,,,,,,,,, +"Multi-page messages (large messages) - tests various paths in encoder.",,,,,,,,,,,,,,,,,,,, +97,"L",1,16,0,16,0,32724,32724,0,0,FALSE,FALSE,FALSE,32768,256,0,0,0,0,"data 1 page" +98,"L",1,16,0,16,1,32725,32725,0,0,FALSE,FALSE,FALSE,32769,257,0,0,0,0,"data 1 page + 1 byte (tail split; 1 byte over page boundary)" +99,"L",1,16,0,16,11,32735,32735,0,0,FALSE,FALSE,FALSE,32779,257,0,0,0,0,"data 1 page + 11 bytes (tail split; 11 bytes over page boundary)" +100,"L",1,16,0,16,12,32736,32736,0,0,FALSE,FALSE,FALSE,32780,257,0,0,0,0,"data 1 page + 12 bytes (tail separated exactly onto next page)" +101,"L",1,16,0,16,13,32737,32737,0,0,FALSE,FALSE,FALSE,32781,257,0,0,0,0,"data 1 page + 13 bytes (data split; 1 byte over page boundary)" +102,"L",1,16,0,16,0,32468,32468,256,256,FALSE,FALSE,FALSE,32768,256,0,0,0,0,"data 1 page [txn]" +103,"L",1,16,0,16,1,32469,32469,256,256,FALSE,FALSE,FALSE,32769,257,0,0,0,0,"data 1 page + 1 byte (tail split; 1 byte over page boundary) [txn]" +104,"L",1,16,0,16,11,32479,32479,256,256,FALSE,FALSE,FALSE,32779,257,0,0,0,0,"data 1 page + 11 bytes (tail split; 11 bytes over page boundary) [txn]" +105,"L",1,16,0,16,12,32480,32480,256,256,FALSE,FALSE,FALSE,32780,257,0,0,0,0,"data 1 page + 12 bytes (tail separated exactly onto next page) [txn]" +106,"L",1,16,0,16,13,32481,32481,256,256,FALSE,FALSE,FALSE,32781,257,0,0,0,0,"data 1 page + 13 bytes (data split; 1 byte over page boundary) [txn]" +107,"L",2,16,0,16,0,65492,65492,0,0,FALSE,FALSE,FALSE,65536,512,0,0,0,0,"data 2 pages" +108,"L",2,16,0,16,1,65493,65493,0,0,FALSE,FALSE,FALSE,65537,513,0,0,0,0,"data 2 pages + 1 byte (tail split; 1 byte over page boundary)" +109,"L",2,16,0,16,11,65503,65503,0,0,FALSE,FALSE,FALSE,65547,513,0,0,0,0,"data 2 pages + 11 bytes (tail split; 11 bytes over page boundary)" +110,"L",2,16,0,16,12,65504,65504,0,0,FALSE,FALSE,FALSE,65548,513,0,0,0,0,"data 2 pages + 12 bytes (tail separated exactly onto next page)" +111,"L",2,16,0,16,13,65505,65505,0,0,FALSE,FALSE,FALSE,65549,513,0,0,0,0,"data 2 pages + 13 bytes (data split; 1 byte over page boundary)" +112,"L",2,16,0,16,0,65236,65236,256,256,FALSE,FALSE,FALSE,65536,512,0,0,0,0,"data 2 pages [txn]" +113,"L",2,16,0,16,1,65237,65237,256,256,FALSE,FALSE,FALSE,65537,513,0,0,0,0,"data 2 pages + 1 byte (tail split; 1 byte over page boundary) [txn]" +114,"L",2,16,0,16,11,65247,65247,256,256,FALSE,FALSE,FALSE,65547,513,0,0,0,0,"data 2 pages + 11 bytes (tail split; 11 bytes over page boundary) [txn]" +115,"L",2,16,0,16,12,65248,65248,256,256,FALSE,FALSE,FALSE,65548,513,0,0,0,0,"data 2 pages + 12 bytes (tail separated exactly onto next page) [txn]" +116,"L",2,16,0,16,13,65249,65249,256,256,FALSE,FALSE,FALSE,65549,513,0,0,0,0,"data 2 pages + 13 bytes (data split; 1 byte over page boundary) [txn]" +117,"L",4,16,0,16,0,131028,131028,0,0,FALSE,FALSE,FALSE,131072,1024,0,0,0,0,"data 4 pages" +118,"L",4,16,0,16,1,131029,131029,0,0,FALSE,FALSE,FALSE,131073,1025,0,0,0,0,"data 4 pages + 1 byte (tail split; 1 byte over page boundary)" +119,"L",4,16,0,16,11,131039,131039,0,0,FALSE,FALSE,FALSE,131083,1025,0,0,0,0,"data 4 pages + 11 bytes (tail split; 11 bytes over page boundary)" +120,"L",4,16,0,16,12,131040,131040,0,0,FALSE,FALSE,FALSE,131084,1025,0,0,0,0,"data 4 pages + 12 bytes (tail separated exactly onto next page)" +121,"L",4,16,0,16,13,131041,131041,0,0,FALSE,FALSE,FALSE,131085,1025,0,0,0,0,"data 4 pages + 13 bytes (data split; 1 byte over page boundary)" +122,"L",4,16,0,16,0,130772,130772,256,256,FALSE,FALSE,FALSE,131072,1024,0,0,0,0,"data 4 pages [txn]" +123,"L",4,16,0,16,1,130773,130773,256,256,FALSE,FALSE,FALSE,131073,1025,0,0,0,0,"data 4 pages + 1 byte (tail split; 1 byte over page boundary) [txn]" +124,"L",4,16,0,16,11,130783,130783,256,256,FALSE,FALSE,FALSE,131083,1025,0,0,0,0,"data 4 pages + 11 bytes (tail split; 11 bytes over page boundary) [txn]" +125,"L",4,16,0,16,12,130784,130784,256,256,FALSE,FALSE,FALSE,131084,1025,0,0,0,0,"data 4 pages + 12 bytes (tail separated exactly onto next page) [txn]" +126,"L",4,16,0,16,13,130785,130785,256,256,FALSE,FALSE,FALSE,131085,1025,0,0,0,0,"data 4 pages + 13 bytes (data split; 1 byte over page boundary) [txn]" +127,"L",3.5,16,0,16,0,114644,114644,0,0,FALSE,FALSE,FALSE,114688,896,0,0,0,0,"data 3.5 pages" +128,"L",3.5,16,0,16,1,114645,114645,0,0,FALSE,FALSE,FALSE,114689,897,0,0,0,0,"data 3.5 pages + 1 byte" +129,"L",3.5,16,0,16,0,114388,114388,256,256,FALSE,FALSE,FALSE,114688,896,0,0,0,0,"data 3.5 pages [txn]" +130,"L",3.5,16,0,16,1,114389,114389,256,256,FALSE,FALSE,FALSE,114689,897,0,0,0,0,"data 3.5 pages + 1 byte [txn]" +131,"L",1,16,0,16,-1,10,10,32735,32735,FALSE,FALSE,FALSE,32789,257,0,0,0,0,"xid 1 page – 1 byte; data 10 bytes (exact fit) [txn]" +132,"L",1,16,0,16,0,10,10,32736,32736,FALSE,FALSE,FALSE,32790,257,0,0,0,0,"xid 1 page; data 10 bytes (exact fit) [txn]" +133,"L",1,16,0,16,1,10,10,32737,32737,FALSE,FALSE,FALSE,32791,257,0,0,0,0,"xid 1 page + 1 byte; data 10 bytes (exact fit) [txn]" +134,"L",2,16,0,16,-1,10,10,65503,65503,FALSE,FALSE,FALSE,65557,513,0,0,0,0,"xid 2 pages – 1 byte; data 10 bytes (exact fit) [txn]" +135,"L",2,16,0,16,0,10,10,65504,65504,FALSE,FALSE,FALSE,65558,513,0,0,0,0,"xid 2 pages; data 10 bytes (exact fit) [txn]" +136,"L",2,16,0,16,1,10,10,65505,65505,FALSE,FALSE,FALSE,65559,513,0,0,0,0,"xid 2 pages + 1 byte; data 10 bytes (exact fit) [txn]" +137,"L",4,16,0,16,-1,10,10,131039,131039,FALSE,FALSE,FALSE,131093,1025,0,0,0,0,"xid 4 pages – 1 byte; data 10 bytes (exact fit) [txn]" +138,"L",4,16,0,16,0,10,10,131040,131040,FALSE,FALSE,FALSE,131094,1025,0,0,0,0,"xid 4 pages; data 10 bytes (exact fit) [txn]" +139,"L",4,16,0,16,1,10,10,131041,131041,FALSE,FALSE,FALSE,131095,1025,0,0,0,0,"xid 4 pages + 1 byte; data 10 bytes (exact fit) [txn]" +140,"L",3.5,16,0,16,0,10,10,114656,114656,FALSE,FALSE,FALSE,114710,897,0,0,0,0,"xid 3.5 pages; data 10 bytes (exact fit) [txn]" +141,"L",3.5,16,0,16,1,10,10,114657,114657,FALSE,FALSE,FALSE,114711,897,0,0,0,0,"xid 3.5 pages + 1 byte; data 10 bytes (exact fit) [txn]" +142,"L",1,16,0,16,-1,10,10,32735,32735,TRUE,FALSE,FALSE,32789,257,32779,257,32771,257,"xid 1 page – 1 byte for enq rec; data 10 bytes (exact fit) [deq, txn]" +143,"L",1,16,0,16,0,10,10,32736,32736,TRUE,FALSE,FALSE,32790,257,32780,257,32772,257,"xid 1 page for enq rec; data 10 bytes (exact fit) [deq, txn]" +144,"L",1,16,0,16,1,10,10,32737,32737,TRUE,FALSE,FALSE,32791,257,32781,257,32773,257,"xid 1 page + 1 byte for enq rec; data 10 bytes (exact fit) [deq, txn]" +145,"L",2,16,0,16,-1,10,10,65503,65503,TRUE,FALSE,FALSE,65557,513,65547,513,65539,513,"xid 2 pages – 1 byte for enq rec; data 10 bytes (exact fit) [deq, txn]" +146,"L",2,16,0,16,0,10,10,65504,65504,TRUE,FALSE,FALSE,65558,513,65548,513,65540,513,"xid 2 pages for enq rec; data 10 bytes (exact fit) [deq, txn]" +147,"L",2,16,0,16,1,10,10,65505,65505,TRUE,FALSE,FALSE,65559,513,65549,513,65541,513,"xid 2 pages + 1 byte for enq rec; data 10 bytes (exact fit) [deq, txn]" +148,"L",4,16,0,16,-1,10,10,131039,131039,TRUE,FALSE,FALSE,131093,1025,131083,1025,131075,1025,"xid 4 pages – 1 byte for enq rec; data 10 bytes (exact fit) [deq, txn]" +149,"L",4,16,0,16,0,10,10,131040,131040,TRUE,FALSE,FALSE,131094,1025,131084,1025,131076,1025,"xid 4 pages for enq rec; data 10 bytes (exact fit) [deq, txn]" +150,"L",4,16,0,16,1,10,10,131041,131041,TRUE,FALSE,FALSE,131095,1025,131085,1025,131077,1025,"xid 4 pages + 1 byte for enq rec; data 10 bytes (exact fit) [deq, txn]" +151,"L",3.5,16,0,16,0,10,10,114656,114656,TRUE,FALSE,FALSE,114710,897,114700,897,114692,897,"xid 3.5 pages for enq rec; data 10 bytes (exact fit) [deq, txn]" +152,"L",3.5,16,0,16,1,10,10,114657,114657,TRUE,FALSE,FALSE,114711,897,114701,897,114693,897,"xid 3.5 pages + 1 byte for enq rec; data 10 bytes (exact fit) [deq, txn]" +153,"L",1,16,0,16,-1,10,10,32735,32735,TRUE,FALSE,FALSE,32789,257,32779,257,32771,257,"xid 1 page – 1 byte for deq rec; data 10 bytes (exact fit) [deq, txn]" +154,"L",1,16,0,16,0,10,10,32736,32736,TRUE,FALSE,FALSE,32790,257,32780,257,32772,257,"xid 1 page for deq rec; data 10 bytes (exact fit) [deq, txn]" +155,"L",1,16,0,16,1,10,10,32737,32737,TRUE,FALSE,FALSE,32791,257,32781,257,32773,257,"xid 1 page + 1 byte for deq rec; data 10 bytes (exact fit) [deq, txn]" +156,"L",2,16,0,16,-1,10,10,65503,65503,TRUE,FALSE,FALSE,65557,513,65547,513,65539,513,"xid 2 pages – 1 byte for deq rec; data 10 bytes (exact fit) [deq, txn]" +157,"L",2,16,0,16,0,10,10,65504,65504,TRUE,FALSE,FALSE,65558,513,65548,513,65540,513,"xid 2 pages for deq rec; data 10 bytes (exact fit) [deq, txn]" +158,"L",2,16,0,16,1,10,10,65505,65505,TRUE,FALSE,FALSE,65559,513,65549,513,65541,513,"xid 2 pages + 1 byte for deq rec; data 10 bytes (exact fit) [deq, txn]" +159,"L",4,16,0,16,-1,10,10,131039,131039,TRUE,FALSE,FALSE,131093,1025,131083,1025,131075,1025,"xid 4 pages – 1 byte for deq rec; data 10 bytes (exact fit) [deq, txn]" +160,"L",4,16,0,16,0,10,10,131040,131040,TRUE,FALSE,FALSE,131094,1025,131084,1025,131076,1025,"xid 4 pages for deq rec; data 10 bytes (exact fit) [deq, txn]" +161,"L",4,16,0,16,1,10,10,131041,131041,TRUE,FALSE,FALSE,131095,1025,131085,1025,131077,1025,"xid 4 pages + 1 byte for deq rec; data 10 bytes (exact fit) [deq, txn]" +162,"L",3.5,16,0,16,0,10,10,114656,114656,TRUE,FALSE,FALSE,114710,897,114700,897,114692,897,"xid 3.5 pages for deq rec; data 10 bytes (exact fit) [deq, txn]" +163,"L",3.5,16,0,16,1,10,10,114657,114657,TRUE,FALSE,FALSE,114711,897,114701,897,114693,897,"xid 3.5 pages + 1 byte for deq rec; data 10 bytes (exact fit) [deq, txn]" +164,"L",1,16,0,16,-1,10,10,32743,32743,TRUE,FALSE,FALSE,32797,257,32787,257,32779,257,"xid 1 page – 1 byte for txn rec; data 10 bytes (exact fit) [deq, txn]" +165,"L",1,16,0,16,0,10,10,32744,32744,TRUE,FALSE,FALSE,32798,257,32788,257,32780,257,"xid 1 page for txn rec; data 10 bytes (exact fit) [deq, txn]" +166,"L",1,16,0,16,1,10,10,32745,32745,TRUE,FALSE,FALSE,32799,257,32789,257,32781,257,"xid 1 page + 1 byte for txn rec; data 10 bytes (exact fit) [deq, txn]" +167,"L",2,16,0,16,-1,10,10,65511,65511,TRUE,FALSE,FALSE,65565,513,65555,513,65547,513,"xid 2 pages – 1 byte for txn rec; data 10 bytes (exact fit) [deq, txn]" +168,"L",2,16,0,16,0,10,10,65512,65512,TRUE,FALSE,FALSE,65566,513,65556,513,65548,513,"xid 2 pages for txn rec; data 10 bytes (exact fit) [deq, txn]" +169,"L",2,16,0,16,1,10,10,65513,65513,TRUE,FALSE,FALSE,65567,513,65557,513,65549,513,"xid 2 pages + 1 byte for txn rec; data 10 bytes (exact fit) [deq, txn]" +170,"L",4,16,0,16,-1,10,10,131047,131047,TRUE,FALSE,FALSE,131101,1025,131091,1025,131083,1025,"xid 4 pages – 1 byte for txn rec; data 10 bytes (exact fit) [deq, txn]" +171,"L",4,16,0,16,0,10,10,131048,131048,TRUE,FALSE,FALSE,131102,1025,131092,1025,131084,1025,"xid 4 pages for txn rec; data 10 bytes (exact fit) [deq, txn]" +172,"L",4,16,0,16,1,10,10,131049,131049,TRUE,FALSE,FALSE,131103,1025,131093,1025,131085,1025,"xid 4 pages + 1 byte for txn rec; data 10 bytes (exact fit) [deq, txn]" +173,"L",3.5,16,0,16,0,10,10,114664,114664,TRUE,FALSE,FALSE,114718,897,114708,897,114700,897,"xid 3.5 pages for txn rec; data 10 bytes (exact fit) [deq, txn]" +174,"L",3.5,16,0,16,1,10,10,114665,114665,TRUE,FALSE,FALSE,114719,897,114709,897,114701,897,"xid 3.5 pages + 1 byte for txn rec; data 10 bytes (exact fit) [deq, txn]" +,,,,,,,,,,,,,,,,,,,, +"High volume tests of random message lengths - RHM_WRONLY req'd for auto-dequeue == FALSE",,,,,,,,,,,,,,,,,,,, +#175,"M",1,5000000,0,5000000,0,0,84,0,0,TRUE,FALSE,FALSE,128,1,32,1,0,0,"1 dblk max [deq]" +#176,"M",3,3000000,0,3000000,0,0,340,0,0,TRUE,FALSE,FALSE,384,3,32,1,0,0,"3 dblks max [deq]" +#177,"M",10,1600000,0,1600000,0,0,1236,0,0,TRUE,FALSE,FALSE,1280,10,32,1,0,0,"10 dblks max [deq]" +#178,"M",30,600000,0,600000,0,0,3796,0,0,TRUE,FALSE,FALSE,3840,30,32,1,0,0,"30 dblks max [deq]" +#179,"M",100,200000,0,200000,0,0,12756,0,0,TRUE,FALSE,FALSE,12800,100,32,1,0,0,"100 dblks max [deq]" +#180,"M",300,60000,0,60000,0,0,38356,0,0,TRUE,FALSE,FALSE,38400,300,32,1,0,0,"300 dblks max [deq]" +#181,"M",1000,20000,0,20000,0,0,127956,0,0,TRUE,FALSE,FALSE,128000,1000,32,1,0,0,"1000 dblks max [deq]" +#182,"M",1,5000000,0,5000000,0,0,100,1,100,TRUE,FALSE,FALSE,244,2,144,2,136,2,"100 bytes xid max + 100 bytes data max [deq txn]" +#183,"M",3,3000000,0,3000000,0,0,300,1,300,TRUE,FALSE,FALSE,644,6,344,3,336,3,"300 bytes xid max + 300 bytes data max [deq txn]" +#184,"M",10,1600000,0,1600000,0,0,1000,1,1000,TRUE,FALSE,FALSE,2044,16,1044,9,1036,9,"1000 bytes xid max + 1000 bytes data max [deq txn]" +#185,"M",30,600000,0,600000,0,0,3000,1,3000,TRUE,FALSE,FALSE,6044,48,3044,24,3036,24,"3000 bytes xid max + 3000 bytes data max [deq txn]" +#186,"M",100,200000,0,200000,0,0,10000,1,10000,TRUE,FALSE,FALSE,20044,157,10044,79,10036,79,"10000 bytes xid max + 10000 bytes data max [deq txn]" +#187,"M",300,60000,0,60000,0,0,30000,1,30000,TRUE,FALSE,FALSE,60044,470,30044,235,30036,235,"30000 bytes xid max + 30000 bytes data max [deq txn]" +#188,"M",1000,20000,0,20000,0,0,100000,1,100000,TRUE,FALSE,FALSE,200044,1563,100044,782,100036,782,"100000 bytes xid max + 100000 bytes data max [deq txn]" +,,,,,,,,,,,,,,,,,,,, +"STANDARD PERFORMANCE BENCHMARK: 10,000,000 writes, data=212b (2 dblks)",,,,,,,,,,,,,,,,,,,, +#189,"M",1,10000000,0,10000000,0,212,212,0,0,TRUE,FALSE,FALSE,256,2,32,1,0,0,"212 bytes data (2 dblks enq + 1 dblk deq)" +#190,"M",1,10000000,0,10000000,0,148,148,64,64,TRUE,FALSE,FALSE,256,2,108,1,100,1,"148 bytes data + 64 bytes xid (2 dblks enq + 1 dblks deq + 1 dblks txn)" diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/main.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/main.cpp new file mode 100644 index 0000000000..c8a4642b1c --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/main.cpp @@ -0,0 +1,57 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "test_mgr.h" + +#include "args.h" +#include <csignal> +#include <iostream> + +#define PACKAGE_NAME "Journal Test Tool" +#define VERSION "0.1" + +namespace po = boost::program_options; + +int main(int argc, char** argv) +{ + std::signal(SIGINT, mrg::jtt::test_mgr::signal_handler); + std::signal(SIGTERM, mrg::jtt::test_mgr::signal_handler); + + std::cout << PACKAGE_NAME << " v." << VERSION << std::endl; + + std::ostringstream oss; + oss << PACKAGE_NAME << " options"; + mrg::jtt::args args(oss.str()); + if (args.parse(argc, argv)) return 1; + + try + { + mrg::jtt::test_mgr tm(args); + tm.run(); + if (tm.error()) return 2; // One or more tests threw exceptions + } + catch (const std::exception& e) + { + std::cerr << e.what() << std::endl; + return 3; + } + return 0; +} diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/read_arg.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/read_arg.cpp new file mode 100644 index 0000000000..94a07c7005 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/read_arg.cpp @@ -0,0 +1,93 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "read_arg.h" + +#include <cassert> +#include <boost/program_options.hpp> +namespace po = boost::program_options; + +namespace mrg +{ +namespace jtt +{ +std::map<std::string, read_arg::read_mode_t> read_arg::_map; +std::string read_arg::_description; +const bool read_arg::init = __init(); + +// static init fn +bool +read_arg::__init() +{ + // Set string versions of each enum option here + _map["NONE"] = NONE; + _map["ALL"] = ALL; + _map["RANDOM"] = RANDOM; + _map["LAZYLOAD"] = LAZYLOAD; + _description = "Determines if and when messages will be read prior to dequeueing. " + "Values: (NONE | ALL | RANDOM | LAZYLOAD)"; + return true; +} + +void +read_arg::parse(const std::string& str) +{ + std::map<std::string, read_arg::read_mode_t>::const_iterator i = _map.find(str); + if (i == _map.end()) + throw po::invalid_option_value(str); + _rm = i->second; +} + +// static fn +const std::string& +read_arg::str(const read_mode_t rm) +{ + std::map<std::string, read_mode_t>::const_iterator i = _map.begin(); + while (i->second != rm && i != _map.end()) i++; + assert(i != _map.end()); + return i->first; +} + +// static fn +const std::string& +read_arg::descr() +{ + return _description; +} + +std::ostream& +operator<<(std::ostream& os, const read_arg& ra) +{ + os << ra.str(); + return os; +} + +std::istream& +operator>>(std::istream& is, read_arg& ra) +{ + std::string s; + is >> s; + ra.parse(s); + return is; +} + +} // namespace jtt +} // namespace mrg diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/read_arg.h b/qpid/cpp/src/tests/legacystore/jrnl/jtt/read_arg.h new file mode 100644 index 0000000000..a8fd6f198e --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/read_arg.h @@ -0,0 +1,62 @@ +/* + * + * 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. + * + */ + +#ifndef mrg_jtt_read_arg_hpp +#define mrg_jtt_read_arg_hpp + +#include <string> +#include <map> + +namespace mrg +{ +namespace jtt +{ + +class read_arg +{ + public: + enum read_mode_t { NONE, ALL, RANDOM, LAZYLOAD}; + private: + static std::map<std::string, read_mode_t> _map; + static std::string _description; + static const bool init; + static bool __init(); + read_mode_t _rm; + public: + inline read_arg() : _rm(NONE) {} + inline read_arg(read_mode_t rm) : _rm(rm) {} + + inline read_mode_t val() const { return _rm; } + inline void set_val(const read_mode_t rm) { _rm = rm; } + void parse(const std::string& str); + + inline const std::string& str() const { return str(_rm); } + static const std::string& str(const read_mode_t rm); + static const std::string& descr(); + + friend std::ostream& operator<<(std::ostream& os, const read_arg& ra); + friend std::istream& operator>>(std::istream& is, read_arg& ra); +}; + +} // namespace jtt +} // namespace mrg + +#endif // ifndef mrg_jtt_read_arg_hpp diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case.cpp new file mode 100644 index 0000000000..e06e053504 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case.cpp @@ -0,0 +1,179 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "test_case.h" + +#include <cstdlib> +#include <iomanip> +#include <sstream> + +namespace mrg +{ +namespace jtt +{ + +test_case::test_case(const unsigned test_case_num, const u_int32_t num_msgs, + const std::size_t min_data_size, const std::size_t max_data_size, const bool auto_deq, + const std::size_t min_xid_size, const std::size_t max_xid_size, const transient_t transient, + const external_t external, const std::string& comment): + _test_case_num(test_case_num), + _num_msgs(num_msgs), + _min_data_size(min_data_size), + _max_data_size(max_data_size), + _auto_dequeue(auto_deq), + _min_xid_size(min_xid_size), + _max_xid_size(max_xid_size), + _transient(transient), + _external(external), + _comment(comment), + _result_average(), + _result_jmap() +{} + +test_case::~test_case() +{} + +std::size_t +test_case::this_data_size() const +{ + if (_min_data_size == _max_data_size) + return _max_data_size; + std::size_t size_diff = _max_data_size - _min_data_size; + return _min_data_size + std::size_t(1.0 * std::rand() * size_diff/(RAND_MAX + 1.0)); +} + +std::size_t +test_case::this_xid_size() const +{ + // TODO: rework when probabilities are introduced. Assume 50% if _min_xid_size = 0 + if (_max_xid_size == 0) + return std::size_t(0); + if (_min_xid_size == 0) + { + if (1.0 * std::rand() / RAND_MAX < 0.5) + return std::size_t(0); + } + std::size_t size_diff = _max_xid_size - _min_xid_size; + return _min_xid_size + std::size_t(1.0 * std::rand() * size_diff/(RAND_MAX + 1.0)); +} + +bool +test_case::this_transience() const +{ + // TODO: rework when probabilities are introduced. Assume 50% if JTT_RANDOM + if (_transient == JTT_TRANSIENT) + return false; + if (_transient == JTT_PERSISTNET) + return true; + return 1.0 * std::rand() / RAND_MAX < 0.5; +} + +bool +test_case::this_external() const +{ + // TODO: rework when probabilities are introduced. Assume 50% if JDL_RANDOM + if (_external == JDL_INTERNAL) + return false; + if (_external == JDL_EXTERNAL) + return true; + return 1.0 * std::rand() / RAND_MAX < 0.5; +} + +void +test_case::add_result(test_case_result::shared_ptr& tcrp) +{ + _result_average.add_test_result(tcrp); + res_map_citr ari = _result_jmap.find(tcrp->jid()); + if (ari == _result_jmap.end()) + { + test_case_result_agregation::shared_ptr p(new test_case_result_agregation(tcrp->jid())); + p->add_test_result(tcrp); + _result_jmap.insert(res_map_pair(tcrp->jid(), p)); + } + else + ari->second->add_test_result(tcrp); +} + +void +test_case::set_fmt_chk_res(const bool res, const std::string& jid) +{ + _result_average.set_fmt_chk_res(res); + res_map_citr ari = _result_jmap.find(jid); + if (ari != _result_jmap.end()) + ari->second->set_fmt_chk_res(res); +} + +const test_case_result::shared_ptr +test_case::jmap_last(std::string& jid) const +{ + res_map_citr i = _result_jmap.find(jid); + if (i == _result_jmap.end()) + return test_case_result::shared_ptr(); + u_int32_t num_res = (*i).second->num_results(); + if (num_res) + return (*(*i).second)[num_res - 1]; + return test_case_result::shared_ptr(); +} + +void +test_case::clear() +{ + _result_average.clear(); + _result_jmap.clear(); +} + +const std::string +test_case::str() const +{ + std::ostringstream oss; + oss << "Test Parameters: Test case no. " << _test_case_num << ":" << std::endl; + oss << " Comment: " << _comment << std::endl; + oss << " Number of messages: " << _num_msgs << std::endl; + oss << " Data size: " << _min_data_size; + if (_min_data_size == _max_data_size) + oss << " bytes (fixed)" << std::endl; + else + oss << " - " << _max_data_size << " bytes" << std::endl; + oss << " XID size: " << _min_xid_size; + if (_min_xid_size == _max_xid_size) + oss << " bytes (fixed)" << std::endl; + else + oss << " - " << _max_xid_size << " bytes" << std::endl; + oss << " Auto-dequeue: " << (_auto_dequeue ? "true" : "false") << std::endl; + oss << " Persistence: "; + switch (_transient) + { + case JTT_TRANSIENT: oss << "TRANSIENT" << std::endl; break; + case JTT_PERSISTNET: oss << "PERSISTNET" << std::endl; break; + case JTT_RANDOM: oss << "RANDOM" << std::endl; break; + } + oss << " Message Data: "; + switch (_external) + { + case JDL_INTERNAL: oss << "INTERNAL"; break; + case JDL_EXTERNAL: oss << "EXTERNAL"; break; + case JDL_RANDOM: oss << "RANDOM"; break; + } + return oss.str(); +} + +} // namespace jtt +} // namespace mrg diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case.h b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case.h new file mode 100644 index 0000000000..f72dd05f0c --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case.h @@ -0,0 +1,110 @@ +/* + * + * 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. + * + */ + +#ifndef mrg_jtt_test_case_hpp +#define mrg_jtt_test_case_hpp + +#include <boost/shared_ptr.hpp> +#include <cstddef> +#include <map> +#include "test_case_result.h" +#include "test_case_result_agregation.h" +#include <vector> + +namespace mrg +{ +namespace jtt +{ + + class test_case + { + public: + enum transient_type { JTT_TRANSIENT = 0, JTT_PERSISTNET, JTT_RANDOM }; + typedef transient_type transient_t; + + enum data_location { JDL_INTERNAL = 0, JDL_EXTERNAL, JDL_RANDOM }; + typedef data_location external_t; + + typedef boost::shared_ptr<test_case> shared_ptr; + + typedef std::map<std::string, test_case_result_agregation::shared_ptr> res_map; + typedef std::pair<std::string, test_case_result_agregation::shared_ptr> res_map_pair; + typedef res_map::const_iterator res_map_citr; + + private: + unsigned _test_case_num; + u_int32_t _num_msgs; + std::size_t _min_data_size; + std::size_t _max_data_size; + bool _auto_dequeue; + // TODO: add probability of transaction to these params + std::size_t _min_xid_size; + std::size_t _max_xid_size; + // TODO: change these enums (transient_t & external_t) to probabilities + transient_t _transient; + external_t _external; + std::string _comment; + + test_case_result_agregation _result_average; // overall average result + res_map _result_jmap; // map of per-journal averages + + public: + test_case(const unsigned test_case_num, const u_int32_t num_msgs, + const std::size_t min_data_size, const std::size_t max_data_size, + const bool auto_deq, const std::size_t min_xid_size, + const std::size_t max_xid_size, const transient_t transient, + const external_t external, const std::string& comment); + virtual ~test_case(); + + inline unsigned test_case_num() const { return _test_case_num; } + inline u_int32_t num_msgs() const { return _num_msgs; } + inline std::size_t min_data_size() const { return _min_data_size; } + inline std::size_t max_data_size() const { return _max_data_size; } + std::size_t this_data_size() const; + inline bool auto_deq() const { return _auto_dequeue; } + inline std::size_t min_xid_size() const { return _min_xid_size; } + inline std::size_t max_xid_size() const { return _max_xid_size; } + std::size_t this_xid_size() const; + inline transient_t transient() const { return _transient; } + bool this_transience() const; + inline external_t external() const { return _external; } + bool this_external() const; + inline const std::string& comment() const { return _comment; } + + void add_result(test_case_result::shared_ptr& p); + void set_fmt_chk_res(const bool res, const std::string& jid); + + inline const test_case_result_agregation& average() const { return _result_average; } + inline u_int32_t num_results() const { return _result_average.num_results(); } + inline unsigned num_jrnls() const { return _result_jmap.size(); } + inline res_map_citr jrnl_average(std::string& jid) const { return _result_jmap.find(jid); } + inline res_map_citr jmap_begin() const { return _result_jmap.begin(); } + inline res_map_citr jmap_end() const { return _result_jmap.end(); } + const test_case_result::shared_ptr jmap_last(std::string& jid) const; + + void clear(); + const std::string str() const; + }; + +} // namespace jtt +} // namespace mrg + +#endif // ifndef mrg_jtt_test_case_hpp diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_result.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_result.cpp new file mode 100644 index 0000000000..2f88f265a5 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_result.cpp @@ -0,0 +1,201 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "test_case_result.h" + +#include <iomanip> +#include <sstream> + +namespace mrg +{ +namespace jtt +{ + +test_case_result::test_case_result(const std::string& jid): + _jid(jid), + _num_enq(0), + _num_deq(0), + _num_read(0), + _num_rproc(0), + _start_time(), + _stop_time(), + _stopped(false), + _test_time(), + _exception_list() +{} + +test_case_result::~test_case_result() +{} + +const std::string +test_case_result::test_time_str() const +{ + return _test_time.str(9); +} + +void +test_case_result::add_exception(const journal::jexception& e, const bool set_stop_time_flag) +{ + if (!_stopped && set_stop_time_flag) + { + set_stop_time(); + _stopped = true; + } + _exception_list.push_back(e.what()); +} + +void +test_case_result::add_exception(const std::string& err_str, const bool set_stop_time_flag) +{ + if (!_stopped && set_stop_time_flag) + { + set_stop_time(); + _stopped = true; + } + _exception_list.push_back(err_str); +} + +void +test_case_result::add_exception(const char* err_str, const bool set_stop_time_flag) +{ + if (!_stopped && set_stop_time_flag) + { + set_stop_time(); + _stopped = true; + } + _exception_list.push_back(err_str); +} + +void +test_case_result::clear() +{ + _num_enq = 0; + _num_deq = 0; + _num_read = 0; + _start_time.set_zero(); + _stop_time.set_zero(); + _test_time.set_zero(); + _exception_list.clear(); +} + +const std::string +test_case_result::str(const bool summary) const +{ + std::ostringstream oss; + if (summary) + { + oss << _jid << ":"; + oss << str_summary(); + if (_exception_list.size()) + oss << "; fail: " << _exception_list[0] << std::endl; + else + oss << "; ok" << std::endl; + } + else + { + oss << "--- Journal instance: jid=\"" << _jid << "\" ---" << std::endl; + oss << str_full(); + if (_exception_list.size()) + oss << " exception/error:" << _exception_list[0] << std::endl; + } + return oss.str(); +} + +const std::string +test_case_result::str_full() const +{ + const double t = _test_time.tv_sec + (_test_time.tv_nsec/1e9); + const bool no_exception = _exception_list.empty(); + std::ostringstream oss; + oss.setf(std::ios::fixed, std::ios::floatfield); + oss.precision(2); + if (no_exception) + { + oss.precision(6); + oss << " total test time: " << t << "s" << std::endl; + } + oss.precision(3); + oss << " total number enqueues: " << _num_enq; + if (no_exception) + oss << " (" << (_num_enq / t) << " enq/sec)"; + oss << std::endl; + oss << " total number dequeues: " << _num_deq; + if (no_exception) + oss << " (" << (_num_deq / t) << " deq/sec)"; + oss << std::endl; + oss << "total write operations: " << (_num_enq + _num_deq); + if (no_exception) + oss << " (" << ((_num_enq + _num_deq) / t) << " wrops/sec)"; + oss << std::endl; + oss << " total number reads: " << _num_read; + if (no_exception) + oss << " (" << (_num_read / t) << " rd/sec)"; + oss << std::endl; + oss << " total operations: " << (_num_enq + _num_deq + _num_read); + if (no_exception) + oss << " (" << ((_num_enq + _num_deq + _num_read) / t) << " ops/sec)"; + oss << std::endl; + oss << " overall result: " << (no_exception ? "PASS" : "*** FAIL ***") << std::endl; + return oss.str(); +} + +const std::string +test_case_result::str_summary() const +{ + const double t = _test_time.tv_sec + (_test_time.tv_nsec/1e9); + const bool no_exception = _exception_list.empty(); + std::ostringstream oss; + oss.setf(std::ios::fixed, std::ios::floatfield); + if (no_exception) + { + oss.precision(6); + oss << " t=" << t << "s;"; + } + else + oss << " exception"; + oss.precision(3); + oss << " enq=" << _num_enq; + if (no_exception) + oss << " (" << (_num_enq / t) << ")"; + oss << "; deq=" << _num_deq; + if (no_exception) + oss << " (" << (_num_deq / t) << ")"; + oss << "; wr=" << (_num_enq + _num_deq); + if (no_exception) + oss << " (" << ((_num_enq + _num_deq) / t) << ")"; + oss << "; rd=" << _num_read; + if (no_exception) + oss << " (" << (_num_read / t) << ")"; + oss << "; tot=" << (_num_enq + _num_deq + _num_read); + if (no_exception) + oss << " (" << ((_num_enq + _num_deq + _num_read) / t) << ")"; + return oss.str(); +} + +void +test_case_result::calc_test_time() +{ + if (!_start_time.is_zero() && _stop_time >= _start_time) + _test_time = _stop_time - _start_time; +} + +} // namespace jtt +} // namespace mrg diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_result.h b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_result.h new file mode 100644 index 0000000000..d15f9d021d --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_result.h @@ -0,0 +1,100 @@ +/* + * + * 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. + * + */ + +#ifndef mrg_jtt_test_case_result_hpp +#define mrg_jtt_test_case_result_hpp + +#include <boost/shared_ptr.hpp> +#include <deque> +#include "qpid/legacystore/jrnl/jexception.h" +#include "qpid/legacystore/jrnl/time_ns.h" +#include <string> + +namespace mrg +{ +namespace jtt +{ + + class test_case_result + { + public: + typedef boost::shared_ptr<test_case_result> shared_ptr; + + typedef std::deque<std::string> elist; + typedef elist::const_iterator elist_citr; + + protected: + std::string _jid; + u_int32_t _num_enq; + u_int32_t _num_deq; + u_int32_t _num_read; // Messages actually read + u_int32_t _num_rproc; // Messages handled by read thread (not all are read) + journal::time_ns _start_time; + journal::time_ns _stop_time; + bool _stopped; + journal::time_ns _test_time; + elist _exception_list; + + public: + test_case_result(const std::string& jid); + virtual ~test_case_result(); + + inline const std::string& jid() const { return _jid; } + inline u_int32_t num_enq() const { return _num_enq; } + inline u_int32_t incr_num_enq() { return ++_num_enq; } + inline u_int32_t num_deq() const { return _num_deq; } + inline u_int32_t incr_num_deq() { return ++_num_deq; } + inline u_int32_t num_read() const { return _num_read; } + inline u_int32_t incr_num_read() { return ++_num_read; } + inline u_int32_t num_rproc() const { return _num_rproc; } + inline u_int32_t incr_num_rproc() { return ++_num_rproc; } + + inline const journal::time_ns& start_time() const { return _start_time; } + inline void set_start_time() { ::clock_gettime(CLOCK_REALTIME, &_start_time); } + inline const journal::time_ns& stop_time() const { return _stop_time; } + inline void set_stop_time() + { ::clock_gettime(CLOCK_REALTIME, &_stop_time); calc_test_time(); } + inline void set_test_time(const journal::time_ns& ts) { _test_time = ts; } + inline const journal::time_ns& test_time() const { return _test_time; } + const std::string test_time_str() const; + + void add_exception(const journal::jexception& e, const bool set_stop_time_flag = true); + void add_exception(const std::string& err_str, const bool set_stop_time_flag = true); + void add_exception(const char* err_str, const bool set_stop_time_flag = true); + inline bool exception() const { return _exception_list.size() > 0; } + inline unsigned exception_count() const { return _exception_list.size(); } + inline elist_citr begin() { return _exception_list.begin(); } + inline elist_citr end() { return _exception_list.end(); } + inline const std::string& operator[](unsigned i) { return _exception_list[i]; } + + void clear(); + const std::string str(const bool summary) const; + + protected: + const std::string str_full() const; + const std::string str_summary() const; + void calc_test_time(); + }; + +} // namespace jtt +} // namespace mrg + +#endif // ifndef mrg_jtt_test_case_result_hpp diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_result_agregation.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_result_agregation.cpp new file mode 100644 index 0000000000..da439e71e8 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_result_agregation.cpp @@ -0,0 +1,185 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "test_case_result_agregation.h" + +#include <iomanip> +#include <sstream> + +namespace mrg +{ +namespace jtt +{ + +test_case_result_agregation::test_case_result_agregation(): + test_case_result("Average"), + _tc_average(true), + _fmt_chk_done(false), + _fmt_chk_err(false), + _res_list() +{ +} + +test_case_result_agregation::test_case_result_agregation(const std::string& jid): + test_case_result(jid), + _tc_average(false), + _fmt_chk_done(false), + _fmt_chk_err(false), + _res_list() +{} + +test_case_result_agregation::~test_case_result_agregation() +{} + +void +test_case_result_agregation::add_test_result(const test_case_result::shared_ptr& tcrp) +{ + if (_tc_average || _jid.compare(tcrp->jid()) == 0) + { + _num_enq += tcrp->num_enq(); + _num_deq += tcrp->num_deq(); + _num_read += tcrp->num_read(); + add_test_time(tcrp->test_time()); + _exception_list.insert(_exception_list.end(), tcrp->begin(), tcrp->end()); + _res_list.push_back(tcrp); + } +} + +bool +test_case_result_agregation::exception() const +{ + for (tcrp_list_citr i = _res_list.begin(); i < _res_list.end(); i++) + if ((*i)->exception()) + return true; + return false; +} + +unsigned +test_case_result_agregation::exception_count() const +{ + unsigned cnt = 0; + for (tcrp_list_citr i = _res_list.begin(); i < _res_list.end(); i++) + cnt += (*i)->exception_count(); + return cnt; +} + +void +test_case_result_agregation::clear() +{ + test_case_result::clear(); + _res_list.clear(); +} + +const std::string +test_case_result_agregation::str(const bool last_only, const bool summary) const +{ + std::ostringstream oss; + if (last_only) + oss << " " << _res_list.at(_res_list.size()-1)->str(summary); + else + { + for (tcrp_list_citr i=_res_list.begin(); i!=_res_list.end(); i++) + oss << " " << (*i)->str(summary); + } + if (_res_list.size() > 1) + oss << " " << (summary ? str_summary(last_only) : str_full(last_only)); + return oss.str(); +} + +const std::string +test_case_result_agregation::str_full(const bool /*last_only*/) const +{ + std::ostringstream oss; + oss.precision(2); + if (_tc_average) + oss << "Average across all journal instances:" << std::endl; + else + oss << "Average for jid=\"" << _jid << "\":" << std::endl; + oss << " total number results: " << _res_list.size() << std::endl; + oss << " number exceptions: " << _exception_list.size() << " (" << + (100.0 * _res_list.size() / _exception_list.size()) << "%)" << std::endl; + + oss << test_case_result::str_full(); + + if (_exception_list.size()) + { + unsigned n = 0; + oss << "List of exceptions/errors:" << std::endl; + for (elist_citr i = _exception_list.begin(); i != _exception_list.end(); i++, n++) + oss << " " << n << ". " << (*i) << std::endl; + } + + if (!_tc_average && _res_list.size() > 1) + { + oss << "Individual results:" << std::endl; + for (tcrp_list_citr i=_res_list.begin(); i!=_res_list.end(); i++) + oss << " " << (*i)->str(false) << std::endl; + oss << std::endl; + } + + return oss.str(); +} + +const std::string +test_case_result_agregation::str_summary(const bool /*last_only*/) const +{ + std::ostringstream oss; + if (_tc_average) + oss << "overall average [" << _res_list.size() << "]:"; + else + oss << "average (" << _res_list.size() << "):"; + + oss << test_case_result::str_summary(); + if (_fmt_chk_done) + oss << " fmt-chk=" << (_fmt_chk_err ? "fail" : "ok"); + + if (_exception_list.size()) + { + if (_tc_average) + oss << " fail: " << _exception_list.size() << " exception" + << (_exception_list.size()>1?"s":"") << std::endl; + else + { + if (_exception_list.size() == 1) + oss << " fail: " << *_exception_list.begin() << std::endl; + else + { + oss << std::endl; + unsigned n = 0; + for (elist_citr i = _exception_list.begin(); i != _exception_list.end(); i++, n++) + oss << " " << n << ". " << (*i) << std::endl; + } + } + } + else + oss << " ok" << std::endl; + return oss.str(); +} + +const journal::time_ns& +test_case_result_agregation::add_test_time(const journal::time_ns& t) +{ + _test_time += t; + return _test_time; +} + +} // namespace jtt +} // namespace mrg diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_result_agregation.h b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_result_agregation.h new file mode 100644 index 0000000000..0b3998176c --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_result_agregation.h @@ -0,0 +1,81 @@ +/* + * + * 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. + * + */ + +#ifndef mrg_jtt_test_case_result_agregation_hpp +#define mrg_jtt_test_case_result_agregation_hpp + +#include "test_case_result.h" + +#include <iostream> +#include <vector> + +namespace mrg +{ +namespace jtt +{ + + class test_case_result_agregation : public test_case_result + { + public: + typedef boost::shared_ptr<test_case_result_agregation> shared_ptr; + + typedef std::vector<test_case_result::shared_ptr> tcrp_list; + typedef tcrp_list::const_iterator tcrp_list_citr; + + private: + bool _tc_average; + bool _fmt_chk_done; + bool _fmt_chk_err; + tcrp_list _res_list; + + public: + test_case_result_agregation(); // used for average across jrnl instances + test_case_result_agregation(const std::string& jid); + virtual ~test_case_result_agregation(); + + void add_test_result(const test_case_result::shared_ptr& tcrp); + + inline bool tc_average_mode() const { return _tc_average; } + inline bool fmt_chk_done() const { return _fmt_chk_done; } + inline bool fmt_chk_res() const { return _fmt_chk_err; } + inline void set_fmt_chk_res(const bool err) + { _fmt_chk_done = true; _fmt_chk_err |= err; if (err) add_exception("Journal format error"); } + inline u_int32_t num_results() const { return _res_list.size(); } + inline tcrp_list_citr rlist_begin() const { return _res_list.begin(); } + inline tcrp_list_citr rlist_end() const { return _res_list.end(); } + inline const test_case_result::shared_ptr& operator[](unsigned i) const + { return _res_list[i]; } + bool exception() const; + unsigned exception_count() const; + + void clear(); + const std::string str(const bool last_only, const bool summary) const; + + private: + const std::string str_full(const bool last_only) const; + const std::string str_summary(const bool last_only) const; + const journal::time_ns& add_test_time(const journal::time_ns& t); + }; + +} // namespace jtt +} // namespace mrg + +#endif // ifndef mrg_jtt_test_case_result_agregation_hpp diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_set.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_set.cpp new file mode 100644 index 0000000000..b818d6c7ae --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_set.cpp @@ -0,0 +1,169 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "test_case_set.h" + +#include <cstdlib> +#include <fstream> +#include <iostream> + +namespace mrg +{ +namespace jtt +{ + +test_case_set::test_case_set(): + _tc_list(), + _csv_ignored(0) +{} + +test_case_set::test_case_set(const std::string& csv_filename, const bool recover_mode, + const csv_map& cols): + _tc_list(), + _csv_ignored(0) +{ + append_from_csv(csv_filename, recover_mode, cols); +} + +test_case_set::~test_case_set() +{} + +void +test_case_set::append(const unsigned test_case_num, const u_int32_t num_msgs, + const std::size_t min_data_size, const std::size_t max_data_size, const bool auto_deq, + const std::size_t min_xid_size, const std::size_t max_xid_size, + const test_case::transient_t transient, const test_case::external_t external, + const std::string& comment) +{ + test_case::shared_ptr tcp(new test_case(test_case_num, num_msgs, min_data_size, + max_data_size, auto_deq, min_xid_size, max_xid_size, transient, external, comment)); + append(tcp); +} + + +#define CSV_BUFF_SIZE 2048 +void +test_case_set::append_from_csv(const std::string& csv_filename, const bool recover_mode, + const csv_map& cols) +{ + char buff[CSV_BUFF_SIZE]; + std::ifstream ifs(csv_filename.c_str()); + while (ifs.good()) + { + ifs.getline(buff, (std::streamsize)CSV_BUFF_SIZE); + if (ifs.gcount()) + { + test_case::shared_ptr tcp = get_tc_from_csv(buff, cols); + if (tcp.get()) + { + if (!recover_mode || tcp->auto_deq()) + append(tcp); + else + _csv_ignored++; + } + } + } +} + +test_case::shared_ptr +test_case_set::get_tc_from_csv(const std::string& csv_line, const csv_map& cols) +{ + unsigned test_case_num = 0; + u_int32_t num_msgs = 0; + std::size_t min_data_size = 0; + std::size_t max_data_size = 0; + bool auto_deq = false; + std::size_t min_xid_size = 0; + std::size_t max_xid_size = 0; + test_case::transient_t transient = test_case::JTT_TRANSIENT; + test_case::external_t external = test_case::JDL_INTERNAL; + std::string comment; + + csv_tok t(csv_line); + unsigned col_num = 0; + for (csv_tok_citr t_itr = t.begin(); t_itr != t.end(); ++t_itr, ++col_num) + { + const std::string& tok = *t_itr; + csv_map_citr m_citr = cols.find(col_num); + if (m_citr != cols.end()) + { + switch (m_citr->second) + { + case CSV_TC_NUM: + if (!tok.size() || tok[0] < '0' || tok[0] > '9') + return test_case::shared_ptr(); + test_case_num = unsigned(std::atol(tok.c_str())); + break; + case CSV_TC_NUM_MSGS: num_msgs = u_int32_t(std::atol(tok.c_str())); break; + case CSV_TC_MIN_DATA_SIZE: min_data_size = std::size_t(std::atol(tok.c_str())); break; + case CSV_TC_MAX_DATA_SIZE: max_data_size = std::size_t(std::atol(tok.c_str())); break; + case CSV_TC_AUTO_DEQ: + if (tok == "TRUE" || tok == "1") + auto_deq = true; + break; + case CSV_TC_MIN_XID_SIZE: min_xid_size = std::size_t(std::atol(tok.c_str())); break; + case CSV_TC_MAX_XID_SIZE: max_xid_size = std::size_t(std::atol(tok.c_str())); break; + case CSV_TC_TRANSIENT: + if (tok == "TRUE" || tok == "1") + transient = test_case::JTT_PERSISTNET; + else if (tok == "RANDOM" || tok == "-1") + transient = test_case::JTT_RANDOM; + break; + case CSV_TC_EXTERNAL: + if (tok == "TRUE" || tok == "1") + external = test_case::JDL_EXTERNAL; + else if (tok == "RANDOM" || tok == "-1") + external = test_case::JDL_RANDOM; + break; + case CSV_TC_COMMENT: comment = *t_itr; break; + } + } + } + if (col_num) + return test_case::shared_ptr(new test_case(test_case_num, num_msgs, min_data_size, + max_data_size, auto_deq, min_xid_size, max_xid_size, transient, external, comment)); + else + return test_case::shared_ptr(); +} + +// Static member initializations +// This csv_map is for use on the standard spreadsheet-derived test case csv files. +test_case_set::csv_map test_case_set::std_csv_map; +const bool test_case_set::_map_init = __init(); + +bool +test_case_set::__init() +{ + std_csv_map.insert(test_case_set::csv_pair(0, test_case_set::CSV_TC_NUM)); + std_csv_map.insert(test_case_set::csv_pair(5, test_case_set::CSV_TC_NUM_MSGS)); + std_csv_map.insert(test_case_set::csv_pair(7, test_case_set::CSV_TC_MIN_DATA_SIZE)); + std_csv_map.insert(test_case_set::csv_pair(8, test_case_set::CSV_TC_MAX_DATA_SIZE)); + std_csv_map.insert(test_case_set::csv_pair(11, test_case_set::CSV_TC_AUTO_DEQ)); + std_csv_map.insert(test_case_set::csv_pair(9, test_case_set::CSV_TC_MIN_XID_SIZE)); + std_csv_map.insert(test_case_set::csv_pair(10, test_case_set::CSV_TC_MAX_XID_SIZE)); + std_csv_map.insert(test_case_set::csv_pair(12, test_case_set::CSV_TC_TRANSIENT)); + std_csv_map.insert(test_case_set::csv_pair(13, test_case_set::CSV_TC_EXTERNAL)); + std_csv_map.insert(test_case_set::csv_pair(20, test_case_set::CSV_TC_COMMENT)); + return true; +} + +} // namespace jtt +} // namespace mrg diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_set.h b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_set.h new file mode 100644 index 0000000000..94a1ee3172 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_set.h @@ -0,0 +1,99 @@ +/* + * + * 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. + * + */ + +#ifndef mrg_jtt_test_case_set_hpp +#define mrg_jtt_test_case_set_hpp + +#include "test_case.h" + +#include <cstddef> +#include <boost/tokenizer.hpp> +#include <map> +#include <vector> + +namespace mrg +{ +namespace jtt +{ + + class test_case_set + { + public: + enum csv_col_enum { + CSV_TC_NUM = 0, + CSV_TC_NUM_MSGS, + CSV_TC_MIN_DATA_SIZE, + CSV_TC_MAX_DATA_SIZE, + CSV_TC_AUTO_DEQ, + CSV_TC_MIN_XID_SIZE, + CSV_TC_MAX_XID_SIZE, + CSV_TC_TRANSIENT, + CSV_TC_EXTERNAL, + CSV_TC_COMMENT }; + typedef std::pair<unsigned, csv_col_enum> csv_pair; + typedef std::map<unsigned, csv_col_enum> csv_map; + typedef csv_map::const_iterator csv_map_citr; + static csv_map std_csv_map; + + typedef std::vector<test_case::shared_ptr> tcl; + typedef tcl::iterator tcl_itr; + typedef tcl::const_iterator tcl_citr; + + typedef boost::tokenizer<boost::escaped_list_separator<char> > csv_tok; + typedef csv_tok::const_iterator csv_tok_citr; + + private: + tcl _tc_list; + static const bool _map_init; + unsigned _csv_ignored; + + public: + test_case_set(); + test_case_set(const std::string& csv_filename, const bool recover_mode, + const csv_map& cols = std_csv_map); + virtual ~test_case_set(); + + inline unsigned size() const { return _tc_list.size(); } + inline unsigned ignored() const { return _csv_ignored; } + inline bool empty() const { return _tc_list.empty(); } + + inline void append(const test_case::shared_ptr& tc) { _tc_list.push_back(tc); } + void append(const unsigned test_case_num, const u_int32_t num_msgs, + const std::size_t min_data_size, const std::size_t max_data_size, + const bool auto_deq, const std::size_t min_xid_size, + const std::size_t max_xid_size, const test_case::transient_t transient, + const test_case::external_t external, const std::string& comment); + void append_from_csv(const std::string& csv_filename, const bool recover_mode, + const csv_map& cols = std_csv_map); + inline tcl_itr begin() { return _tc_list.begin(); } + inline tcl_itr end() { return _tc_list.end(); } + inline const test_case::shared_ptr& operator[](unsigned i) { return _tc_list[i]; } + inline void clear() { _tc_list.clear(); } + + private: + test_case::shared_ptr get_tc_from_csv(const std::string& csv_line, const csv_map& cols); + static bool __init(); + }; + +} // namespace jtt +} // namespace mrg + +#endif // ifndef mrg_jtt_test_case_set_hpp diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_mgr.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_mgr.cpp new file mode 100644 index 0000000000..de0b5dbfb9 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_mgr.cpp @@ -0,0 +1,218 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "test_mgr.h" + +#include <cstdlib> +#include <iostream> +#include <sys/stat.h> +#include "test_case_set.h" + +namespace mrg +{ +namespace jtt +{ + +test_mgr::test_mgr(args& args): + _ji_list(), + _args(args), + _err_flag(false), + _random_fn_ptr(random_fn) +{ + if (_args.seed) + std::srand(_args.seed); +} + +test_mgr::~test_mgr() +{} + +void +test_mgr::run() +{ + // TODO: complete tidy-up of non-summary (verbose) results, then pull through + // a command-line summary to control this. + // Idea: --summary: prints short results afterwards + // --verbose: prints long version as test progresses + // defualt: none of these, similar to current summary = true version. + const bool summary = true; + + std::cout << "CSV file: \"" << _args.test_case_csv_file_name << "\""; + test_case_set tcs(_args.test_case_csv_file_name, _args.recover_mode); + + if (tcs.size()) + { + std::cout << " (found " << tcs.size() << " test case" << (tcs.size() != 1 ? "s" : "") << + ")" << std::endl; + if (tcs.ignored()) + std::cout << "WARNING: " << tcs.ignored() << " test cases were ignored. (All test " + "cases without auto-dequeue are ignored when recover-mode is selected.)" << + std::endl; + _args.print_args(); + } + else if(tcs.ignored()) + { + std::cout << " WARNING: All " << tcs.ignored() << " test case(s) were ignored. (All test " + "cases without auto-dequeue are ignored when recover-mode is selected.)" << + std::endl; + } + else + std::cout << " (WARNING: This CSV file is empty or does not exist.)" << std::endl; + + do + { + unsigned u = 0; + if (_args.randomize) + random_shuffle(tcs.begin(), tcs.end(), _random_fn_ptr); + for (test_case_set::tcl_itr tci = tcs.begin(); tci != tcs.end(); tci++, u++) + { + if (summary) + std::cout << "Test case " << (*tci)->test_case_num() << ": \"" << + (*tci)->comment() << "\"" << std::endl; + else + std::cout << (*tci)->str() << std::endl; + if (!_args.reuse_instance || _ji_list.empty()) + initialize_jrnls(); + for (ji_list_citr jii=_ji_list.begin(); jii!=_ji_list.end(); jii++) + (*jii)->init_tc(*tci, &_args); + for (ji_list_citr jii=_ji_list.begin(); jii!=_ji_list.end(); jii++) + (*jii)->run_tc(); + for (ji_list_citr jii=_ji_list.begin(); jii!=_ji_list.end(); jii++) + (*jii)->tc_wait_compl(); + + if (_args.format_chk) + { + for (ji_list_citr jii=_ji_list.begin(); jii!=_ji_list.end(); jii++) + { + jrnl_init_params::shared_ptr jpp = (*jii)->params(); + std::string ja = _args.jfile_analyzer; + if (ja.empty()) ja = "./jfile_chk.py"; + if (!exists(ja)) + { + std::ostringstream oss; + oss << "ERROR: Validation program \"" << ja << "\" does not exist" << std::endl; + throw std::runtime_error(oss.str()); + } + std::ostringstream oss; + oss << ja << " -b " << jpp->base_filename(); + // TODO: When jfile_check.py can handle previously recovered journals for + // specific tests, then remove this exclusion. + if (!_args.recover_mode) + { + oss << " -c " << _args.test_case_csv_file_name; + oss << " -t " << (*tci)->test_case_num(); + } + oss << " -q " << jpp->jdir(); + bool res = system(oss.str().c_str()) != 0; + (*tci)->set_fmt_chk_res(res, jpp->jid()); + if (res) _err_flag = true; + } + } + + if (!_args.recover_mode && !_args.keep_jrnls) + for (ji_list_citr jii=_ji_list.begin(); jii!=_ji_list.end(); jii++) + try { mrg::journal::jdir::delete_dir((*jii)->jrnl_dir()); } + catch (...) {} // TODO - work out exception strategy for failure here... + + print_results(*tci, summary); + if ((*tci)->average().exception()) + _err_flag = true; + if (_abort || (!_args.repeat_flag && _signal)) + break; + if (_args.pause_secs && tci != tcs.end()) + ::usleep(_args.pause_secs * 1000000); + } + } + while (_args.repeat_flag && !_signal); +} + +// static fn: +void +test_mgr::signal_handler(int sig) +{ + if (_signal) + _abort = true; + _signal = sig; + std::cout << std::endl; + std::cout << "********************************" << std::endl; + std::cout << "Caught signal " << sig << std::endl; + if (_abort) + std::cout << "Aborting..." << std::endl; + else + std::cout << "Completing current test cycle..." << std::endl; + std::cout << "********************************" << std::endl << std::endl; +} + +bool +test_mgr::exists(std::string fname) +{ + struct stat s; + if (::stat(fname.c_str(), &s)) + { + if (errno == ENOENT) // No such dir or file + return false; + // Throw for any other condition + std::ostringstream oss; + oss << "ERROR: test_mgr::exists(): file=\"" << fname << "\": " << FORMAT_SYSERR(errno); + throw std::runtime_error(oss.str()); + } + return true; +} + +void +test_mgr::initialize_jrnls() +{ + _ji_list.clear(); + for (unsigned i=0; i<_args.num_jrnls; i++) + { + std::ostringstream jid; + jid << std::hex << std::setfill('0'); + jid << "test_" << std::setw(4) << std::hex << i; + std::ostringstream jdir; + jdir << _args.journal_dir << "/" << jid.str(); + jrnl_init_params::shared_ptr jpp(new jrnl_init_params(jid.str(), jdir.str(), jid.str())); + jrnl_instance::shared_ptr jip(new jrnl_instance(jpp)); + _ji_list.push_back(jip); + } +} + +void +test_mgr::print_results(test_case::shared_ptr tcp, const bool summary) +{ + if (!summary) + std::cout << " === Results ===" << std::endl; + +// TODO - the reporting is broken when --repeat is used. The following commented-out +// section was an attempt to fix it, but there are too many side-effects. +// for (test_case::res_map_citr i=tcp->jmap_begin(); i!=tcp->jmap_end(); i++) +// std::cout << (*i).second->str(summary, summary); +// if (tcp->num_jrnls() > 1) + std::cout << tcp->average().str(false, summary); + + if (!summary) + std::cout << std::endl; +} + +// static instances +volatile sig_atomic_t test_mgr::_signal = 0; +volatile bool test_mgr::_abort = false; + +} // namespace jtt +} // namespace mrg diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_mgr.h b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_mgr.h new file mode 100644 index 0000000000..e608ac6280 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_mgr.h @@ -0,0 +1,68 @@ +/* + * + * 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. + * + */ + +#ifndef mrg_jtt_test_mgr_hpp +#define mrg_jtt_test_mgr_hpp + +#include "args.h" +#include <csignal> +#include <cstdlib> +#include "jrnl_instance.h" + +namespace mrg +{ +namespace jtt +{ + class test_mgr + { + public: + typedef std::vector<jrnl_instance::shared_ptr> ji_list; + typedef ji_list::iterator ji_list_itr; + typedef ji_list::const_iterator ji_list_citr; + + private: + ji_list _ji_list; + args& _args; + bool _err_flag; + ptrdiff_t (*_random_fn_ptr)(const ptrdiff_t i); + static volatile std::sig_atomic_t _signal; + static volatile bool _abort; + + public: + test_mgr(args& args); + virtual ~test_mgr(); + void run(); + inline bool error() const { return _err_flag; } + + static void signal_handler(int signal); + + private: + static bool exists(std::string file_name); + void initialize_jrnls(); + void print_results(test_case::shared_ptr tcp, const bool summary); + inline static ptrdiff_t random_fn(const ptrdiff_t i) + { return static_cast<ptrdiff_t>(1.0 * i * std::rand() / RAND_MAX); } + }; + +} // namespace jtt +} // namespace mrg + +#endif // ifndef mrg_jtt_test_mgr_hpp diff --git a/qpid/cpp/src/tests/legacystore/jrnl/prof b/qpid/cpp/src/tests/legacystore/jrnl/prof new file mode 100755 index 0000000000..2abe7baa4a --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/prof @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +mkdir -p profile +opcontrol --setup --no-vmlinux --separate=library +opcontrol --start +# -- Do stuff here -- +./jtest wtests.csv 264 +# -- End of stuff -- +opcontrol --stop +opcontrol --dump +opcontrol --shutdown +opreport -l ./jtest +opannotate --source --output-dir=profile ./jtest diff --git a/qpid/cpp/src/tests/legacystore/jrnl/run-journal-tests b/qpid/cpp/src/tests/legacystore/jrnl/run-journal-tests new file mode 100755 index 0000000000..e169e39c60 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/run-journal-tests @@ -0,0 +1,47 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +if test x${TMP_DATA_DIR} == x; then + export TMP_DATA_DIR=/tmp +fi +fail=0 +num_jrnls=3 + +# Run jtt using default test set +echo +echo "===== Mode 1: New journal instance, no recover =====" +jtt/jtt --analyzer ../../tools/store_chk --jrnl-dir ${TMP_DATA_DIR} --csv jtt/jtt.csv --format-chk --num-jrnls ${num_jrnls} || fail=1 +rm -rf ${TMP_DATA_DIR}/test_0* +echo +echo "===== Mode 2: Re-use journal instance, no recover =====" +jtt/jtt --analyzer ../../tools/store_chk --jrnl-dir ${TMP_DATA_DIR} --csv jtt/jtt.csv --reuse-instance --format-chk --num-jrnls ${num_jrnls} || fail=1 +rm -rf ${TMP_DATA_DIR}/test_0* +echo +echo "===== Mode 3: New journal instance, recover previous test journal =====" +jtt/jtt --analyzer ../../tools/store_chk --jrnl-dir ${TMP_DATA_DIR} --csv jtt/jtt.csv --recover-mode --format-chk --num-jrnls ${num_jrnls} || fail=1 +rm -rf ${TMP_DATA_DIR}/test_0* +echo +echo "===== Mode 4: Re-use journal instance, recover previous test journal =====" +jtt/jtt --analyzer ../../tools/store_chk --jrnl-dir ${TMP_DATA_DIR} --csv jtt/jtt.csv --reuse-instance --recover-mode --format-chk --num-jrnls ${num_jrnls} || fail=1 +rm -rf ${TMP_DATA_DIR}/test_0* +echo + +exit $fail diff --git a/qpid/cpp/src/tests/legacystore/jrnl/tests.ods b/qpid/cpp/src/tests/legacystore/jrnl/tests.ods Binary files differnew file mode 100644 index 0000000000..d900374321 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/jrnl/tests.ods diff --git a/qpid/cpp/src/tests/legacystore/persistence.py b/qpid/cpp/src/tests/legacystore/persistence.py new file mode 100644 index 0000000000..c4ab712f14 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/persistence.py @@ -0,0 +1,574 @@ +# +# 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, re, traceback, socket +from getopt import getopt, GetoptError + +from qpid.connection import Connection +from qpid.util import connect +from qpid.datatypes import Message, RangedSet +from qpid.queue import Empty +from qpid.session import SessionException +from qpid.testlib import TestBase010 +from time import sleep + +class PersistenceTest(TestBase010): + + XA_RBROLLBACK = 1 + XA_RBTIMEOUT = 2 + XA_OK = 0 + + def createMessage(self, **kwargs): + session = self.session + dp = {} + dp['delivery_mode'] = 2 + mp = {} + for k, v in kwargs.iteritems(): + if k in ['routing_key', 'delivery_mode']: dp[k] = v + if k in ['message_id', 'correlation_id', 'application_headers']: mp[k] = v + args = [] + args.append(session.delivery_properties(**dp)) + if len(mp): + args.append(session.message_properties(**mp)) + if kwargs.has_key('body'): args.append(kwargs['body']) + return Message(*args) + + def phase1(self): + session = self.session + + session.queue_declare(queue="queue-a", durable=True) + session.queue_declare(queue="queue-b", durable=True) + session.exchange_bind(queue="queue-a", exchange="amq.direct", binding_key="a") + session.exchange_bind(queue="queue-b", exchange="amq.direct", binding_key="b") + + session.message_transfer(destination="amq.direct", + message=self.createMessage(routing_key="a", correlation_id="Msg0001", body="A_Message1")) + session.message_transfer(destination="amq.direct", + message=self.createMessage(routing_key="b", correlation_id="Msg0002", body="B_Message1")) + +# session.queue_declare(queue="lvq-test", durable=True, arguments={"qpid.last_value_queue":True}) +# session.message_transfer(message=self.createMessage(routing_key="lvq-test", application_headers={"qpid.LVQ_key":"B"}, body="B1")) +# session.message_transfer(message=self.createMessage(routing_key="lvq-test", application_headers={"qpid.LVQ_key":"A"}, body="A1")) +# session.message_transfer(message=self.createMessage(routing_key="lvq-test", application_headers={"qpid.LVQ_key":"A"}, body="A2")) +# session.message_transfer(message=self.createMessage(routing_key="lvq-test", application_headers={"qpid.LVQ_key":"B"}, body="B2")) +# session.message_transfer(message=self.createMessage(routing_key="lvq-test", application_headers={"qpid.LVQ_key":"B"}, body="B3")) +# session.message_transfer(message=self.createMessage(routing_key="lvq-test", application_headers={"qpid.LVQ_key":"C"}, body="C1")) + + + + def phase2(self): + session = self.session + + #check queues exists + session.queue_declare(queue="queue-a", durable=True, passive=True) + session.queue_declare(queue="queue-b", durable=True, passive=True) + + #check they are still bound to amq.direct correctly + responses = [] + responses.append(session.exchange_bound(queue="queue-a", exchange="amq.direct", binding_key="a")) + responses.append(session.exchange_bound(queue="queue-b", exchange="amq.direct", binding_key="b")) + for r in responses: + self.assert_(not r.exchange_not_found) + self.assert_(not r.queue_not_found) + self.assert_(not r.key_not_matched) + + + #check expected messages are there + self.assertMessageOnQueue("queue-a", "Msg0001", "A_Message1") + self.assertMessageOnQueue("queue-b", "Msg0002", "B_Message1") + + self.assertEmptyQueue("queue-a") + self.assertEmptyQueue("queue-b") + + session.queue_declare(queue="queue-c", durable=True) + + #send a message to a topic such that it reaches all queues + session.exchange_bind(queue="queue-a", exchange="amq.topic", binding_key="abc") + session.exchange_bind(queue="queue-b", exchange="amq.topic", binding_key="abc") + session.exchange_bind(queue="queue-c", exchange="amq.topic", binding_key="abc") + + session.message_transfer(destination="amq.topic", + message=self.createMessage(routing_key="abc", correlation_id="Msg0003", body="AB_Message2")) + +# #check LVQ exists and has exepected messages: +# session.queue_declare(queue="lvq-test", durable=True, passive=True) +# session.message_subscribe(destination="lvq", queue="lvq-test") +# lvq = session.incoming("lvq") +# lvq.start() +# accepted = RangedSet() +# for m in ["A2", "B3", "C1"]: +# msg = lvq.get(timeout=1) +# self.assertEquals(m, msg.body) +# accepted.add(msg.id) +# try: +# extra = lvq.get(timeout=1) +# self.fail("lvq-test not empty, contains: " + extra.body) +# except Empty: None +# #publish some more messages while subscriber is active (no replacement): +# session.message_transfer(message=self.createMessage(routing_key="lvq-test", application_headers={"qpid.LVQ_key":"C"}, body="C2")) +# session.message_transfer(message=self.createMessage(routing_key="lvq-test", application_headers={"qpid.LVQ_key":"C"}, body="C3")) +# session.message_transfer(message=self.createMessage(routing_key="lvq-test", application_headers={"qpid.LVQ_key":"A"}, body="A3")) +# session.message_transfer(message=self.createMessage(routing_key="lvq-test", application_headers={"qpid.LVQ_key":"A"}, body="A4")) +# session.message_transfer(message=self.createMessage(routing_key="lvq-test", application_headers={"qpid.LVQ_key":"C"}, body="C4")) +# #check that accepting replaced messages is safe +# session.message_accept(accepted) + + + def phase3(self): + session = self.session + +# #lvq recovery validation +# session.queue_declare(queue="lvq-test", durable=True, passive=True) +# session.message_subscribe(destination="lvq", queue="lvq-test") +# lvq = session.incoming("lvq") +# lvq.start() +# accepted = RangedSet() +# lvq.start() +# for m in ["C4", "A4"]: +# msg = lvq.get(timeout=1) +# self.assertEquals(m, msg.body) +# accepted.add(msg.id) +# session.message_accept(accepted) +# try: +# extra = lvq.get(timeout=1) +# self.fail("lvq-test not empty, contains: " + extra.body) +# except Empty: None +# session.message_cancel(destination="lvq") +# session.queue_delete(queue="lvq-test") + + + #check queues exists + session.queue_declare(queue="queue-a", durable=True, passive=True) + session.queue_declare(queue="queue-b", durable=True, passive=True) + session.queue_declare(queue="queue-c", durable=True, passive=True) + + session.tx_select() + #check expected messages are there + self.assertMessageOnQueue("queue-a", "Msg0003", "AB_Message2") + self.assertMessageOnQueue("queue-b", "Msg0003", "AB_Message2") + self.assertMessageOnQueue("queue-c", "Msg0003", "AB_Message2") + + self.assertEmptyQueue("queue-a") + self.assertEmptyQueue("queue-b") + self.assertEmptyQueue("queue-c") + + #note: default bindings must be restored for this to work + session.message_transfer(message=self.createMessage( + routing_key="queue-a", correlation_id="Msg0004", body="A_Message3")) + session.message_transfer(message=self.createMessage( + routing_key="queue-a", correlation_id="Msg0005", body="A_Message4")) + session.message_transfer(message=self.createMessage( + routing_key="queue-a", correlation_id="Msg0006", body="A_Message5")) + + session.tx_commit() + + + #delete a queue + session.queue_delete(queue="queue-c") + + session.message_subscribe(destination="ctag", queue="queue-a", accept_mode=0) + session.message_flow(destination="ctag", unit=0, value=0xFFFFFFFF) + session.message_flow(destination="ctag", unit=1, value=0xFFFFFFFF) + included = session.incoming("ctag") + msg1 = included.get(timeout=1) + self.assertExpectedContent(msg1, "Msg0004", "A_Message3") + msg2 = included.get(timeout=1) + self.assertExpectedContent(msg2, "Msg0005", "A_Message4") + msg3 = included.get(timeout=1) + self.assertExpectedContent(msg3, "Msg0006", "A_Message5") + self.ack(msg1, msg2, msg3) + + session.message_transfer(destination="amq.direct", message=self.createMessage( + routing_key="queue-b", correlation_id="Msg0007", body="B_Message3")) + + session.tx_rollback() + + + def phase4(self): + session = self.session + + #check queues exists + session.queue_declare(queue="queue-a", durable=True, passive=True) + session.queue_declare(queue="queue-b", durable=True, passive=True) + + self.assertMessageOnQueue("queue-a", "Msg0004", "A_Message3") + self.assertMessageOnQueue("queue-a", "Msg0005", "A_Message4") + self.assertMessageOnQueue("queue-a", "Msg0006", "A_Message5") + + self.assertEmptyQueue("queue-a") + self.assertEmptyQueue("queue-b") + + #check this queue doesn't exist + try: + session.queue_declare(queue="queue-c", durable=True, passive=True) + raise Exception("Expected queue-c to have been deleted") + except SessionException, e: + self.assertEquals(404, e.args[0].error_code) + + def phase5(self): + + session = self.session + queues = ["queue-a1", "queue-a2", "queue-b1", "queue-b2", "queue-c1", "queue-c2", "queue-d1", "queue-d2"] + + for q in queues: + session.queue_declare(queue=q, durable=True) + session.queue_purge(queue=q) + + session.message_transfer(message=self.createMessage( + routing_key="queue-a1", correlation_id="MsgA", body="MessageA")) + session.message_transfer(message=self.createMessage( + routing_key="queue-b1", correlation_id="MsgB", body="MessageB")) + session.message_transfer(message=self.createMessage( + routing_key="queue-c1", correlation_id="MsgC", body="MessageC")) + session.message_transfer(message=self.createMessage( + routing_key="queue-d1", correlation_id="MsgD", body="MessageD")) + + session.dtx_select() + txa = self.xid('a') + txb = self.xid('b') + txc = self.xid('c') + txd = self.xid('d') + + self.txswap("queue-a1", "queue-a2", txa) + self.txswap("queue-b1", "queue-b2", txb) + self.txswap("queue-c1", "queue-c2", txc) + self.txswap("queue-d1", "queue-d2", txd) + + #no queue should have any messages accessible + for q in queues: + self.assertEqual(0, session.queue_query(queue=q).message_count, "Bad count for %s" % (q)) + + self.assertEqual(self.XA_OK, session.dtx_commit(xid=txa, one_phase=True).status) + self.assertEqual(self.XA_OK, session.dtx_rollback(xid=txb).status) + self.assertEqual(self.XA_OK, session.dtx_prepare(xid=txc).status) + self.assertEqual(self.XA_OK, session.dtx_prepare(xid=txd).status) + + #further checks + not_empty = ["queue-a2", "queue-b1"] + for q in queues: + if q in not_empty: + self.assertEqual(1, session.queue_query(queue=q).message_count, "Bad count for %s" % (q)) + else: + self.assertEqual(0, session.queue_query(queue=q).message_count, "Bad count for %s" % (q)) + + + def phase6(self): + session = self.session + + #check prepared transaction are reported correctly by recover + txc = self.xid('c') + txd = self.xid('d') + + xids = session.dtx_recover().in_doubt + ids = [x.global_id for x in xids] #TODO: come up with nicer way to test these + + if txc.global_id not in ids: + self.fail("Recovered xids not as expected. missing: %s" % (txc)) + if txd.global_id not in ids: + self.fail("Recovered xids not as expected. missing: %s" % (txd)) + self.assertEqual(2, len(xids)) + + + queues = ["queue-a1", "queue-a2", "queue-b1", "queue-b2", "queue-c1", "queue-c2", "queue-d1", "queue-d2"] + not_empty = ["queue-a2", "queue-b1"] + + #re-check + not_empty = ["queue-a2", "queue-b1"] + for q in queues: + if q in not_empty: + self.assertEqual(1, session.queue_query(queue=q).message_count, "Bad count for %s" % (q)) + else: + self.assertEqual(0, session.queue_query(queue=q).message_count, "Bad count for %s" % (q)) + + #complete the prepared transactions + self.assertEqual(self.XA_OK, session.dtx_commit(xid=txc).status) + self.assertEqual(self.XA_OK, session.dtx_rollback(xid=txd).status) + not_empty.append("queue-c2") + not_empty.append("queue-d1") + + for q in queues: + if q in not_empty: + self.assertEqual(1, session.queue_query(queue=q).message_count) + else: + self.assertEqual(0, session.queue_query(queue=q).message_count) + + def phase7(self): + session = self.session + session.synchronous = False + + # check xids from phase 6 are gone + txc = self.xid('c') + txd = self.xid('d') + + xids = session.dtx_recover().in_doubt + ids = [x.global_id for x in xids] #TODO: come up with nicer way to test these + + if txc.global_id in ids: + self.fail("Xid still present : %s" % (txc)) + if txd.global_id in ids: + self.fail("Xid still present : %s" % (txc)) + self.assertEqual(0, len(xids)) + + #test deletion of queue after publish + #create queue + session.queue_declare(queue = "q", auto_delete=True, durable=True) + + #send message + for i in range(1, 10): + session.message_transfer(message=self.createMessage(routing_key = "q", body = "my-message")) + + session.synchronous = True + #explicitly delete queue + session.queue_delete(queue = "q") + + #test acking of message from auto-deleted queue + #create queue + session.queue_declare(queue = "q", auto_delete=True, durable=True) + + #send message + session.message_transfer(message=self.createMessage(routing_key = "q", body = "my-message")) + + #create consumer + session.message_subscribe(queue = "q", destination = "a", accept_mode=0, acquire_mode=0) + session.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "a") + session.message_flow(unit = 0, value = 10, destination = "a") + queue = session.incoming("a") + + #consume the message, cancel subscription (triggering auto-delete), then ack it + msg = queue.get(timeout = 5) + session.message_cancel(destination = "a") + self.ack(msg) + + #test implicit deletion of bindings when queue is deleted + session.queue_declare(queue = "durable-subscriber-queue", exclusive=True, durable=True) + session.exchange_bind(exchange="amq.topic", queue="durable-subscriber-queue", binding_key="xyz") + session.message_transfer(destination= "amq.topic", message=self.createMessage(routing_key = "xyz", body = "my-message")) + session.queue_delete(queue = "durable-subscriber-queue") + + #test unbind: + #create a series of bindings to a queue + session.queue_declare(queue = "binding-test-queue", durable=True) + session.exchange_bind(exchange="amq.direct", queue="binding-test-queue", binding_key="abc") + session.exchange_bind(exchange="amq.direct", queue="binding-test-queue", binding_key="pqr") + session.exchange_bind(exchange="amq.direct", queue="binding-test-queue", binding_key="xyz") + session.exchange_bind(exchange="amq.match", queue="binding-test-queue", binding_key="a", arguments={"x-match":"all", "p":"a"}) + session.exchange_bind(exchange="amq.match", queue="binding-test-queue", binding_key="b", arguments={"x-match":"all", "p":"b"}) + session.exchange_bind(exchange="amq.match", queue="binding-test-queue", binding_key="c", arguments={"x-match":"all", "p":"c"}) + #then restart broker... + + + def phase8(self): + session = self.session + + #continue testing unbind: + #send messages to the queue via each of the bindings + for k in ["abc", "pqr", "xyz"]: + data = "first %s" % (k) + session.message_transfer(destination= "amq.direct", message=self.createMessage(routing_key=k, body=data)) + for a in [{"p":"a"}, {"p":"b"}, {"p":"c"}]: + data = "first %s" % (a["p"]) + session.message_transfer(destination="amq.match", message=self.createMessage(application_headers=a, body=data)) + #unbind some bindings (using final 0-10 semantics) + session.exchange_unbind(exchange="amq.direct", queue="binding-test-queue", binding_key="pqr") + session.exchange_unbind(exchange="amq.match", queue="binding-test-queue", binding_key="b") + #send messages again + for k in ["abc", "pqr", "xyz"]: + data = "second %s" % (k) + session.message_transfer(destination= "amq.direct", message=self.createMessage(routing_key=k, body=data)) + for a in [{"p":"a"}, {"p":"b"}, {"p":"c"}]: + data = "second %s" % (a["p"]) + session.message_transfer(destination="amq.match", message=self.createMessage(application_headers=a, body=data)) + + #check that only the correct messages are received + expected = [] + for k in ["abc", "pqr", "xyz"]: + expected.append("first %s" % (k)) + for a in [{"p":"a"}, {"p":"b"}, {"p":"c"}]: + expected.append("first %s" % (a["p"])) + for k in ["abc", "xyz"]: + expected.append("second %s" % (k)) + for a in [{"p":"a"}, {"p":"c"}]: + expected.append("second %s" % (a["p"])) + + session.message_subscribe(queue = "binding-test-queue", destination = "binding-test") + session.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "binding-test") + session.message_flow(unit = 0, value = 10, destination = "binding-test") + queue = session.incoming("binding-test") + + while len(expected): + msg = queue.get(timeout=1) + if msg.body not in expected: + self.fail("Missing message: %s" % msg.body) + expected.remove(msg.body) + try: + msg = queue.get(timeout=1) + self.fail("Got extra message: %s" % msg.body) + except Empty: pass + + + + session.queue_declare(queue = "durable-subscriber-queue", exclusive=True, durable=True) + session.exchange_bind(exchange="amq.topic", queue="durable-subscriber-queue", binding_key="xyz") + session.message_transfer(destination= "amq.topic", message=self.createMessage(routing_key = "xyz", body = "my-message")) + session.queue_delete(queue = "durable-subscriber-queue") + + + def xid(self, txid, branchqual = ''): + return self.session.xid(format=0, global_id=txid, branch_id=branchqual) + + def txswap(self, src, dest, tx): + self.assertEqual(self.XA_OK, self.session.dtx_start(xid=tx).status) + self.session.message_subscribe(destination="temp-swap", queue=src, accept_mode=0) + self.session.message_flow(destination="temp-swap", unit=0, value=1) + self.session.message_flow(destination="temp-swap", unit=1, value=0xFFFFFFFF) + msg = self.session.incoming("temp-swap").get(timeout=1) + self.session.message_cancel(destination="temp-swap") + self.session.message_transfer(message=self.createMessage(routing_key=dest, correlation_id=self.getProperty(msg, 'correlation_id'), + body=msg.body)) + self.ack(msg) + self.assertEqual(self.XA_OK, self.session.dtx_end(xid=tx).status) + + def assertEmptyQueue(self, name): + self.assertEqual(0, self.session.queue_query(queue=name).message_count) + + def assertConnectionException(self, expectedCode, message): + self.assertEqual("connection", message.method.klass.name) + self.assertEqual("close", message.method.name) + self.assertEqual(expectedCode, message.reply_code) + + def assertExpectedMethod(self, reply, klass, method): + self.assertEqual(klass, reply.method.klass.name) + self.assertEqual(method, reply.method.name) + + def assertExpectedContent(self, msg, id, body): + self.assertEqual(id, self.getProperty(msg, 'correlation_id')) + self.assertEqual(body, msg.body) + return msg + + def getProperty(self, msg, name): + for h in msg.headers: + if hasattr(h, name): return getattr(h, name) + return None + + def ack(self, *msgs): + session = self.session + set = RangedSet() + for m in msgs: + set.add(m.id) + #TODO: tidy up completion + session.receiver._completed.add(m.id) + session.message_accept(set) + session.channel.session_completed(session.receiver._completed) + + def assertExpectedGetResult(self, id, body): + return self.assertExpectedContent(session.incoming("incoming-gets").get(timeout=1), id, body) + + def assertEqual(self, expected, actual, msg=''): + if expected != actual: raise Exception("%s expected: %s actual: %s" % (msg, expected, actual)) + + def assertMessageOnQueue(self, queue, id, body): + self.session.message_subscribe(destination="incoming-gets", queue=queue, accept_mode=0) + self.session.message_flow(destination="incoming-gets", unit=0, value=1) + self.session.message_flow(destination="incoming-gets", unit=1, value=0xFFFFFFFF) + msg = self.session.incoming("incoming-gets").get(timeout=1) + self.assertExpectedContent(msg, id, body) + self.ack(msg) + self.session.message_cancel(destination="incoming-gets") + + + def __init__(self): + TestBase010.__init__(self, "run") + self.setBroker("localhost") + self.errata = [] + + def connect(self): + """ Connects to the broker """ + self.conn = Connection(connect(self.host, self.port)) + self.conn.start(timeout=10) + self.session = self.conn.session("test-session", timeout=10) + + def run(self, args=sys.argv[1:]): + try: + opts, extra = getopt(args, "r:s:e:b:p:h", ["retry=", "spec=", "errata=", "broker=", "phase=", "help"]) + except GetoptError, e: + self._die(str(e)) + phase = 0 + retry = 0; + for opt, value in opts: + if opt in ("-h", "--help"): self._die() + if opt in ("-s", "--spec"): self.spec = value + if opt in ("-e", "--errata"): self.errata.append(value) + if opt in ("-b", "--broker"): self.setBroker(value) + if opt in ("-p", "--phase"): phase = int(value) + if opt in ("-r", "--retry"): retry = int(value) + + if not phase: self._die("please specify the phase to run") + phase = "phase%d" % phase + self.connect() + + try: + getattr(self, phase)() + print phase, "succeeded" + res = True; + except Exception, e: + print phase, "failed: ", e + traceback.print_exc() + res = False + + + if not self.session.error(): self.session.close(timeout=10) + self.conn.close(timeout=10) + + # Crude fix to wait for thread in client to exit after return from session_close() + # Reduces occurrences of "Unhandled exception in thread" messages after each test + import time + time.sleep(1) + + return res + + + def setBroker(self, broker): + rex = re.compile(r""" + # [ <user> [ / <password> ] @] <host> [ :<port> ] + ^ (?: ([^/]*) (?: / ([^@]*) )? @)? ([^:]+) (?: :([0-9]+))?$""", re.X) + match = rex.match(broker) + if not match: self._die("'%s' is not a valid broker" % (broker)) + self.user, self.password, self.host, self.port = match.groups() + self.port = int(default(self.port, 5672)) + self.user = default(self.user, "guest") + self.password = default(self.password, "guest") + + def _die(self, message = None): + if message: print message + print """ +Options: + -h/--help : this message + -s/--spec <spec.xml> : file containing amqp XML spec + -p/--phase : test phase to run + -b/--broker [<user>[/<password>]@]<host>[:<port>] : broker to connect to + """ + sys.exit(1) + +def default(value, default): + if (value == None): return default + else: return value + +if __name__ == "__main__": + test = PersistenceTest() + if not test.run(): sys.exit(1) diff --git a/qpid/cpp/src/tests/legacystore/python_tests/__init__.py b/qpid/cpp/src/tests/legacystore/python_tests/__init__.py new file mode 100644 index 0000000000..ebb9da8670 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/python_tests/__init__.py @@ -0,0 +1,24 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Do not delete - marks this directory as a python package. + +from client_persistence import * +from resize import * + diff --git a/qpid/cpp/src/tests/legacystore/python_tests/client_persistence.py b/qpid/cpp/src/tests/legacystore/python_tests/client_persistence.py new file mode 100644 index 0000000000..37c12601be --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/python_tests/client_persistence.py @@ -0,0 +1,239 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import os + +from brokertest import EXPECT_EXIT_OK +from store_test import StoreTest, Qmf, store_args +from qpid.messaging import * + +import qpid.messaging, brokertest +brokertest.qm = qpid.messaging # FIXME aconway 2014-04-04: Tests fail with SWIG client. + +class ExchangeQueueTests(StoreTest): + """ + Simple tests of the broker exchange and queue types + """ + + def test_direct_exchange(self): + """Test Direct exchange.""" + broker = self.broker(store_args(), name="test_direct_exchange", expect=EXPECT_EXIT_OK) + msg1 = Message("A_Message1", durable=True, correlation_id="Msg0001") + msg2 = Message("B_Message1", durable=True, correlation_id="Msg0002") + broker.send_message("a", msg1) + broker.send_message("b", msg2) + broker.terminate() + + broker = self.broker(store_args(), name="test_direct_exchange") + self.check_message(broker, "a", msg1, True) + self.check_message(broker, "b", msg2, True) + + def test_topic_exchange(self): + """Test Topic exchange.""" + broker = self.broker(store_args(), name="test_topic_exchange", expect=EXPECT_EXIT_OK) + ssn = broker.connect().session() + snd1 = ssn.sender("abc/key1; {create:always, node:{type:topic, durable:True}}") + snd2 = ssn.sender("abc/key2; {create:always, node:{type:topic, durable:True}}") + ssn.receiver("a; {create:always, link:{x-bindings:[{exchange:abc, key:key1}]}, node:{durable:True}}") + ssn.receiver("b; {create:always, link:{x-bindings:[{exchange:abc, key:key1}]}, node:{durable:True}}") + ssn.receiver("c; {create:always, link:{x-bindings:[{exchange:abc, key:key1}, " + "{exchange:abc, key: key2}]}, node:{durable:True}}") + ssn.receiver("d; {create:always, link:{x-bindings:[{exchange:abc, key:key2}]}, node:{durable:True}}") + ssn.receiver("e; {create:always, link:{x-bindings:[{exchange:abc, key:key2}]}, node:{durable:True}}") + msg1 = Message("Message1", durable=True, correlation_id="Msg0003") + snd1.send(msg1) + msg2 = Message("Message2", durable=True, correlation_id="Msg0004") + snd2.send(msg2) + broker.terminate() + + broker = self.broker(store_args(), name="test_topic_exchange") + self.check_message(broker, "a", msg1, True) + self.check_message(broker, "b", msg1, True) + self.check_messages(broker, "c", [msg1, msg2], True) + self.check_message(broker, "d", msg2, True) + self.check_message(broker, "e", msg2, True) + + + def test_legacy_lvq(self): + """Test legacy LVQ.""" + broker = self.broker(store_args(), name="test_lvq", expect=EXPECT_EXIT_OK) + ma1 = Message("A1", durable=True, correlation_id="Msg0005", properties={"qpid.LVQ_key":"A"}) + ma2 = Message("A2", durable=True, correlation_id="Msg0006", properties={"qpid.LVQ_key":"A"}) + mb1 = Message("B1", durable=True, correlation_id="Msg0007", properties={"qpid.LVQ_key":"B"}) + mb2 = Message("B2", durable=True, correlation_id="Msg0008", properties={"qpid.LVQ_key":"B"}) + mb3 = Message("B3", durable=True, correlation_id="Msg0009", properties={"qpid.LVQ_key":"B"}) + mc1 = Message("C1", durable=True, correlation_id="Msg0010", properties={"qpid.LVQ_key":"C"}) + broker.send_messages("lvq-test", [mb1, ma1, ma2, mb2, mb3, mc1], + xprops="arguments:{\"qpid.last_value_queue\":True}") + broker.terminate() + + broker = self.broker(store_args(), name="test_lvq", expect=EXPECT_EXIT_OK) + ssn = self.check_messages(broker, "lvq-test", [ma2, mb3, mc1], empty=True, ack=False) + # Add more messages while subscriber is active (no replacement): + ma3 = Message("A3", durable=True, correlation_id="Msg0011", properties={"qpid.LVQ_key":"A"}) + ma4 = Message("A4", durable=True, correlation_id="Msg0012", properties={"qpid.LVQ_key":"A"}) + mc2 = Message("C2", durable=True, correlation_id="Msg0013", properties={"qpid.LVQ_key":"C"}) + mc3 = Message("C3", durable=True, correlation_id="Msg0014", properties={"qpid.LVQ_key":"C"}) + mc4 = Message("C4", durable=True, correlation_id="Msg0015", properties={"qpid.LVQ_key":"C"}) + broker.send_messages("lvq-test", [mc2, mc3, ma3, ma4, mc4], session=ssn) + ssn.acknowledge() + broker.terminate() + + broker = self.broker(store_args(), name="test_lvq") + self.check_messages(broker, "lvq-test", [ma4, mc4], True) + + + def test_fanout_exchange(self): + """Test Fanout Exchange""" + broker = self.broker(store_args(), name="test_fanout_exchange", expect=EXPECT_EXIT_OK) + ssn = broker.connect().session() + snd = ssn.sender("TestFanoutExchange; {create: always, node: {type: topic, x-declare: {type: fanout}}}") + ssn.receiver("TestFanoutExchange; {link: {name: \"q1\", durable: True, reliability:at-least-once}}") + ssn.receiver("TestFanoutExchange; {link: {name: \"q2\", durable: True, reliability:at-least-once}}") + ssn.receiver("TestFanoutExchange; {link: {name: \"q3\", durable: True, reliability:at-least-once}}") + msg1 = Message("Msg1", durable=True, correlation_id="Msg0001") + snd.send(msg1) + msg2 = Message("Msg2", durable=True, correlation_id="Msg0002") + snd.send(msg2) + broker.terminate() + + broker = self.broker(store_args(), name="test_fanout_exchange") + self.check_messages(broker, "q1", [msg1, msg2], True) + self.check_messages(broker, "q2", [msg1, msg2], True) + self.check_messages(broker, "q3", [msg1, msg2], True) + + + def test_message_reject(self): + broker = self.broker(store_args(), name="test_message_reject", expect=EXPECT_EXIT_OK) + ssn = broker.connect().session() + snd = ssn.sender("tmr; {create:always, node:{type:queue, durable:True}}") + rcv = ssn.receiver("tmr; {create:always, node:{type:queue, durable:True}}") + m1 = Message("test_message_reject", durable=True, correlation_id="Msg0001") + snd.send(m1) + m2 = rcv.fetch() + ssn.acknowledge(message=m2, disposition=Disposition(REJECTED)) + broker.terminate() + + broker = self.broker(store_args(), name="test_message_reject") + qmf = Qmf(broker) + assert qmf.queue_message_count("tmr") == 0 + + + def test_route(self): + """ Test the recovery of a route (link and bridge objects.""" + broker = self.broker(store_args(), name="test_route", expect=EXPECT_EXIT_OK) + qmf = Qmf(broker) + qmf_broker_obj = qmf.get_objects("broker")[0] + + # create a "link" + link_args = {"host":"a.fake.host.com", "port":9999, "durable":True, + "authMechanism":"PLAIN", "username":"guest", "password":"guest", + "transport":"tcp"} + result = qmf_broker_obj.create("link", "test-link", link_args, False) + self.assertEqual(result.status, 0, result) + link = qmf.get_objects("link")[0] + + # create bridge + bridge_args = {"link":"test-link", "src":"amq.direct", "dest":"amq.fanout", + "key":"my-key", "durable":True} + result = qmf_broker_obj.create("bridge", "test-bridge", bridge_args, False); + self.assertEqual(result.status, 0, result) + bridge = qmf.get_objects("bridge")[0] + + broker.terminate() + + # recover the link and bridge + broker = self.broker(store_args(), name="test_route") + qmf = Qmf(broker) + qmf_broker_obj = qmf.get_objects("broker")[0] + self.assertEqual(len(qmf.get_objects("link")), 1) + self.assertEqual(len(qmf.get_objects("bridge")), 1) + + + +class AlternateExchangePropertyTests(StoreTest): + """ + Test the persistence of the Alternate Exchange property for exchanges and queues. + """ + + def test_exchange(self): + """Exchange alternate exchange property persistence test""" + broker = self.broker(store_args(), name="test_exchange", expect=EXPECT_EXIT_OK) + qmf = Qmf(broker) + qmf.add_exchange("altExch", "direct", durable=True) # Serves as alternate exchange instance + qmf.add_exchange("testExch", "direct", durable=True, alt_exchange_name="altExch") + qmf.close() + broker.terminate() + + broker = self.broker(store_args(), name="test_exchange") + qmf = Qmf(broker) + try: + qmf.add_exchange("altExch", "direct", passive=True) + except Exception, error: + self.fail("Alternate exchange (\"altExch\") instance not recovered: %s" % error) + try: + qmf.add_exchange("testExch", "direct", passive=True) + except Exception, error: + self.fail("Test exchange (\"testExch\") instance not recovered: %s" % error) + self.assertTrue(qmf.query_exchange("testExch", alt_exchange_name = "altExch"), + "Alternate exchange property not found or is incorrect on exchange \"testExch\".") + qmf.close() + + def test_queue(self): + """Queue alternate exchange property persistexchangeNamece test""" + broker = self.broker(store_args(), name="test_queue", expect=EXPECT_EXIT_OK) + qmf = Qmf(broker) + qmf.add_exchange("altExch", "direct", durable=True) # Serves as alternate exchange instance + qmf.add_queue("testQueue", durable=True, alt_exchange_name="altExch") + qmf.close() + broker.terminate() + + broker = self.broker(store_args(), name="test_queue") + qmf = Qmf(broker) + try: + qmf.add_exchange("altExch", "direct", passive=True) + except Exception, error: + self.fail("Alternate exchange (\"altExch\") instance not recovered: %s" % error) + try: + qmf.add_queue("testQueue", passive=True) + except Exception, error: + self.fail("Test queue (\"testQueue\") instance not recovered: %s" % error) + self.assertTrue(qmf.query_queue("testQueue", alt_exchange_name = "altExch"), + "Alternate exchange property not found or is incorrect on queue \"testQueue\".") + qmf.close() + + +class RedeliveredTests(StoreTest): + """ + Test the behavior of the redelivered flag in the context of persistence + """ + + def test_broker_recovery(self): + """Test that the redelivered flag is set on messages after recovery of broker""" + broker = self.broker(store_args(), name="test_broker_recovery", expect=EXPECT_EXIT_OK) + msg_content = "xyz"*100 + msg = Message(msg_content, durable=True) + broker.send_message("testQueue", msg) + broker.terminate() + + broker = self.broker(store_args(), name="test_broker_recovery") + rcv_msg = broker.get_message("testQueue") + self.assertEqual(msg_content, rcv_msg.content) + self.assertTrue(rcv_msg.redelivered) + diff --git a/qpid/cpp/src/tests/legacystore/python_tests/resize.py b/qpid/cpp/src/tests/legacystore/python_tests/resize.py new file mode 100644 index 0000000000..e719b755da --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/python_tests/resize.py @@ -0,0 +1,170 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import glob +import os +import subprocess + +from brokertest import EXPECT_EXIT_OK +from qpid.datatypes import uuid4 +from store_test import StoreTest, store_args +from qpid.messaging import Message + +import qpid.messaging, brokertest +brokertest.qm = qpid.messaging # TODO aconway 2014-04-04: Tests fail with SWIG client. + +class ResizeTest(StoreTest): + + resize_tool = os.getenv("QPID_STORE_RESIZE_TOOL", "qpid-store-resize") + print resize_tool + def _resize_store(self, store_dir, queue_name, resize_num_files, resize_file_size, exp_fail): + for f in glob.glob(os.path.join(store_dir, "*")): + final_store_dir = os.path.join(f, queue_name) + p = subprocess.Popen([self.resize_tool, final_store_dir, "--num-jfiles", str(resize_num_files), + "--jfile-size-pgs", str(resize_file_size), "--quiet"], stdout = subprocess.PIPE, + stderr = subprocess.STDOUT) + res = p.wait() + err_found = False + try: + for l in p.stdout: + if exp_fail: + err_found = True + print "[Expected error]:", + print l, + finally: + p.stdout.close() + return res + + def _resize_test(self, queue_name, num_msgs, msg_size, resize_num_files, resize_file_size, init_num_files = 8, + init_file_size = 24, exp_fail = False, wait_time = None): + # Using a sender will force the creation of an empty persistent queue which is needed for some tests + broker = self.broker(store_args(), name="broker", expect=EXPECT_EXIT_OK, wait=wait_time) + ssn = broker.connect().session() + snd = ssn.sender("%s; {create:always, node:{durable:True}}" % queue_name) + + msgs = [] + for index in range(0, num_msgs): + msg = Message(self.make_message(index, msg_size), durable=True, id=uuid4(), correlation_id="msg-%04d"%index) + msgs.append(msg) + snd.send(msg) + broker.terminate() + + res = self._resize_store(os.path.join(self.dir, "broker", "rhm", "jrnl"), queue_name, resize_num_files, + resize_file_size, exp_fail) + if res != 0: + if exp_fail: + return + self.fail("ERROR: Resize operation failed with return code %d" % res) + elif exp_fail: + self.fail("ERROR: Resize operation succeeded, but a failure was expected") + + broker = self.broker(store_args(), name="broker") + self.check_messages(broker, queue_name, msgs, True) + + # TODO: Check the physical files to check number and size are as expected. + + +class SimpleTest(ResizeTest): + """ + Simple tests of the resize utility for resizing a journal to larger and smaller sizes. + """ + + def test_empty_store_same(self): + self._resize_test(queue_name = "empty_store_same", + num_msgs = 0, msg_size = 0, + init_num_files = 8, init_file_size = 24, + resize_num_files = 8, resize_file_size = 24) + + def test_empty_store_up(self): + self._resize_test(queue_name = "empty_store_up", + num_msgs = 0, msg_size = 0, + init_num_files = 8, init_file_size = 24, + resize_num_files = 16, resize_file_size = 48) + + def test_empty_store_down(self): + self._resize_test(queue_name = "empty_store_down", + num_msgs = 0, msg_size = 0, + init_num_files = 8, init_file_size = 24, + resize_num_files = 6, resize_file_size = 12) + +# TODO: Put into long tests, make sure there is > 128GB free disk space +# def test_empty_store_max(self): +# self._resize_test(queue_name = "empty_store_max", +# num_msgs = 0, msg_size = 0, +# init_num_files = 8, init_file_size = 24, +# resize_num_files = 64, resize_file_size = 32768, +# wait_time = 120) + + def test_empty_store_min(self): + self._resize_test(queue_name = "empty_store_min", + num_msgs = 0, msg_size = 0, + init_num_files = 8, init_file_size = 24, + resize_num_files = 4, resize_file_size = 1) + + def test_basic_up(self): + self._resize_test(queue_name = "basic_up", + num_msgs = 100, msg_size = 10000, + init_num_files = 8, init_file_size = 24, + resize_num_files = 16, resize_file_size = 48) + + def test_basic_down(self): + self._resize_test(queue_name = "basic_down", + num_msgs = 100, msg_size = 10000, + init_num_files = 8, init_file_size = 24, + resize_num_files = 4, resize_file_size = 15) + + def test_basic_low(self): + self._resize_test(queue_name = "basic_low", + num_msgs = 100, msg_size = 10000, + init_num_files = 8, init_file_size = 24, + resize_num_files = 4, resize_file_size = 4, + exp_fail = True) + + def test_basic_under(self): + self._resize_test(queue_name = "basic_under", + num_msgs = 100, msg_size = 10000, + init_num_files = 8, init_file_size = 24, + resize_num_files = 4, resize_file_size = 3, + exp_fail = True) + + def test_very_large_msg_up(self): + self._resize_test(queue_name = "very_large_msg_up", + num_msgs = 4, msg_size = 2000000, + init_num_files = 8, init_file_size = 24, + resize_num_files = 16, resize_file_size = 48) + + def test_very_large_msg_down(self): + self._resize_test(queue_name = "very_large_msg_down", + num_msgs = 4, msg_size = 2000000, + init_num_files = 16, init_file_size = 64, + resize_num_files = 16, resize_file_size = 48) + + def test_very_large_msg_low(self): + self._resize_test(queue_name = "very_large_msg_low", + num_msgs = 4, msg_size = 2000000, + init_num_files = 8, init_file_size = 24, + resize_num_files = 7, resize_file_size = 20, + exp_fail = True) + + def test_very_large_msg_under(self): + self._resize_test(queue_name = "very_large_msg_under", + num_msgs = 4, msg_size = 2000000, + init_num_files = 8, init_file_size = 24, + resize_num_files = 6, resize_file_size = 8, + exp_fail = True) diff --git a/qpid/cpp/src/tests/legacystore/python_tests/store_test.py b/qpid/cpp/src/tests/legacystore/python_tests/store_test.py new file mode 100644 index 0000000000..cc846aefd4 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/python_tests/store_test.py @@ -0,0 +1,417 @@ +# +# 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 re +from brokertest import BrokerTest +from qpid.messaging import Empty +from qmf.console import Session + +import qpid.messaging, brokertest +brokertest.qm = qpid.messaging # TODO aconway 2014-04-04: Tests fail with SWIG client. + + +def store_args(store_dir = None): + """Return the broker args necessary to load the async store""" + assert BrokerTest.store_lib + if store_dir == None: + return [] + return ["--store-dir", store_dir] + +class Qmf: + """ + QMF functions not yet available in the new QMF API. Remove this and replace with new API when it becomes available. + """ + def __init__(self, broker): + self.__session = Session() + self.__broker = self.__session.addBroker("amqp://localhost:%d"%broker.port()) + + def add_exchange(self, exchange_name, exchange_type, alt_exchange_name=None, passive=False, durable=False, + arguments = None): + """Add a new exchange""" + amqp_session = self.__broker.getAmqpSession() + if arguments == None: + arguments = {} + if alt_exchange_name: + amqp_session.exchange_declare(exchange=exchange_name, type=exchange_type, + alternate_exchange=alt_exchange_name, passive=passive, durable=durable, + arguments=arguments) + else: + amqp_session.exchange_declare(exchange=exchange_name, type=exchange_type, passive=passive, durable=durable, + arguments=arguments) + + def add_queue(self, queue_name, alt_exchange_name=None, passive=False, durable=False, arguments = None): + """Add a new queue""" + amqp_session = self.__broker.getAmqpSession() + if arguments == None: + arguments = {} + if alt_exchange_name: + amqp_session.queue_declare(queue_name, alternate_exchange=alt_exchange_name, passive=passive, + durable=durable, arguments=arguments) + else: + amqp_session.queue_declare(queue_name, passive=passive, durable=durable, arguments=arguments) + + def delete_queue(self, queue_name): + """Delete an existing queue""" + amqp_session = self.__broker.getAmqpSession() + amqp_session.queue_delete(queue_name) + + def _query(self, name, _class, package, alt_exchange_name=None): + """Qmf query function which can optionally look for the presence of an alternate exchange name""" + try: + obj_list = self.__session.getObjects(_class=_class, _package=package) + found = False + for obj in obj_list: + if obj.name == name: + found = True + if alt_exchange_name != None: + alt_exch_list = self.__session.getObjects(_objectId=obj.altExchange) + if len(alt_exch_list) == 0 or alt_exch_list[0].name != alt_exchange_name: + return False + break + return found + except Exception: + return False + + + def query_exchange(self, exchange_name, alt_exchange_name=None): + """Test for the presence of an exchange, and optionally whether it has an alternate exchange set to a known + value.""" + return self._query(exchange_name, "exchange", "org.apache.qpid.broker", alt_exchange_name) + + def query_queue(self, queue_name, alt_exchange_name=None): + """Test for the presence of an exchange, and optionally whether it has an alternate exchange set to a known + value.""" + return self._query(queue_name, "queue", "org.apache.qpid.broker", alt_exchange_name) + + def queue_message_count(self, queue_name): + """Query the number of messages on a queue""" + queue_list = self.__session.getObjects(_class="queue", _name=queue_name) + if len(queue_list): + return queue_list[0].msgDepth + + def queue_empty(self, queue_name): + """Check if a queue is empty (has no messages waiting)""" + return self.queue_message_count(queue_name) == 0 + + def get_objects(self, target_class, target_package="org.apache.qpid.broker"): + return self.__session.getObjects(_class=target_class, _package=target_package) + + + def close(self): + self.__session.delBroker(self.__broker) + self.__session = None + + +class StoreTest(BrokerTest): + """ + This subclass of BrokerTest adds some convenience test/check functions + """ + + def _chk_empty(self, queue, receiver): + """Check if a queue is empty (has no more messages)""" + try: + msg = receiver.fetch(timeout=0) + self.assert_(False, "Queue \"%s\" not empty: found message: %s" % (queue, msg)) + except Empty: + pass + + @staticmethod + def make_message(msg_count, msg_size): + """Make message content. Format: 'abcdef....' followed by 'msg-NNNN', where NNNN is the message count""" + msg = "msg-%04d" % msg_count + msg_len = len(msg) + buff = "" + if msg_size != None and msg_size > msg_len: + for index in range(0, msg_size - msg_len): + if index == msg_size - msg_len - 1: + buff += "-" + else: + buff += chr(ord('a') + (index % 26)) + return buff + msg + + # Functions for formatting address strings + + @staticmethod + def _fmt_csv(string_list, list_braces = None): + """Format a list using comma-separation. Braces are optionally added.""" + if len(string_list) == 0: + return "" + first = True + str_ = "" + if list_braces != None: + str_ += list_braces[0] + for string in string_list: + if string != None: + if first: + first = False + else: + str_ += ", " + str_ += string + if list_braces != None: + str_ += list_braces[1] + return str_ + + def _fmt_map(self, string_list): + """Format a map {l1, l2, l3, ...} from a string list. Each item in the list must be a formatted map + element('key:val').""" + return self._fmt_csv(string_list, list_braces="{}") + + def _fmt_list(self, string_list): + """Format a list [l1, l2, l3, ...] from a string list.""" + return self._fmt_csv(string_list, list_braces="[]") + + def addr_fmt(self, node_name, **kwargs): + """Generic AMQP to new address formatter. Takes common (but not all) AMQP options and formats an address + string.""" + # Get keyword args + node_subject = kwargs.get("node_subject") + create_policy = kwargs.get("create_policy") + delete_policy = kwargs.get("delete_policy") + assert_policy = kwargs.get("assert_policy") + mode = kwargs.get("mode") + link = kwargs.get("link", False) + link_name = kwargs.get("link_name") + node_type = kwargs.get("node_type") + durable = kwargs.get("durable", False) + link_reliability = kwargs.get("link_reliability") + x_declare_list = kwargs.get("x_declare_list", []) + x_bindings_list = kwargs.get("x_bindings_list", []) + x_subscribe_list = kwargs.get("x_subscribe_list", []) + + node_flag = not link and (node_type != None or durable or len(x_declare_list) > 0 or len(x_bindings_list) > 0) + link_flag = link and (link_name != None or durable or link_reliability != None or len(x_declare_list) > 0 or + len(x_bindings_list) > 0 or len(x_subscribe_list) > 0) + assert not (node_flag and link_flag) + + opt_str_list = [] + if create_policy != None: + opt_str_list.append("create: %s" % create_policy) + if delete_policy != None: + opt_str_list.append("delete: %s" % delete_policy) + if assert_policy != None: + opt_str_list.append("assert: %s" % assert_policy) + if mode != None: + opt_str_list.append("mode: %s" % mode) + if node_flag or link_flag: + node_str_list = [] + if link_name != None: + node_str_list.append("name: \"%s\"" % link_name) + if node_type != None: + node_str_list.append("type: %s" % node_type) + if durable: + node_str_list.append("durable: True") + if link_reliability != None: + node_str_list.append("reliability: %s" % link_reliability) + if len(x_declare_list) > 0: + node_str_list.append("x-declare: %s" % self._fmt_map(x_declare_list)) + if len(x_bindings_list) > 0: + node_str_list.append("x-bindings: %s" % self._fmt_list(x_bindings_list)) + if len(x_subscribe_list) > 0: + node_str_list.append("x-subscribe: %s" % self._fmt_map(x_subscribe_list)) + if node_flag: + opt_str_list.append("node: %s" % self._fmt_map(node_str_list)) + else: + opt_str_list.append("link: %s" % self._fmt_map(node_str_list)) + addr_str = node_name + if node_subject != None: + addr_str += "/%s" % node_subject + if len(opt_str_list) > 0: + addr_str += "; %s" % self._fmt_map(opt_str_list) + return addr_str + + def snd_addr(self, node_name, **kwargs): + """ Create a send (node) address""" + # Get keyword args + topic = kwargs.get("topic") + topic_flag = kwargs.get("topic_flag", False) + auto_create = kwargs.get("auto_create", True) + auto_delete = kwargs.get("auto_delete", False) + durable = kwargs.get("durable", False) + exclusive = kwargs.get("exclusive", False) + ftd_count = kwargs.get("ftd_count") + ftd_size = kwargs.get("ftd_size") + policy = kwargs.get("policy", "flow-to-disk") + exchage_type = kwargs.get("exchage_type") + + create_policy = None + if auto_create: + create_policy = "always" + delete_policy = None + if auto_delete: + delete_policy = "always" + node_type = None + if topic != None or topic_flag: + node_type = "topic" + x_declare_list = ["\"exclusive\": %s" % exclusive] + if ftd_count != None or ftd_size != None: + queue_policy = ["\'qpid.policy_type\': %s" % policy] + if ftd_count: + queue_policy.append("\'qpid.max_count\': %d" % ftd_count) + if ftd_size: + queue_policy.append("\'qpid.max_size\': %d" % ftd_size) + x_declare_list.append("arguments: %s" % self._fmt_map(queue_policy)) + if exchage_type != None: + x_declare_list.append("type: %s" % exchage_type) + + return self.addr_fmt(node_name, topic=topic, create_policy=create_policy, delete_policy=delete_policy, + node_type=node_type, durable=durable, x_declare_list=x_declare_list) + + def rcv_addr(self, node_name, **kwargs): + """ Create a receive (link) address""" + # Get keyword args + auto_create = kwargs.get("auto_create", True) + auto_delete = kwargs.get("auto_delete", False) + link_name = kwargs.get("link_name") + durable = kwargs.get("durable", False) + browse = kwargs.get("browse", False) + exclusive = kwargs.get("exclusive", False) + binding_list = kwargs.get("binding_list", []) + ftd_count = kwargs.get("ftd_count") + ftd_size = kwargs.get("ftd_size") + policy = kwargs.get("policy", "flow-to-disk") + + create_policy = None + if auto_create: + create_policy = "always" + delete_policy = None + if auto_delete: + delete_policy = "always" + mode = None + if browse: + mode = "browse" + x_declare_list = ["\"exclusive\": %s" % exclusive] + if ftd_count != None or ftd_size != None: + queue_policy = ["\'qpid.policy_type\': %s" % policy] + if ftd_count: + queue_policy.append("\'qpid.max_count\': %d" % ftd_count) + if ftd_size: + queue_policy.append("\'qpid.max_size\': %d" % ftd_size) + x_declare_list.append("arguments: %s" % self._fmt_map(queue_policy)) + x_bindings_list = [] + for binding in binding_list: + x_bindings_list.append("{exchange: %s, key: %s}" % binding) + if durable: reliability = 'at-least-once' + else: reliability = None + return self.addr_fmt(node_name, create_policy=create_policy, delete_policy=delete_policy, mode=mode, link=True, + link_name=link_name, durable=durable, x_declare_list=x_declare_list, + x_bindings_list=x_bindings_list, link_reliability=reliability) + + def check_message(self, broker, queue, exp_msg, transactional=False, empty=False, ack=True, browse=False): + """Check that a message is on a queue by dequeuing it and comparing it to the expected message""" + return self.check_messages(broker, queue, [exp_msg], transactional, empty, ack, browse) + + def check_messages(self, broker, queue, exp_msg_list, transactional=False, empty=False, ack=True, browse=False, + emtpy_flag=False): + """Check that messages is on a queue by dequeuing them and comparing them to the expected messages""" + if emtpy_flag: + num_msgs = 0 + else: + num_msgs = len(exp_msg_list) + ssn = broker.connect().session(transactional=transactional) + rcvr = ssn.receiver(self.rcv_addr(queue, browse=browse), capacity=num_msgs) + if num_msgs > 0: + try: + recieved_msg_list = [rcvr.fetch(timeout=0) for i in range(num_msgs)] + except Empty: + self.assert_(False, "Queue \"%s\" is empty, unable to retrieve expected message %d." % (queue, i)) + for i in range(0, len(recieved_msg_list)): + self.assertEqual(recieved_msg_list[i].content, exp_msg_list[i].content) + self.assertEqual(recieved_msg_list[i].correlation_id, exp_msg_list[i].correlation_id) + if empty: + self._chk_empty(queue, rcvr) + if ack: + ssn.acknowledge() + if transactional: + ssn.commit() + ssn.connection.close() + else: + if transactional: + ssn.commit() + return ssn + + + # Functions for finding strings in the broker log file (or other files) + + @staticmethod + def _read_file(file_name): + """Returns the content of file named file_name as a string""" + file_handle = file(file_name) + try: + return file_handle.read() + finally: + file_handle.close() + + def _get_hits(self, broker, search): + """Find all occurrences of the search in the broker log (eliminating possible duplicates from msgs on multiple + queues)""" + # TODO: Use sets when RHEL-4 is no longer supported + hits = [] + for hit in search.findall(self._read_file(broker.log)): + if hit not in hits: + hits.append(hit) + return hits + + def _reconsile_hits(self, broker, ftd_msgs, release_hits): + """Remove entries from list release_hits if they match the message id in ftd_msgs. Check for remaining + release_hits.""" + for msg in ftd_msgs: + found = False + for hit in release_hits: + if str(msg.id) in hit: + release_hits.remove(hit) + #print "Found %s in %s" % (msg.id, broker.log) + found = True + break + if not found: + self.assert_(False, "Unable to locate released message %s in log %s" % (msg.id, broker.log)) + if len(release_hits) > 0: + err = "Messages were unexpectedly released in log %s:\n" % broker.log + for hit in release_hits: + err += " %s\n" % hit + self.assert_(False, err) + + def check_msg_release(self, broker, ftd_msgs): + """ Check for 'Content released' messages in broker log for messages in ftd_msgs""" + hits = self._get_hits(broker, re.compile("debug Message id=\"[0-9a-f-]{36}\"; pid=0x[0-9a-f]+: " + "Content released$", re.MULTILINE)) + self._reconsile_hits(broker, ftd_msgs, hits) + + def check_msg_release_on_commit(self, broker, ftd_msgs): + """ Check for 'Content released on commit' messages in broker log for messages in ftd_msgs""" + hits = self._get_hits(broker, re.compile("debug Message id=\"[0-9a-f-]{36}\"; pid=0x[0-9a-f]+: " + "Content released on commit$", re.MULTILINE)) + self._reconsile_hits(broker, ftd_msgs, hits) + + def check_msg_release_on_recover(self, broker, ftd_msgs): + """ Check for 'Content released after recovery' messages in broker log for messages in ftd_msgs""" + hits = self._get_hits(broker, re.compile("debug Message id=\"[0-9a-f-]{36}\"; pid=0x[0-9a-f]+: " + "Content released after recovery$", re.MULTILINE)) + self._reconsile_hits(broker, ftd_msgs, hits) + + def check_msg_block(self, broker, ftd_msgs): + """Check for 'Content release blocked' messages in broker log for messages in ftd_msgs""" + hits = self._get_hits(broker, re.compile("debug Message id=\"[0-9a-f-]{36}\"; pid=0x[0-9a-f]+: " + "Content release blocked$", re.MULTILINE)) + self._reconsile_hits(broker, ftd_msgs, hits) + + def check_msg_block_on_commit(self, broker, ftd_msgs): + """Check for 'Content release blocked' messages in broker log for messages in ftd_msgs""" + hits = self._get_hits(broker, re.compile("debug Message id=\"[0-9a-f-]{36}\"; pid=0x[0-9a-f]+: " + "Content release blocked on commit$", re.MULTILINE)) + self._reconsile_hits(broker, ftd_msgs, hits) diff --git a/qpid/cpp/src/tests/legacystore/run_long_python_tests b/qpid/cpp/src/tests/legacystore/run_long_python_tests new file mode 100644 index 0000000000..be6380302c --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/run_long_python_tests @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +./run_python_tests LONG_TEST diff --git a/qpid/cpp/src/tests/legacystore/run_python_tests b/qpid/cpp/src/tests/legacystore/run_python_tests new file mode 100755 index 0000000000..c1d04a28a1 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/run_python_tests @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +source $QPID_TEST_COMMON + +ensure_python_tests + +#Add our directory to the python path +export PYTHONPATH=$srcdir/legacystore:$PYTHONPATH + +MODULENAME=python_tests + +echo "Running Python tests in module ${MODULENAME}..." + +QPID_PORT=${QPID_PORT:-5672} +FAILING=${FAILING:-/dev/null} +PYTHON_TESTS=${PYTHON_TESTS:-$*} + +OUTDIR=${MODULENAME}.tmp +rm -rf $OUTDIR + +# To debug a test, add the following options to the end of the following line: +# -v DEBUG -c qpid.messaging.io.ops [*.testName] +${QPID_PYTHON_TEST} -m ${MODULENAME} -I $FAILING -DOUTDIR=$OUTDIR \ + $PYTHON_TEST || exit 1 + diff --git a/qpid/cpp/src/tests/legacystore/run_short_python_tests b/qpid/cpp/src/tests/legacystore/run_short_python_tests new file mode 100644 index 0000000000..9b9e7c59be --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/run_short_python_tests @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +./run_python_tests SHORT_TEST diff --git a/qpid/cpp/src/tests/legacystore/system_test.sh b/qpid/cpp/src/tests/legacystore/system_test.sh new file mode 100644 index 0000000000..52cecbce8a --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/system_test.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +error() { echo $*; exit 1; } + +# Make sure $QPID_DIR contains what we need. +if ! test -d "$QPID_DIR" ; then + echo "WARNING: QPID_DIR is not set skipping system tests." + exit +fi +STORE_LIB=../lib/.libs/msgstore.so + +xml_spec=$QPID_DIR/specs/amqp.0-10-qpid-errata.stripped.xml +test -f $xml_spec || error "$xml_spec not found: invalid \$QPID_DIR ?" +export PYTHONPATH=$QPID_DIR/python:$QPID_DIR/extras/qmf/src/py:$QPID_DIR/tools/src/py + +echo "Using directory $TMP_DATA_DIR" + +fail=0 + +# Run the tests with a given set of flags +BROKER_OPTS="--no-module-dir --load-module=$STORE_LIB --data-dir=$TMP_DATA_DIR --auth=no --wcache-page-size 16" +run_tests() { + for p in `seq 1 8`; do + $abs_srcdir/start_broker "$@" ${BROKER_OPTS} || { echo "FAIL broker start"; return 1; } + python "$abs_srcdir/persistence.py" -s "$xml_spec" -b localhost:`cat qpidd.port` -p $p -r 3 || fail=1; + $abs_srcdir/stop_broker + done +} + +run_tests || fail=1 + +exit $fail diff --git a/qpid/cpp/src/tests/legacystore/unit_test.cpp b/qpid/cpp/src/tests/legacystore/unit_test.cpp new file mode 100644 index 0000000000..add80a6f91 --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/unit_test.cpp @@ -0,0 +1,28 @@ +/* + * + * 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. + * + */ + + +// Defines test_main function to link with actual unit test code. +#define BOOST_AUTO_TEST_MAIN // Boost 1.33 +#define BOOST_TEST_MAIN + +#include "unit_test.h" + diff --git a/qpid/cpp/src/tests/legacystore/unit_test.h b/qpid/cpp/src/tests/legacystore/unit_test.h new file mode 100644 index 0000000000..16b6ae2ffb --- /dev/null +++ b/qpid/cpp/src/tests/legacystore/unit_test.h @@ -0,0 +1,69 @@ +#ifndef QPIPD_TEST_UNIT_TEST_H_ +#define QPIPD_TEST_UNIT_TEST_H_ + +/* + * + * 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. + * + */ + + +// Workaround so we can build against boost 1.32, 1.33 and boost 1.34. +// Remove when we no longer need to support 1.32 or 1.33. + +#include <boost/version.hpp> + +#if (BOOST_VERSION < 103400) // v.1.33 and earlier +# include <boost/test/auto_unit_test.hpp> +#else // v.1.34 and later +# include <boost/test/unit_test.hpp> +#endif + +// Keep the test function for compilation but do not not register it. +// TODO aconway 2008-04-23: better workaround for expected failures. +// The following causes the test testUpdateTxState not to run at all. +# define QPID_AUTO_TEST_CASE_EXPECTED_FAILURES(test_name,n) \ + namespace { struct test_name { void test_method(); }; } \ + void test_name::test_method() +// The following runs the test testUpdateTxState, but it fails. +/*#define QPID_AUTO_TEST_CASE_EXPECTED_FAILURES(test_name,n) \ + namespace { struct test_name { void test_method(); }; } \ + BOOST_AUTO_TEST_CASE(name)*/ + +#if (BOOST_VERSION < 103300) // v.1.32 and earlier + +# define QPID_AUTO_TEST_SUITE(name) +# define QPID_AUTO_TEST_CASE(name) BOOST_AUTO_UNIT_TEST(name) +# define QPID_AUTO_TEST_SUITE_END() + +#elif (BOOST_VERSION < 103400) // v.1.33 + +// Note the trailing ';' +# define QPID_AUTO_TEST_SUITE(name) BOOST_AUTO_TEST_SUITE(name); +# define QPID_AUTO_TEST_CASE(name) BOOST_AUTO_TEST_CASE(name) +# define QPID_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END(); + +#else // v.1.34 and later + +# define QPID_AUTO_TEST_SUITE(name) BOOST_AUTO_TEST_SUITE(name) +# define QPID_AUTO_TEST_CASE(name) BOOST_AUTO_TEST_CASE(name) +# define QPID_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() + +#endif + +#endif /*!QPIPD_TEST_UNIT_TEST_H_*/ diff --git a/qpid/cpp/src/tests/linearstore/CMakeLists.txt b/qpid/cpp/src/tests/linearstore/CMakeLists.txt new file mode 100644 index 0000000000..bf6c164818 --- /dev/null +++ b/qpid/cpp/src/tests/linearstore/CMakeLists.txt @@ -0,0 +1,29 @@ +# +# 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. +# + +if(BUILD_LINEARSTORE AND BUILD_TESTING) + +message(STATUS "Building linearstore tests") + +set(test_wrap ${shell} ${CMAKE_SOURCE_DIR}/src/tests/run_test${test_script_suffix} -buildDir=${CMAKE_BINARY_DIR}) + +add_test (linearstore_python_tests ${test_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/run_python_tests${test_script_suffix}) + +endif (BUILD_LINEARSTORE AND BUILD_TESTING) + diff --git a/qpid/cpp/src/tests/linearstore/linearstoredirsetup.sh b/qpid/cpp/src/tests/linearstore/linearstoredirsetup.sh new file mode 100755 index 0000000000..ef39767e9b --- /dev/null +++ b/qpid/cpp/src/tests/linearstore/linearstoredirsetup.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# This script sets up a test directory which contains both +# recoverable and non-recoverable files and directories for +# the empty file pool (EFP). + +# NOTE: The following is based on typical development tree paths, not installed paths + +BASE_DIR=${HOME}/RedHat +STORE_DIR=${BASE_DIR} +PYTHON_TOOLS_DIR=${BASE_DIR}/qpid/tools/src/linearstore +export PYTHONPATH=${BASE_DIR}/qpid/python:${BASE_DIR}/qpid/extras/qmf/src/py:${BASE_DIR}/qpid/tools/src/py + +# Remove old dirs (if present) +rm -rf ${STORE_DIR}/qls +rm -rf ${STORE_DIR}/p002 +rm ${STORE_DIR}/p004 + +# Create new dir tree and links +mkdir ${STORE_DIR}/p002_ext +touch ${STORE_DIR}/p004_ext +mkdir ${STORE_DIR}/qls +mkdir ${STORE_DIR}/qls/p001 +touch ${STORE_DIR}/qls/p003 +ln -s ${STORE_DIR}/p002_ext ${STORE_DIR}/qls/p002 +ln -s ${STORE_DIR}/p004_ext ${STORE_DIR}/qls/p004 + +# Populate efp dirs with empty files +${PYTHON_TOOLS_DIR}/efptool.py $STORE_DIR/qls/ -a -p 1 -s 2048 -n 25 +${PYTHON_TOOLS_DIR}/efptool.py $STORE_DIR/qls/ -a -p 1 -s 512 -n 25 +${PYTHON_TOOLS_DIR}/efptool.py $STORE_DIR/qls/ -a -p 2 -s 2048 -n 25 + +# Show the result for information +${LINEARSTOREDIR}/tools/src/py/linearstore/efptool.py $STORE_DIR/qls/ -l +tree -la $STORE_DIR/qls + diff --git a/qpid/cpp/src/tests/linearstore/python_tests/__init__.py b/qpid/cpp/src/tests/linearstore/python_tests/__init__.py new file mode 100644 index 0000000000..1e59d403e4 --- /dev/null +++ b/qpid/cpp/src/tests/linearstore/python_tests/__init__.py @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Do not delete - marks this directory as a python package. + +from client_persistence import * + diff --git a/qpid/cpp/src/tests/linearstore/python_tests/client_persistence.py b/qpid/cpp/src/tests/linearstore/python_tests/client_persistence.py new file mode 100644 index 0000000000..9ff9480c4c --- /dev/null +++ b/qpid/cpp/src/tests/linearstore/python_tests/client_persistence.py @@ -0,0 +1,239 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +import os + +from brokertest import EXPECT_EXIT_OK +from store_test import StoreTest, Qmf, store_args +from qpid.messaging import * + +import qpid.messaging, brokertest +brokertest.qm = qpid.messaging # FIXME aconway 2014-04-04: Tests fail with SWIG client. + +class ExchangeQueueTests(StoreTest): + """ + Simple tests of the broker exchange and queue types + """ + + def test_direct_exchange(self): + """Test Direct exchange.""" + broker = self.broker(store_args(), name="test_direct_exchange", expect=EXPECT_EXIT_OK) + msg1 = Message("A_Message1", durable=True, correlation_id="Msg0001") + msg2 = Message("B_Message1", durable=True, correlation_id="Msg0002") + broker.send_message("a", msg1) + broker.send_message("b", msg2) + broker.terminate() + + broker = self.broker(store_args(), name="test_direct_exchange") + self.check_message(broker, "a", msg1, True) + self.check_message(broker, "b", msg2, True) + + def test_topic_exchange(self): + """Test Topic exchange.""" + broker = self.broker(store_args(), name="test_topic_exchange", expect=EXPECT_EXIT_OK) + ssn = broker.connect().session() + snd1 = ssn.sender("abc/key1; {create:always, node:{type:topic, durable:True}}") + snd2 = ssn.sender("abc/key2; {create:always, node:{type:topic, durable:True}}") + ssn.receiver("a; {create:always, link:{x-bindings:[{exchange:abc, key:key1}]}, node:{durable:True}}") + ssn.receiver("b; {create:always, link:{x-bindings:[{exchange:abc, key:key1}]}, node:{durable:True}}") + ssn.receiver("c; {create:always, link:{x-bindings:[{exchange:abc, key:key1}, " + "{exchange:abc, key: key2}]}, node:{durable:True}}") + ssn.receiver("d; {create:always, link:{x-bindings:[{exchange:abc, key:key2}]}, node:{durable:True}}") + ssn.receiver("e; {create:always, link:{x-bindings:[{exchange:abc, key:key2}]}, node:{durable:True}}") + msg1 = Message("Message1", durable=True, correlation_id="Msg0003") + snd1.send(msg1) + msg2 = Message("Message2", durable=True, correlation_id="Msg0004") + snd2.send(msg2) + broker.terminate() + + broker = self.broker(store_args(), name="test_topic_exchange") + self.check_message(broker, "a", msg1, True) + self.check_message(broker, "b", msg1, True) + self.check_messages(broker, "c", [msg1, msg2], True) + self.check_message(broker, "d", msg2, True) + self.check_message(broker, "e", msg2, True) + + + def test_legacy_lvq(self): + """Test legacy LVQ.""" + broker = self.broker(store_args(), name="test_lvq", expect=EXPECT_EXIT_OK) + ma1 = Message("A1", durable=True, correlation_id="Msg0005", properties={"qpid.LVQ_key":"A"}) + ma2 = Message("A2", durable=True, correlation_id="Msg0006", properties={"qpid.LVQ_key":"A"}) + mb1 = Message("B1", durable=True, correlation_id="Msg0007", properties={"qpid.LVQ_key":"B"}) + mb2 = Message("B2", durable=True, correlation_id="Msg0008", properties={"qpid.LVQ_key":"B"}) + mb3 = Message("B3", durable=True, correlation_id="Msg0009", properties={"qpid.LVQ_key":"B"}) + mc1 = Message("C1", durable=True, correlation_id="Msg0010", properties={"qpid.LVQ_key":"C"}) + broker.send_messages("lvq-test", [mb1, ma1, ma2, mb2, mb3, mc1], + xprops="arguments:{\"qpid.last_value_queue\":True}") + broker.terminate() + + broker = self.broker(store_args(), name="test_lvq", expect=EXPECT_EXIT_OK) + ssn = self.check_messages(broker, "lvq-test", [ma2, mb3, mc1], empty=True, ack=False) + # Add more messages while subscriber is active (no replacement): + ma3 = Message("A3", durable=True, correlation_id="Msg0011", properties={"qpid.LVQ_key":"A"}) + ma4 = Message("A4", durable=True, correlation_id="Msg0012", properties={"qpid.LVQ_key":"A"}) + mc2 = Message("C2", durable=True, correlation_id="Msg0013", properties={"qpid.LVQ_key":"C"}) + mc3 = Message("C3", durable=True, correlation_id="Msg0014", properties={"qpid.LVQ_key":"C"}) + mc4 = Message("C4", durable=True, correlation_id="Msg0015", properties={"qpid.LVQ_key":"C"}) + broker.send_messages("lvq-test", [mc2, mc3, ma3, ma4, mc4], session=ssn) + ssn.acknowledge() + broker.terminate() + + broker = self.broker(store_args(), name="test_lvq") + self.check_messages(broker, "lvq-test", [ma4, mc4], True) + + + def test_fanout_exchange(self): + """Test Fanout Exchange""" + broker = self.broker(store_args(), name="test_fanout_exchange", expect=EXPECT_EXIT_OK) + ssn = broker.connect().session() + snd = ssn.sender("TestFanoutExchange; {create: always, node: {type: topic, x-declare: {type: fanout}}}") + ssn.receiver("TestFanoutExchange; {link: {name: \"q1\", durable: True, reliability:at-least-once}}") + ssn.receiver("TestFanoutExchange; {link: {name: \"q2\", durable: True, reliability:at-least-once}}") + ssn.receiver("TestFanoutExchange; {link: {name: \"q3\", durable: True, reliability:at-least-once}}") + msg1 = Message("Msg1", durable=True, correlation_id="Msg0001") + snd.send(msg1) + msg2 = Message("Msg2", durable=True, correlation_id="Msg0002") + snd.send(msg2) + broker.terminate() + + broker = self.broker(store_args(), name="test_fanout_exchange") + self.check_messages(broker, "q1", [msg1, msg2], True) + self.check_messages(broker, "q2", [msg1, msg2], True) + self.check_messages(broker, "q3", [msg1, msg2], True) + + + def test_message_reject(self): + broker = self.broker(store_args(), name="test_message_reject", expect=EXPECT_EXIT_OK) + ssn = broker.connect().session() + snd = ssn.sender("tmr; {create:always, node:{type:queue, durable:True}}") + rcv = ssn.receiver("tmr; {create:always, node:{type:queue, durable:True}}") + m1 = Message("test_message_reject", durable=True, correlation_id="Msg0001") + snd.send(m1) + m2 = rcv.fetch() + ssn.acknowledge(message=m2, disposition=Disposition(REJECTED)) + broker.terminate() + + broker = self.broker(store_args(), name="test_message_reject") + qmf = Qmf(broker) + assert qmf.queue_message_count("tmr") == 0 + + + def test_route(self): + """ Test the recovery of a route (link and bridge objects.""" + broker = self.broker(store_args(), name="test_route", expect=EXPECT_EXIT_OK) + qmf = Qmf(broker) + qmf_broker_obj = qmf.get_objects("broker")[0] + + # create a "link" + link_args = {"host":"a.fake.host.com", "port":9999, "durable":True, + "authMechanism":"PLAIN", "username":"guest", "password":"guest", + "transport":"tcp"} + result = qmf_broker_obj.create("link", "test-link", link_args, False) + self.assertEqual(result.status, 0, result) + link = qmf.get_objects("link")[0] + + # create bridge + bridge_args = {"link":"test-link", "src":"amq.direct", "dest":"amq.fanout", + "key":"my-key", "durable":True} + result = qmf_broker_obj.create("bridge", "test-bridge", bridge_args, False); + self.assertEqual(result.status, 0, result) + bridge = qmf.get_objects("bridge")[0] + + broker.terminate() + + # recover the link and bridge + broker = self.broker(store_args(), name="test_route") + qmf = Qmf(broker) + qmf_broker_obj = qmf.get_objects("broker")[0] + self.assertEqual(len(qmf.get_objects("link")), 1) + self.assertEqual(len(qmf.get_objects("bridge")), 1) + + + +class AlternateExchangePropertyTests(StoreTest): + """ + Test the persistence of the Alternate Exchange property for exchanges and queues. + """ + + def test_exchange(self): + """Exchange alternate exchange property persistence test""" + broker = self.broker(store_args(), name="test_exchange", expect=EXPECT_EXIT_OK) + qmf = Qmf(broker) + qmf.add_exchange("altExch", "direct", durable=True) # Serves as alternate exchange instance + qmf.add_exchange("testExch", "direct", durable=True, alt_exchange_name="altExch") + qmf.close() + broker.terminate() + + broker = self.broker(store_args(), name="test_exchange") + qmf = Qmf(broker) + try: + qmf.add_exchange("altExch", "direct", passive=True) + except Exception, error: + self.fail("Alternate exchange (\"altExch\") instance not recovered: %s" % error) + try: + qmf.add_exchange("testExch", "direct", passive=True) + except Exception, error: + self.fail("Test exchange (\"testExch\") instance not recovered: %s" % error) + self.assertTrue(qmf.query_exchange("testExch", alt_exchange_name = "altExch"), + "Alternate exchange property not found or is incorrect on exchange \"testExch\".") + qmf.close() + + def test_queue(self): + """Queue alternate exchange property persistexchangeNamece test""" + broker = self.broker(store_args(), name="test_queue", expect=EXPECT_EXIT_OK) + qmf = Qmf(broker) + qmf.add_exchange("altExch", "direct", durable=True) # Serves as alternate exchange instance + qmf.add_queue("testQueue", durable=True, alt_exchange_name="altExch") + qmf.close() + broker.terminate() + + broker = self.broker(store_args(), name="test_queue") + qmf = Qmf(broker) + try: + qmf.add_exchange("altExch", "direct", passive=True) + except Exception, error: + self.fail("Alternate exchange (\"altExch\") instance not recovered: %s" % error) + try: + qmf.add_queue("testQueue", passive=True) + except Exception, error: + self.fail("Test queue (\"testQueue\") instance not recovered: %s" % error) + self.assertTrue(qmf.query_queue("testQueue", alt_exchange_name = "altExch"), + "Alternate exchange property not found or is incorrect on queue \"testQueue\".") + qmf.close() + + +class RedeliveredTests(StoreTest): + """ + Test the behavior of the redelivered flag in the context of persistence + """ + + def test_broker_recovery(self): + """Test that the redelivered flag is set on messages after recovery of broker""" + broker = self.broker(store_args(), name="test_broker_recovery", expect=EXPECT_EXIT_OK) + msg_content = "xyz"*100 + msg = Message(msg_content, durable=True) + broker.send_message("testQueue", msg) + broker.terminate() + + broker = self.broker(store_args(), name="test_broker_recovery") + rcv_msg = broker.get_message("testQueue") + self.assertEqual(msg_content, rcv_msg.content) + self.assertTrue(rcv_msg.redelivered) + diff --git a/qpid/cpp/src/tests/linearstore/python_tests/store_test.py b/qpid/cpp/src/tests/linearstore/python_tests/store_test.py new file mode 100644 index 0000000000..cc846aefd4 --- /dev/null +++ b/qpid/cpp/src/tests/linearstore/python_tests/store_test.py @@ -0,0 +1,417 @@ +# +# 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 re +from brokertest import BrokerTest +from qpid.messaging import Empty +from qmf.console import Session + +import qpid.messaging, brokertest +brokertest.qm = qpid.messaging # TODO aconway 2014-04-04: Tests fail with SWIG client. + + +def store_args(store_dir = None): + """Return the broker args necessary to load the async store""" + assert BrokerTest.store_lib + if store_dir == None: + return [] + return ["--store-dir", store_dir] + +class Qmf: + """ + QMF functions not yet available in the new QMF API. Remove this and replace with new API when it becomes available. + """ + def __init__(self, broker): + self.__session = Session() + self.__broker = self.__session.addBroker("amqp://localhost:%d"%broker.port()) + + def add_exchange(self, exchange_name, exchange_type, alt_exchange_name=None, passive=False, durable=False, + arguments = None): + """Add a new exchange""" + amqp_session = self.__broker.getAmqpSession() + if arguments == None: + arguments = {} + if alt_exchange_name: + amqp_session.exchange_declare(exchange=exchange_name, type=exchange_type, + alternate_exchange=alt_exchange_name, passive=passive, durable=durable, + arguments=arguments) + else: + amqp_session.exchange_declare(exchange=exchange_name, type=exchange_type, passive=passive, durable=durable, + arguments=arguments) + + def add_queue(self, queue_name, alt_exchange_name=None, passive=False, durable=False, arguments = None): + """Add a new queue""" + amqp_session = self.__broker.getAmqpSession() + if arguments == None: + arguments = {} + if alt_exchange_name: + amqp_session.queue_declare(queue_name, alternate_exchange=alt_exchange_name, passive=passive, + durable=durable, arguments=arguments) + else: + amqp_session.queue_declare(queue_name, passive=passive, durable=durable, arguments=arguments) + + def delete_queue(self, queue_name): + """Delete an existing queue""" + amqp_session = self.__broker.getAmqpSession() + amqp_session.queue_delete(queue_name) + + def _query(self, name, _class, package, alt_exchange_name=None): + """Qmf query function which can optionally look for the presence of an alternate exchange name""" + try: + obj_list = self.__session.getObjects(_class=_class, _package=package) + found = False + for obj in obj_list: + if obj.name == name: + found = True + if alt_exchange_name != None: + alt_exch_list = self.__session.getObjects(_objectId=obj.altExchange) + if len(alt_exch_list) == 0 or alt_exch_list[0].name != alt_exchange_name: + return False + break + return found + except Exception: + return False + + + def query_exchange(self, exchange_name, alt_exchange_name=None): + """Test for the presence of an exchange, and optionally whether it has an alternate exchange set to a known + value.""" + return self._query(exchange_name, "exchange", "org.apache.qpid.broker", alt_exchange_name) + + def query_queue(self, queue_name, alt_exchange_name=None): + """Test for the presence of an exchange, and optionally whether it has an alternate exchange set to a known + value.""" + return self._query(queue_name, "queue", "org.apache.qpid.broker", alt_exchange_name) + + def queue_message_count(self, queue_name): + """Query the number of messages on a queue""" + queue_list = self.__session.getObjects(_class="queue", _name=queue_name) + if len(queue_list): + return queue_list[0].msgDepth + + def queue_empty(self, queue_name): + """Check if a queue is empty (has no messages waiting)""" + return self.queue_message_count(queue_name) == 0 + + def get_objects(self, target_class, target_package="org.apache.qpid.broker"): + return self.__session.getObjects(_class=target_class, _package=target_package) + + + def close(self): + self.__session.delBroker(self.__broker) + self.__session = None + + +class StoreTest(BrokerTest): + """ + This subclass of BrokerTest adds some convenience test/check functions + """ + + def _chk_empty(self, queue, receiver): + """Check if a queue is empty (has no more messages)""" + try: + msg = receiver.fetch(timeout=0) + self.assert_(False, "Queue \"%s\" not empty: found message: %s" % (queue, msg)) + except Empty: + pass + + @staticmethod + def make_message(msg_count, msg_size): + """Make message content. Format: 'abcdef....' followed by 'msg-NNNN', where NNNN is the message count""" + msg = "msg-%04d" % msg_count + msg_len = len(msg) + buff = "" + if msg_size != None and msg_size > msg_len: + for index in range(0, msg_size - msg_len): + if index == msg_size - msg_len - 1: + buff += "-" + else: + buff += chr(ord('a') + (index % 26)) + return buff + msg + + # Functions for formatting address strings + + @staticmethod + def _fmt_csv(string_list, list_braces = None): + """Format a list using comma-separation. Braces are optionally added.""" + if len(string_list) == 0: + return "" + first = True + str_ = "" + if list_braces != None: + str_ += list_braces[0] + for string in string_list: + if string != None: + if first: + first = False + else: + str_ += ", " + str_ += string + if list_braces != None: + str_ += list_braces[1] + return str_ + + def _fmt_map(self, string_list): + """Format a map {l1, l2, l3, ...} from a string list. Each item in the list must be a formatted map + element('key:val').""" + return self._fmt_csv(string_list, list_braces="{}") + + def _fmt_list(self, string_list): + """Format a list [l1, l2, l3, ...] from a string list.""" + return self._fmt_csv(string_list, list_braces="[]") + + def addr_fmt(self, node_name, **kwargs): + """Generic AMQP to new address formatter. Takes common (but not all) AMQP options and formats an address + string.""" + # Get keyword args + node_subject = kwargs.get("node_subject") + create_policy = kwargs.get("create_policy") + delete_policy = kwargs.get("delete_policy") + assert_policy = kwargs.get("assert_policy") + mode = kwargs.get("mode") + link = kwargs.get("link", False) + link_name = kwargs.get("link_name") + node_type = kwargs.get("node_type") + durable = kwargs.get("durable", False) + link_reliability = kwargs.get("link_reliability") + x_declare_list = kwargs.get("x_declare_list", []) + x_bindings_list = kwargs.get("x_bindings_list", []) + x_subscribe_list = kwargs.get("x_subscribe_list", []) + + node_flag = not link and (node_type != None or durable or len(x_declare_list) > 0 or len(x_bindings_list) > 0) + link_flag = link and (link_name != None or durable or link_reliability != None or len(x_declare_list) > 0 or + len(x_bindings_list) > 0 or len(x_subscribe_list) > 0) + assert not (node_flag and link_flag) + + opt_str_list = [] + if create_policy != None: + opt_str_list.append("create: %s" % create_policy) + if delete_policy != None: + opt_str_list.append("delete: %s" % delete_policy) + if assert_policy != None: + opt_str_list.append("assert: %s" % assert_policy) + if mode != None: + opt_str_list.append("mode: %s" % mode) + if node_flag or link_flag: + node_str_list = [] + if link_name != None: + node_str_list.append("name: \"%s\"" % link_name) + if node_type != None: + node_str_list.append("type: %s" % node_type) + if durable: + node_str_list.append("durable: True") + if link_reliability != None: + node_str_list.append("reliability: %s" % link_reliability) + if len(x_declare_list) > 0: + node_str_list.append("x-declare: %s" % self._fmt_map(x_declare_list)) + if len(x_bindings_list) > 0: + node_str_list.append("x-bindings: %s" % self._fmt_list(x_bindings_list)) + if len(x_subscribe_list) > 0: + node_str_list.append("x-subscribe: %s" % self._fmt_map(x_subscribe_list)) + if node_flag: + opt_str_list.append("node: %s" % self._fmt_map(node_str_list)) + else: + opt_str_list.append("link: %s" % self._fmt_map(node_str_list)) + addr_str = node_name + if node_subject != None: + addr_str += "/%s" % node_subject + if len(opt_str_list) > 0: + addr_str += "; %s" % self._fmt_map(opt_str_list) + return addr_str + + def snd_addr(self, node_name, **kwargs): + """ Create a send (node) address""" + # Get keyword args + topic = kwargs.get("topic") + topic_flag = kwargs.get("topic_flag", False) + auto_create = kwargs.get("auto_create", True) + auto_delete = kwargs.get("auto_delete", False) + durable = kwargs.get("durable", False) + exclusive = kwargs.get("exclusive", False) + ftd_count = kwargs.get("ftd_count") + ftd_size = kwargs.get("ftd_size") + policy = kwargs.get("policy", "flow-to-disk") + exchage_type = kwargs.get("exchage_type") + + create_policy = None + if auto_create: + create_policy = "always" + delete_policy = None + if auto_delete: + delete_policy = "always" + node_type = None + if topic != None or topic_flag: + node_type = "topic" + x_declare_list = ["\"exclusive\": %s" % exclusive] + if ftd_count != None or ftd_size != None: + queue_policy = ["\'qpid.policy_type\': %s" % policy] + if ftd_count: + queue_policy.append("\'qpid.max_count\': %d" % ftd_count) + if ftd_size: + queue_policy.append("\'qpid.max_size\': %d" % ftd_size) + x_declare_list.append("arguments: %s" % self._fmt_map(queue_policy)) + if exchage_type != None: + x_declare_list.append("type: %s" % exchage_type) + + return self.addr_fmt(node_name, topic=topic, create_policy=create_policy, delete_policy=delete_policy, + node_type=node_type, durable=durable, x_declare_list=x_declare_list) + + def rcv_addr(self, node_name, **kwargs): + """ Create a receive (link) address""" + # Get keyword args + auto_create = kwargs.get("auto_create", True) + auto_delete = kwargs.get("auto_delete", False) + link_name = kwargs.get("link_name") + durable = kwargs.get("durable", False) + browse = kwargs.get("browse", False) + exclusive = kwargs.get("exclusive", False) + binding_list = kwargs.get("binding_list", []) + ftd_count = kwargs.get("ftd_count") + ftd_size = kwargs.get("ftd_size") + policy = kwargs.get("policy", "flow-to-disk") + + create_policy = None + if auto_create: + create_policy = "always" + delete_policy = None + if auto_delete: + delete_policy = "always" + mode = None + if browse: + mode = "browse" + x_declare_list = ["\"exclusive\": %s" % exclusive] + if ftd_count != None or ftd_size != None: + queue_policy = ["\'qpid.policy_type\': %s" % policy] + if ftd_count: + queue_policy.append("\'qpid.max_count\': %d" % ftd_count) + if ftd_size: + queue_policy.append("\'qpid.max_size\': %d" % ftd_size) + x_declare_list.append("arguments: %s" % self._fmt_map(queue_policy)) + x_bindings_list = [] + for binding in binding_list: + x_bindings_list.append("{exchange: %s, key: %s}" % binding) + if durable: reliability = 'at-least-once' + else: reliability = None + return self.addr_fmt(node_name, create_policy=create_policy, delete_policy=delete_policy, mode=mode, link=True, + link_name=link_name, durable=durable, x_declare_list=x_declare_list, + x_bindings_list=x_bindings_list, link_reliability=reliability) + + def check_message(self, broker, queue, exp_msg, transactional=False, empty=False, ack=True, browse=False): + """Check that a message is on a queue by dequeuing it and comparing it to the expected message""" + return self.check_messages(broker, queue, [exp_msg], transactional, empty, ack, browse) + + def check_messages(self, broker, queue, exp_msg_list, transactional=False, empty=False, ack=True, browse=False, + emtpy_flag=False): + """Check that messages is on a queue by dequeuing them and comparing them to the expected messages""" + if emtpy_flag: + num_msgs = 0 + else: + num_msgs = len(exp_msg_list) + ssn = broker.connect().session(transactional=transactional) + rcvr = ssn.receiver(self.rcv_addr(queue, browse=browse), capacity=num_msgs) + if num_msgs > 0: + try: + recieved_msg_list = [rcvr.fetch(timeout=0) for i in range(num_msgs)] + except Empty: + self.assert_(False, "Queue \"%s\" is empty, unable to retrieve expected message %d." % (queue, i)) + for i in range(0, len(recieved_msg_list)): + self.assertEqual(recieved_msg_list[i].content, exp_msg_list[i].content) + self.assertEqual(recieved_msg_list[i].correlation_id, exp_msg_list[i].correlation_id) + if empty: + self._chk_empty(queue, rcvr) + if ack: + ssn.acknowledge() + if transactional: + ssn.commit() + ssn.connection.close() + else: + if transactional: + ssn.commit() + return ssn + + + # Functions for finding strings in the broker log file (or other files) + + @staticmethod + def _read_file(file_name): + """Returns the content of file named file_name as a string""" + file_handle = file(file_name) + try: + return file_handle.read() + finally: + file_handle.close() + + def _get_hits(self, broker, search): + """Find all occurrences of the search in the broker log (eliminating possible duplicates from msgs on multiple + queues)""" + # TODO: Use sets when RHEL-4 is no longer supported + hits = [] + for hit in search.findall(self._read_file(broker.log)): + if hit not in hits: + hits.append(hit) + return hits + + def _reconsile_hits(self, broker, ftd_msgs, release_hits): + """Remove entries from list release_hits if they match the message id in ftd_msgs. Check for remaining + release_hits.""" + for msg in ftd_msgs: + found = False + for hit in release_hits: + if str(msg.id) in hit: + release_hits.remove(hit) + #print "Found %s in %s" % (msg.id, broker.log) + found = True + break + if not found: + self.assert_(False, "Unable to locate released message %s in log %s" % (msg.id, broker.log)) + if len(release_hits) > 0: + err = "Messages were unexpectedly released in log %s:\n" % broker.log + for hit in release_hits: + err += " %s\n" % hit + self.assert_(False, err) + + def check_msg_release(self, broker, ftd_msgs): + """ Check for 'Content released' messages in broker log for messages in ftd_msgs""" + hits = self._get_hits(broker, re.compile("debug Message id=\"[0-9a-f-]{36}\"; pid=0x[0-9a-f]+: " + "Content released$", re.MULTILINE)) + self._reconsile_hits(broker, ftd_msgs, hits) + + def check_msg_release_on_commit(self, broker, ftd_msgs): + """ Check for 'Content released on commit' messages in broker log for messages in ftd_msgs""" + hits = self._get_hits(broker, re.compile("debug Message id=\"[0-9a-f-]{36}\"; pid=0x[0-9a-f]+: " + "Content released on commit$", re.MULTILINE)) + self._reconsile_hits(broker, ftd_msgs, hits) + + def check_msg_release_on_recover(self, broker, ftd_msgs): + """ Check for 'Content released after recovery' messages in broker log for messages in ftd_msgs""" + hits = self._get_hits(broker, re.compile("debug Message id=\"[0-9a-f-]{36}\"; pid=0x[0-9a-f]+: " + "Content released after recovery$", re.MULTILINE)) + self._reconsile_hits(broker, ftd_msgs, hits) + + def check_msg_block(self, broker, ftd_msgs): + """Check for 'Content release blocked' messages in broker log for messages in ftd_msgs""" + hits = self._get_hits(broker, re.compile("debug Message id=\"[0-9a-f-]{36}\"; pid=0x[0-9a-f]+: " + "Content release blocked$", re.MULTILINE)) + self._reconsile_hits(broker, ftd_msgs, hits) + + def check_msg_block_on_commit(self, broker, ftd_msgs): + """Check for 'Content release blocked' messages in broker log for messages in ftd_msgs""" + hits = self._get_hits(broker, re.compile("debug Message id=\"[0-9a-f-]{36}\"; pid=0x[0-9a-f]+: " + "Content release blocked on commit$", re.MULTILINE)) + self._reconsile_hits(broker, ftd_msgs, hits) diff --git a/qpid/cpp/src/tests/linearstore/run_long_python_tests b/qpid/cpp/src/tests/linearstore/run_long_python_tests new file mode 100644 index 0000000000..be6380302c --- /dev/null +++ b/qpid/cpp/src/tests/linearstore/run_long_python_tests @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +./run_python_tests LONG_TEST diff --git a/qpid/cpp/src/tests/linearstore/run_python_tests b/qpid/cpp/src/tests/linearstore/run_python_tests new file mode 100755 index 0000000000..4ff212a71c --- /dev/null +++ b/qpid/cpp/src/tests/linearstore/run_python_tests @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +source ${QPID_TEST_COMMON} + +ensure_python_tests + +#Add our directory to the python path +export PYTHONPATH=$srcdir/linearstore:${PYTHONPATH} + +MODULENAME=python_tests + +echo "Running Python tests in module ${MODULENAME}..." + +QPID_PORT=${QPID_PORT:-5672} +FAILING=${FAILING:-/dev/null} +PYTHON_TESTS=${PYTHON_TESTS:-$*} + +OUTDIR=${MODULENAME}.tmp +rm -rf ${OUTDIR} + +# To debug a test, add the following options to the end of the following line: +# -v DEBUG -c qpid.messaging.io.ops [*.testName] +${QPID_PYTHON_TEST} -m ${MODULENAME} -I ${FAILING} -DOUTDIR=${OUTDIR} ${PYTHON_TEST} || exit 1 + diff --git a/qpid/cpp/src/tests/linearstore/run_short_python_tests b/qpid/cpp/src/tests/linearstore/run_short_python_tests new file mode 100644 index 0000000000..9b9e7c59be --- /dev/null +++ b/qpid/cpp/src/tests/linearstore/run_short_python_tests @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +./run_python_tests SHORT_TEST diff --git a/qpid/cpp/src/tests/linearstore/tx-test-soak.sh b/qpid/cpp/src/tests/linearstore/tx-test-soak.sh new file mode 100755 index 0000000000..7d5581961f --- /dev/null +++ b/qpid/cpp/src/tests/linearstore/tx-test-soak.sh @@ -0,0 +1,275 @@ +#! /bin/bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# tx-test-soak +# +# Basic test methodology: +# 1. Start broker +# 2. Run qpid-txtest against broker using randomly generated parameters +# 3. After some time, kill the broker using SIGKILL +# 4. Restart broker, recover messages +# 5. Run qpid-txtest against broker in check mode, which checks that all expected messages are present. +# 6. Wash, rinse, repeat... The number of runs is determined by ${NUM_RUNS} + +# NOTE: The following is based on typical development tree paths, not installed paths + +NUM_RUNS=1000 +BASE_DIR=${HOME}/RedHat +CMAKE_BUILD_DIR=${BASE_DIR}/q.cm + +# Infrequently adjusted +RESULT_BASE_DIR_PREFIX=${BASE_DIR}/results.tx-test-soak +RECOVER_TIME_PER_QUEUE=1 +STORE_MODULE="linearstore.so" +BROKER_LOG_LEVEL="info+" +BROKER_MANAGEMENT="no" # "no" or "yes" +TRUNCATE_INTERVAL=10 +MAX_DISK_PERC_USED=90 + +# Constants (don't adjust these) +export BASE_DIR +RELATIVE_BASE_DIR=`python -c "import os,os.path; print os.path.relpath(os.environ['BASE_DIR'], os.environ['PWD'])"` +export PYTHONPATH=${BASE_DIR}/qpid/python:${BASE_DIR}/qpid/extras/qmf/src/py:${BASE_DIR}/qpid/tools/src/py +LOG_FILE_NAME=log.txt +QPIDD_FN=qpidd +QPIDD=${CMAKE_BUILD_DIR}/src/${QPIDD_FN} +TXTEST_FN=qpid-txtest +TXTEST=${CMAKE_BUILD_DIR}/src/tests/${TXTEST_FN} +ANALYZE_FN=qpid_qls_analyze.py +ANALYZE=${BASE_DIR}/qpid/tools/src/py/${ANALYZE_FN} +ANALYZE_ARGS="--efp --show-recs --stats" +QPIDD_BASE_ARGS="--load-module ${STORE_MODULE} -m ${BROKER_MANAGEMENT} --auth no --default-flow-stop-threshold 0 --default-flow-resume-threshold 0 --default-queue-limit 0 --store-dir ${BASE_DIR} --log-enable ${BROKER_LOG_LEVEL} --log-to-stderr no --log-to-stdout no" +TXTEST_INIT_STR="--init yes --transfer no --check no" +TXTEST_RUN_STR="--init no --transfer yes --check no" +TXTEST_CHK_STR="--init no --transfer no --check yes" +SUCCESS_MSG="All expected messages were retrieved." +TIMESTAMP_FORMAT="+%Y-%m-%d_%H:%M:%S" +ANSI_RED="\e[1;31m" +ANSI_NONE="\e[0m" +DEFAULT_EFP_DIR=2048k +DEFAULT_EFP_SIZE=2101248 +SIG_KILL=-9 +SIG_TERM=-15 + +# Creates a random number into the variable named in string $1 in the range [$2..$3] (both inclusive). +# $1: variable name as string to which random value is assigned +# $2: minimum inclusive range of random number +# $3: maximum inclusive range of random number +get_random() { + eval $1=`python -S -c "import random; print random.randint($2,$3)"` +} + +# Uses anon-uniform distribution to set a random message size. +# Most messages must be small (0 - 1k), but we need a few medium (10k) and large (100k) ones also. +# Sets message size into var ${MSG_SIZE} +set_message_size() { + local key=0 + get_random "key" 1 10 + if (( "${key}" == "10" )); then # 1 out of 10 - very large + get_random "MSG_SIZE" 100000 1000000 + FILE_SIZE_MULTIPLIER=3 + elif (( "${key}" >= "8" )); then # 2 out of 10 - large + get_random "MSG_SIZE" 10000 100000 + FILE_SIZE_MULTIPLIER=2 + elif (( "${key}" >= "6" )); then # 2 out of 10 - medium + get_random "MSG_SIZE" 1000 10000 + FILE_SIZE_MULTIPLIER=1 + else # 5 out of 10 - small + get_random "MSG_SIZE" 10 1000 + FILE_SIZE_MULTIPLIER=1 + fi +} + +# Start or restart broker +# $1: Log suffix: either "A" or "B". If "A", broker is started with truncation, otherwise broker is restarted with recovery. +# $2: Truncate flag - only used if Log suffix is "A": if true, then truncate store +# The PID of the broker is returned in ${QPIDD_PID} +start_broker() { + local truncate_val + local truncate_str + if [[ "$1" == "A" ]]; then + if [[ $2 == true ]]; then + truncate_val="yes" + truncate_str="(Store truncated)" + if [[ -e ${BASE_DIR}/qls/p001/efp/${DEFAULT_EFP_DIR} ]]; then + for f in ${BASE_DIR}/qls/p001/efp/${DEFAULT_EFP_DIR}/*; do + local filesize=`stat -c%s "${f}"` + if (( ${filesize} != ${DEFAULT_EFP_SIZE} )); then + rm ${f} + fi + done + fi + else + truncate_val="no" + fi + else + truncate_val="no" + fi + echo "${QPIDD} ${QPIDD_BASE_ARGS} --truncate ${truncate_val} --log-to-file ${RESULT_DIR}/qpidd.$1.log &" > ${RESULT_DIR}/qpidd.$1.cmd + ${QPIDD} ${QPIDD_BASE_ARGS} --truncate ${truncate_val} --log-to-file ${RESULT_DIR}/qpidd.$1.log & + QPIDD_PID=$! + echo "Broker PID=${QPIDD_PID} ${truncate_str}" | tee -a ${LOG_FILE} +} + +# Start or evaluate results of transaction test client +# $1: Log suffix flag: either "A" or "B". If "A", client is started in test mode, otherwise client evaluates recovery. +start_tx_test() { + local tx_test_params="--messages-per-tx ${MSGS_PER_TX} --tx-count 1000000 --total-messages ${TOT_MSGS} --size ${MSG_SIZE} --queues ${NUM_QUEUES}" + if [[ "$1" == "A" ]]; then + # Run in background + echo "${TXTEST##*/} parameters: ${tx_test_params}" | tee -a ${LOG_FILE} + echo "${TXTEST} ${tx_test_params} ${TXTEST_INIT_STR} &> ${RESULT_DIR}/txtest.$1.log" > ${RESULT_DIR}/txtest.$1.cmd + ${TXTEST} ${tx_test_params} ${TXTEST_INIT_STR} &> ${RESULT_DIR}/txtest.$1.log + echo "${TXTEST} ${tx_test_params} ${TXTEST_RUN_STR} &> ${RESULT_DIR}/txtest.$1.log &" >> ${RESULT_DIR}/txtest.$1.cmd + ${TXTEST} ${tx_test_params} ${TXTEST_RUN_STR} &> ${RESULT_DIR}/txtest.$1.log & + else + # Run in foreground + #echo "${TXTEST##*/} ${tx_test_params} ${TXTEST_CHK_STR}" | tee -a ${LOG_FILE} + echo "${TXTEST} ${tx_test_params} ${TXTEST_CHK_STR} &> ${RESULT_DIR}/txtest.$1.log" > ${RESULT_DIR}/txtest.$1.cmd + ${TXTEST} ${tx_test_params} ${TXTEST_CHK_STR} &> ${RESULT_DIR}/txtest.$1.log + fi +} + +# Search for the presence of core.* files, move them into the current result directory and run gdb against them. +# No params +process_core_files() { + ls core.* &> /dev/null + if (( "$?" == "0" )); then + for cf in core.*; do + gdb --batch --quiet -ex "thread apply all bt" -ex "quit" ${QPIDD} ${cf} &> ${RESULT_DIR}/${cf##*/}.gdb.txt + gdb --batch --quiet -ex "thread apply all bt full" -ex "quit" ${QPIDD} ${cf} &> ${RESULT_DIR}/${cf##*/}.gdb-full.txt + cat ${RESULT_DIR}/${cf##*/}.gdb.txt + mv ${cf} ${RESULT_DIR}/ + echo "Core file ${cf##*/} found and recovered" + done + fi +} + +# Kill a process quietly +# $1: Signal +# $2: PID +kill_process() { + kill ${1} ${2} &>> ${LOG_FILE} + wait ${2} &>> ${LOG_FILE} +} + +# Check that test can run: No other copy of qpidd running, enough disk space +check_ready_to_run() { + # Check no copy of qpidd is running + PID=`pgrep ${QPIDD_FN}` + if [[ "$?" == "0" ]]; then + echo "ERROR: qpidd running as pid ${PID}" + exit 1 + fi + # Check disk is < 90% full + local perc_full=`df -h ${HOME} | tail -1 | awk '{print substr($5,0, length($5)-1)}'` + if (( ${perc_full} >= ${MAX_DISK_PERC_USED} )); then + echo "ERROR: Disk is too close to full (${perc_full}%)" + exit 2 + fi +} + +# Analyze store files +# $1: Log suffix flag: either "A" or "B". If "A", client is started in test mode, otherwise client evaluates recovery. +analyze_store() { + ${ANALYZE} ${ANALYZE_ARGS} ${BASE_DIR}/qls &> ${RESULT_DIR}/qls_analysis.$1.log + echo >> ${RESULT_DIR}/qls_analysis.$1.log + echo "----------------------------------------------------------" >> ${RESULT_DIR}/qls_analysis.$1.log + echo "With transactional reconsiliation:" >> ${RESULT_DIR}/qls_analysis.$1.log + echo >> ${RESULT_DIR}/qls_analysis.$1.log + ${ANALYZE} ${ANALYZE_ARGS} --txn ${BASE_DIR}/qls &>> ${RESULT_DIR}/qls_analysis.$1.log +} + +ulimit -c unlimited # Allow core files to be created + +RESULT_BASE_DIR_SUFFIX=`date "${TIMESTAMP_FORMAT}"` +RESULT_BASE_DIR="${RESULT_BASE_DIR_PREFIX}.${RESULT_BASE_DIR_SUFFIX}" +LOG_FILE=${RESULT_BASE_DIR}/${LOG_FILE_NAME} +if [[ -n "${RESULT_BASE_DIR}" ]]; then + rm -rf ${RESULT_BASE_DIR} +fi + +mkdir -p ${RESULT_BASE_DIR} +for rn in `seq ${NUM_RUNS}`; do + # === Prepare result dir, check ready to run test, set run vars === + RESULT_DIR=${RESULT_BASE_DIR}/run_${rn} + mkdir -p ${RESULT_DIR} + check_ready_to_run + if (( (${rn} - 1) % ${TRUNCATE_INTERVAL} == 0 )) || [[ -n ${ERROR_FLAG} ]]; then + TRUNCATE_FLAG=true + else + TRUNCATE_FLAG=false + fi + set_message_size + get_random "MSGS_PER_TX" 1 20 + get_random "TOT_MSGS" 100 1000 + get_random "NUM_QUEUES" 2 15 + MIN_RUNTIME=$(( 20 * ${FILE_SIZE_MULTIPLIER} )) + MAX_RUNTIME=$(( 120 * ${FILE_SIZE_MULTIPLIER} )) + get_random "RUN_TIME" ${MIN_RUNTIME} ${MAX_RUNTIME} + RECOVER_TIME=$(( ${NUM_QUEUES} * ${RECOVER_TIME_PER_QUEUE} * ${FILE_SIZE_MULTIPLIER} )) + echo "Run ${rn} of ${NUM_RUNS} ==============" | tee -a ${LOG_FILE} + + # === PART A: Initial run of qpid-txtest === + start_broker "A" ${TRUNCATE_FLAG} + sleep ${RECOVER_TIME} # Need a way to test if broker has started here + start_tx_test "A" + echo "Running for ${RUN_TIME} secs..." | tee -a ${LOG_FILE} + sleep ${RUN_TIME} + kill_process ${SIG_KILL} ${QPIDD_PID} + sleep 2 + analyze_store "A" + tar -czf ${RESULT_DIR}/qls_A.tar.gz ${RELATIVE_BASE_DIR}/qls + + # === PART B: Recovery and check === + start_broker "B" + echo "Recover time=${RECOVER_TIME} secs..." | tee -a ${LOG_FILE} + sleep ${RECOVER_TIME} # Need a way to test if broker has started here + start_tx_test "B" + sleep 1 + kill_process ${SIG_TERM} ${QPIDD_PID} + sleep 2 + PID=`pgrep ${QPIDD_FN}` + if [[ "$?" == "0" ]]; then + kill_process ${SIG_KILL} ${PID} + sleep 2 + fi + analyze_store "B" + tar -czf ${RESULT_DIR}/qls_B.tar.gz ${RELATIVE_BASE_DIR}/qls + + # === Check for errors, cores and exceptions in logs === + grep -Hn "jexception" ${RESULT_DIR}/qpidd.A.log | tee -a ${LOG_FILE} + grep -Hn "jexception" ${RESULT_DIR}/qpidd.B.log | tee -a ${LOG_FILE} + grep -Hn "Traceback (most recent call last):" ${RESULT_DIR}/qls_analysis.A.log | tee -a ${LOG_FILE} + grep -Hn "Traceback (most recent call last):" ${RESULT_DIR}/qls_analysis.B.log | tee -a ${LOG_FILE} + grep "${SUCCESS_MSG}" ${RESULT_DIR}/txtest.B.log &> /dev/null + if [[ "$?" != "0" ]]; then + echo "ERROR in run ${rn}" >> ${LOG_FILE} + echo -e "${ANSI_RED}ERROR${ANSI_NONE} in run ${rn}" + ERROR_FLAG=true + else + unset ERROR_FLAG + fi + sleep 2 + process_core_files + echo | tee -a ${LOG_FILE} +done + diff --git a/qpid/cpp/src/tests/logging.cpp b/qpid/cpp/src/tests/logging.cpp new file mode 100644 index 0000000000..32cd09d73d --- /dev/null +++ b/qpid/cpp/src/tests/logging.cpp @@ -0,0 +1,512 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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. + * + */ + +#include "test_tools.h" +#include "qpid/log/Logger.h" +#include "qpid/log/Options.h" +#include "qpid/log/OstreamOutput.h" +#include "qpid/memory.h" +#include "qpid/Options.h" +#if defined (_WIN32) +# include "qpid/log/windows/SinkOptions.h" +#else +# include "qpid/log/posix/SinkOptions.h" +#endif + +#include <boost/test/floating_point_comparison.hpp> +#include <boost/algorithm/string/predicate.hpp> +#include <boost/format.hpp> +#include "unit_test.h" + +#include <exception> +#include <fstream> +#include <time.h> + + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(loggingTestSuite) + +using namespace std; +using namespace qpid::log; +using boost::ends_with; +using boost::contains; +using boost::format; + +QPID_AUTO_TEST_CASE(testStatementInit) { + Statement s=QPID_LOG_STATEMENT_INIT(debug); int line=__LINE__; + BOOST_CHECK(!s.enabled); + BOOST_CHECK_EQUAL(string(__FILE__), s.file); + BOOST_CHECK_EQUAL(line, s.line); + BOOST_CHECK_EQUAL(debug, s.level); +} + + +QPID_AUTO_TEST_CASE(testSelector_enable) { + Selector s; + // Simple enable + s.enable(debug,"foo"); + BOOST_CHECK(s.isEnabled(debug,"foo")); + BOOST_CHECK(!s.isEnabled(error,"foo")); + BOOST_CHECK(!s.isEnabled(error,"bar")); + + // Substring match + BOOST_CHECK(s.isEnabled(debug, "bazfoobar")); + BOOST_CHECK(!s.isEnabled(debug, "bazbar")); + + // Different levels for different substrings. + s.enable(info, "bar"); + BOOST_CHECK(s.isEnabled(debug, "foobar")); + BOOST_CHECK(s.isEnabled(info, "foobar")); + BOOST_CHECK(!s.isEnabled(debug, "bar")); + BOOST_CHECK(!s.isEnabled(info, "foo")); + + // Enable-strings + s.enable("notice:blob"); + BOOST_CHECK(s.isEnabled(notice, "blob")); + s.enable("error+:oops"); + BOOST_CHECK(s.isEnabled(error, "oops")); + BOOST_CHECK(s.isEnabled(critical, "oops")); +} + +QPID_AUTO_TEST_CASE(testSelector_disable) { + Selector s; + // Simple enable/disable + s.enable(trace,"foo"); + BOOST_CHECK(s.isEnabled(trace,"foo")); + BOOST_CHECK(!s.isDisabled(trace,"foo")); + s.disable(trace,"foo"); + BOOST_CHECK(s.isEnabled(trace,"foo")); + BOOST_CHECK(s.isDisabled(trace,"foo")); +} + +QPID_AUTO_TEST_CASE(testStatementEnabled) { + // Verify that the singleton enables and disables static + // log statements. + Logger& l = Logger::instance(); + ScopedSuppressLogging ls(l); + l.select(Selector(debug)); + static Statement s=QPID_LOG_STATEMENT_INIT(debug); + BOOST_CHECK(!s.enabled); + static Statement::Initializer init(s); + BOOST_CHECK(s.enabled); + + static Statement s2=QPID_LOG_STATEMENT_INIT(warning); + static Statement::Initializer init2(s2); + BOOST_CHECK(!s2.enabled); + + l.select(Selector(warning)); + BOOST_CHECK(!s.enabled); + BOOST_CHECK(s2.enabled); +} + +struct TestOutput : public Logger::Output { + vector<string> msg; + vector<Statement> stmt; + + TestOutput(Logger& l) { + l.output(std::auto_ptr<Logger::Output>(this)); + } + + void log(const Statement& s, const string& m) { + msg.push_back(m); + stmt.push_back(s); + } + string last() { return msg.back(); } +}; + +using boost::assign::list_of; + +QPID_AUTO_TEST_CASE(testLoggerOutput) { + Logger l; + l.clear(); + l.select(Selector(debug)); + Statement s=QPID_LOG_STATEMENT_INIT(debug); + + TestOutput* out=new TestOutput(l); + + // Verify message is output. + l.log(s, "foo"); + vector<string> expect=list_of("foo\n"); + BOOST_CHECK_EQUAL(expect, out->msg); + + // Verify multiple outputs + TestOutput* out2=new TestOutput(l); + l.log(Statement(), "baz"); + expect.push_back("baz\n"); + BOOST_CHECK_EQUAL(expect, out->msg); + expect.erase(expect.begin()); + BOOST_CHECK_EQUAL(expect, out2->msg); +} + +QPID_AUTO_TEST_CASE(testMacro) { + Logger& l=Logger::instance(); + ScopedSuppressLogging ls(l); + l.select(Selector(info)); + TestOutput* out=new TestOutput(l); + QPID_LOG(info, "foo"); + vector<string> expect=list_of("foo\n"); + BOOST_CHECK_EQUAL(expect, out->msg); + BOOST_CHECK_EQUAL(__FILE__, out->stmt.front().file); + + // Not enabled: + QPID_LOG(debug, "bar"); + BOOST_CHECK_EQUAL(expect, out->msg); + + QPID_LOG(info, 42 << " bingo"); + expect.push_back("42 bingo\n"); + BOOST_CHECK_EQUAL(expect, out->msg); +} + +QPID_AUTO_TEST_CASE(testLoggerFormat) { + Logger& l = Logger::instance(); + ScopedSuppressLogging ls(l); + l.select(Selector(critical)); + TestOutput* out=new TestOutput(l); + + l.format(Logger::FILE); + QPID_LOG(critical, "foo"); + BOOST_CHECK_EQUAL(out->last(), string(__FILE__)+": foo\n"); + + l.format(Logger::FILE|Logger::LINE); + QPID_LOG(critical, "foo"); + BOOST_CHECK_EQUAL(out->last().find(__FILE__), 0u); + + l.format(Logger::FUNCTION); + QPID_LOG(critical, "foo"); + BOOST_CHECK( ends_with( out->last(), ": foo\n")); + string name = out->last().substr(0, out->last().length() - 6); + BOOST_CHECK( contains( string(BOOST_CURRENT_FUNCTION), name)); + + l.format(Logger::LEVEL); + QPID_LOG(critical, "foo"); + BOOST_CHECK_EQUAL("critical foo\n", out->last()); +} + +QPID_AUTO_TEST_CASE(testOstreamOutput) { + Logger& l=Logger::instance(); + ScopedSuppressLogging ls(l); + l.select(Selector(error)); + ostringstream os; + l.output(qpid::make_auto_ptr<Logger::Output>(new OstreamOutput(os))); + QPID_LOG(error, "foo"); + QPID_LOG(error, "bar"); + QPID_LOG(error, "baz"); + BOOST_CHECK_EQUAL("foo\nbar\nbaz\n", os.str()); +} + +#if 0 // This test requires manual intervention. Normally disabled. +QPID_AUTO_TEST_CASE(testSyslogOutput) { + Logger& l=Logger::instance(); + Logger::StateSaver ls(l); + l.clear(); + l.select(Selector(info)); + l.syslog("qpid_test"); + QPID_LOG(info, "Testing QPID"); + BOOST_ERROR("Manually verify that /var/log/messages contains a recent line 'Testing QPID'"); +} +#endif // 0 + +int count() { + static int n = 0; + return n++; +} + +int loggedCount() { + static int n = 0; + QPID_LOG(debug, "counting: " << n); + return n++; +} + + +using namespace qpid::sys; + +// Measure CPU time. +clock_t timeLoop(int times, int (*fp)()) { + clock_t start=clock(); + while (times-- > 0) + (*fp)(); + return clock() - start; +} + +// Overhead test disabled because it consumes a ton of CPU and takes +// forever under valgrind. Not friendly for regular test runs. +// +#if 0 +QPID_AUTO_TEST_CASE(testOverhead) { + // Ensure that the ratio of CPU time for an incrementing loop + // with and without disabled log statements is in acceptable limits. + // + int times=100000000; + clock_t noLog=timeLoop(times, count); + clock_t withLog=timeLoop(times, loggedCount); + double ratio=double(withLog)/double(noLog); + + // NB: in initial tests the ratio was consistently below 1.5, + // 2.5 is reasonable and should avoid spurios failures + // due to machine load. + // + BOOST_CHECK_SMALL(ratio, 2.5); +} +#endif // 0 + +Statement statement( + Level level, const char* file="", int line=0, const char* fn=0) +{ + Statement s={0, file, line, fn, level, ::qpid::log::unspecified}; + return s; +} + + +#define ARGC(argv) (sizeof(argv)/sizeof(char*)) + +QPID_AUTO_TEST_CASE(testOptionsParse) { + const char* argv[]={ + 0, + "--log-enable", "error+:foo", + "--log-enable", "debug:bar", + "--log-enable", "info", + "--log-disable", "error+:foo", + "--log-disable", "debug:bar", + "--log-disable", "info", + "--log-to-stderr", "no", + "--log-to-file", "logout", + "--log-level", "yes", + "--log-source", "1", + "--log-thread", "true", + "--log-function", "YES" + }; + qpid::log::Options opts(""); +#ifdef _WIN32 + qpid::log::windows::SinkOptions sinks("test"); +#else + qpid::log::posix::SinkOptions sinks("test"); +#endif + opts.parse(ARGC(argv), const_cast<char**>(argv)); + sinks = *opts.sinkOptions; + vector<string> expect=list_of("error+:foo")("debug:bar")("info"); + BOOST_CHECK_EQUAL(expect, opts.selectors); + BOOST_CHECK_EQUAL(expect, opts.deselectors); + BOOST_CHECK(!sinks.logToStderr); + BOOST_CHECK(!sinks.logToStdout); + BOOST_CHECK(sinks.logFile == "logout"); + BOOST_CHECK(opts.level); + BOOST_CHECK(opts.source); + BOOST_CHECK(opts.function); + BOOST_CHECK(opts.thread); +} + +QPID_AUTO_TEST_CASE(testOptionsDefault) { + qpid::log::Options opts(""); +#ifdef _WIN32 + qpid::log::windows::SinkOptions sinks("test"); +#else + qpid::log::posix::SinkOptions sinks("test"); +#endif + sinks = *opts.sinkOptions; + BOOST_CHECK(sinks.logToStderr); + BOOST_CHECK(!sinks.logToStdout); + BOOST_CHECK(sinks.logFile.length() == 0); + vector<string> expect=list_of("notice+"); + BOOST_CHECK_EQUAL(expect, opts.selectors); + BOOST_CHECK(opts.time && opts.level); + BOOST_CHECK(!(opts.source || opts.function || opts.thread)); +} + +QPID_AUTO_TEST_CASE(testSelectorFromOptions) { + const char* argv[]={ + 0, + "--log-enable", "error+:foo", + "--log-enable", "debug:bar", + "--log-enable", "info" + }; + qpid::log::Options opts(""); + opts.parse(ARGC(argv), const_cast<char**>(argv)); + vector<string> expect=list_of("error+:foo")("debug:bar")("info"); + BOOST_CHECK_EQUAL(expect, opts.selectors); + Selector s(opts); + BOOST_CHECK(!s.isEnabled(warning, "x")); + BOOST_CHECK(!s.isEnabled(debug, "x")); + BOOST_CHECK(s.isEnabled(debug, "bar")); + BOOST_CHECK(s.isEnabled(error, "foo")); + BOOST_CHECK(s.isEnabled(critical, "foo")); +} + +QPID_AUTO_TEST_CASE(testDeselectorFromOptions) { + const char* argv[]={ + 0, + "--log-disable", "error-:foo", + "--log-disable", "debug:bar", + "--log-disable", "info" + }; + qpid::log::Options opts(""); + opts.parse(ARGC(argv), const_cast<char**>(argv)); + vector<string> expect=list_of("error-:foo")("debug:bar")("info"); + BOOST_CHECK_EQUAL(expect, opts.deselectors); + Selector s(opts); + BOOST_CHECK(!s.isDisabled(warning, "x")); + BOOST_CHECK(!s.isDisabled(debug, "x")); + BOOST_CHECK(s.isDisabled(debug, "bar")); + BOOST_CHECK(s.isDisabled(trace, "foo")); + BOOST_CHECK(s.isDisabled(debug, "foo")); + BOOST_CHECK(s.isDisabled(info, "foo")); + BOOST_CHECK(s.isDisabled(notice, "foo")); + BOOST_CHECK(s.isDisabled(warning, "foo")); + BOOST_CHECK(s.isDisabled(error, "foo")); + BOOST_CHECK(!s.isDisabled(critical, "foo")); +} + +QPID_AUTO_TEST_CASE(testMultiConflictingSelectorFromOptions) { + const char* argv[]={ + 0, + "--log-enable", "trace+:foo", + "--log-disable", "error-:foo", + "--log-enable", "debug:bar", + "--log-disable", "debug:bar", + "--log-enable", "info", + "--log-disable", "info", + "--log-enable", "debug+:Model", + "--log-disable", "info-:Model" + }; + qpid::log::Options opts(""); + opts.parse(ARGC(argv), const_cast<char**>(argv)); + Selector s(opts); + BOOST_CHECK(!s.isEnabled(warning, "x", log::broker)); + BOOST_CHECK(!s.isEnabled(debug, "x", log::broker)); + BOOST_CHECK(!s.isEnabled(trace, "foo", log::broker)); + BOOST_CHECK(!s.isEnabled(debug, "foo", log::broker)); + BOOST_CHECK(!s.isEnabled(info, "foo", log::broker)); + BOOST_CHECK(!s.isEnabled(notice, "foo", log::broker)); + BOOST_CHECK(!s.isEnabled(warning, "foo", log::broker)); + BOOST_CHECK(!s.isEnabled(error, "foo", log::broker)); + BOOST_CHECK(s.isEnabled(critical, "foo", log::broker)); + BOOST_CHECK(!s.isEnabled(debug, "bar", log::model)); + BOOST_CHECK(!s.isEnabled(trace, "zaz", log::model)); + BOOST_CHECK(!s.isEnabled(debug, "zaz", log::model)); + BOOST_CHECK(!s.isEnabled(info, "zaz", log::model)); + BOOST_CHECK(s.isEnabled(notice, "zaz", log::model)); + BOOST_CHECK(s.isEnabled(warning, "zaz", log::model)); + BOOST_CHECK(s.isEnabled(error, "zaz", log::model)); + BOOST_CHECK(s.isEnabled(critical, "zaz", log::model)); +} + +QPID_AUTO_TEST_CASE(testLoggerStateure) { + Logger& l=Logger::instance(); + ScopedSuppressLogging ls(l); + qpid::log::Options opts("test"); + const char* argv[]={ + 0, + "--log-time", "no", + "--log-source", "yes", + "--log-to-stderr", "no", + "--log-to-file", "logging.tmp", + "--log-enable", "critical" + }; + opts.parse(ARGC(argv), const_cast<char**>(argv)); + l.configure(opts); + QPID_LOG_CAT(critical, test, "foo"); int srcline=__LINE__; + ifstream log("logging.tmp"); + string line; + getline(log, line); + string expect=(format("[Test] critical %s:%d: foo")%__FILE__%srcline).str(); + BOOST_CHECK_EQUAL(expect, line); + log.close(); + unlink("logging.tmp"); +} + +QPID_AUTO_TEST_CASE(testQuoteNonPrintable) { + Logger& l=Logger::instance(); + ScopedSuppressLogging ls(l); + qpid::log::Options opts("test"); + opts.time=false; +#ifdef _WIN32 + qpid::log::windows::SinkOptions *sinks = + dynamic_cast<qpid::log::windows::SinkOptions *>(opts.sinkOptions.get()); +#else + qpid::log::posix::SinkOptions *sinks = + dynamic_cast<qpid::log::posix::SinkOptions *>(opts.sinkOptions.get()); +#endif + sinks->logToStderr = false; + sinks->logFile = "logging.tmp"; + l.configure(opts); + + char s[] = "null\0tab\tspace newline\nret\r\x80\x99\xff"; + string str(s, sizeof(s)); + QPID_LOG_CAT(critical, test, str); + ifstream log("logging.tmp"); + string line; + getline(log, line, '\0'); + string expect="[Test] critical null\\x00tab\tspace newline\nret\r\\x80\\x99\\xFF\\x00\n"; + BOOST_CHECK_EQUAL(expect, line); + log.close(); + unlink("logging.tmp"); +} + +QPID_AUTO_TEST_CASE(testSelectorElements) { + SelectorElement s("debug"); + BOOST_CHECK_EQUAL(s.levelStr, "debug"); + BOOST_CHECK_EQUAL(s.patternStr, ""); + BOOST_CHECK_EQUAL(s.level, debug); + BOOST_CHECK(!s.isDisable); + BOOST_CHECK(!s.isCategory); + BOOST_CHECK(!s.isLevelAndAbove); + BOOST_CHECK(!s.isLevelAndBelow); + + SelectorElement t("debug:Broker"); + BOOST_CHECK_EQUAL(t.levelStr, "debug"); + BOOST_CHECK_EQUAL(t.patternStr, "Broker"); + BOOST_CHECK_EQUAL(t.level, debug); + BOOST_CHECK_EQUAL(t.category, broker); + BOOST_CHECK(!t.isDisable); + BOOST_CHECK(t.isCategory); + BOOST_CHECK(!t.isLevelAndAbove); + BOOST_CHECK(!t.isLevelAndBelow); + + SelectorElement u("info+:qmf::"); + BOOST_CHECK_EQUAL(u.levelStr, "info"); + BOOST_CHECK_EQUAL(u.patternStr, "qmf::"); + BOOST_CHECK_EQUAL(u.level, info); + BOOST_CHECK(!u.isDisable); + BOOST_CHECK(!u.isCategory); + BOOST_CHECK(u.isLevelAndAbove); + BOOST_CHECK(!u.isLevelAndBelow); + + SelectorElement v("critical-"); + BOOST_CHECK_EQUAL(v.levelStr, "critical"); + BOOST_CHECK_EQUAL(v.patternStr, ""); + BOOST_CHECK_EQUAL(v.level, critical); + BOOST_CHECK(!v.isDisable); + BOOST_CHECK(!v.isCategory); + BOOST_CHECK(!v.isLevelAndAbove); + BOOST_CHECK(v.isLevelAndBelow); + + SelectorElement w("!warning-:Management"); + BOOST_CHECK_EQUAL(w.levelStr, "warning"); + BOOST_CHECK_EQUAL(w.patternStr, "Management"); + BOOST_CHECK_EQUAL(w.level, warning); + BOOST_CHECK_EQUAL(w.category, management); + BOOST_CHECK(w.isDisable); + BOOST_CHECK(w.isCategory); + BOOST_CHECK(!w.isLevelAndAbove); + BOOST_CHECK(w.isLevelAndBelow); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/misc.py b/qpid/cpp/src/tests/misc.py new file mode 100644 index 0000000000..257fb9e754 --- /dev/null +++ b/qpid/cpp/src/tests/misc.py @@ -0,0 +1,119 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from qpid.tests.messaging.implementation import * +from qpid.tests.messaging import VersionTest + +class MiscellaneousTests (VersionTest): + """ + Tests for various aspects of qpidd behaviour + """ + def test_exclusive(self): + con = self.create_connection("amqp1.0", True) + rcv = con.session().receiver("q; {create:always, node:{properties:{exclusive:True,auto-delete:True}}}") + + other = self.create_connection("amqp1.0", True) + try: + #can send to the queue + snd = other.session().sender("q") + + #can browse the queue + browser = other.session().receiver("q; {mode:browse}") + + #can't consume from the queue + try: + consumer = other.session().receiver("q") + assert False, ("Should not be able to consume from exclusively owned queue") + except LinkError, e: None + try: + exclusive = other.session().receiver("q; {create: always, node:{properties:{exclusive:True}}}") + assert False, ("Should not be able to consume exclusively from exclusively owned queue") + except LinkError, e: None + finally: + rcv.close() + con.close() + other.close() + +class AutoDeleteExchangeTests(VersionTest): + def init_test(self, exchange_type="topic"): + rcv = self.ssn.receiver("my-topic; {create:always, node:{type:topic, properties:{'exchange-type':%s, 'auto-delete':True}}}" % exchange_type) + snd = self.ssn.sender("my-topic") + #send some messages + msgs = [Message(content=c) for c in ['a','b','c','d']] + for m in msgs: snd.send(m) + + #verify receipt + for expected in msgs: + msg = rcv.fetch(0) + assert msg.content == expected.content + self.ssn.acknowledge(msg) + return (rcv, snd) + + def on_rcv_detach_test(self, exchange_type="topic"): + rcv, snd = self.init_test(exchange_type) + rcv.close() + #verify exchange is still there + snd.send(Message(content="will be dropped")) + snd.close() + #now verify it is no longer there + try: + self.ssn.sender("my-topic") + assert False, "Attempt to send to deleted exchange should fail" + except MessagingError: None + + def on_snd_detach_test(self, exchange_type="topic"): + rcv, snd = self.init_test(exchange_type) + snd.close() + #verify exchange is still there + snd = self.ssn.sender("my-topic") + snd.send(Message(content="will be dropped")) + snd.close() + rcv.close() + #now verify it is no longer there + try: + self.ssn.sender("my-topic") + assert False, "Attempt to send to deleted exchange should fail" + except MessagingError: None + + def test_autodelete_fanout_exchange_on_rcv_detach(self): + self.on_rcv_detach_test("fanout") + + def test_autodelete_fanout_exchange_on_snd_detach(self): + self.on_snd_detach_test("fanout") + + def test_autodelete_direct_exchange_on_rcv_detach(self): + self.on_rcv_detach_test("direct") + + def test_autodelete_direct_exchange_on_snd_detach(self): + self.on_snd_detach_test("direct") + + def test_autodelete_topic_exchange_on_rcv_detach(self): + self.on_rcv_detach_test("topic") + + def test_autodelete_topic_exchange_on_snd_detach(self): + self.on_snd_detach_test("topic") + + def test_autodelete_headers_exchange_on_rcv_detach(self): + self.on_rcv_detach_test("headers") + + def test_autodelete_headers_exchange_on_snd_detach(self): + self.on_snd_detach_test("headers") + + + diff --git a/qpid/cpp/src/tests/msg_group_test.cpp b/qpid/cpp/src/tests/msg_group_test.cpp new file mode 100644 index 0000000000..ca87197ff3 --- /dev/null +++ b/qpid/cpp/src/tests/msg_group_test.cpp @@ -0,0 +1,641 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include <qpid/messaging/Address.h> +#include <qpid/messaging/Connection.h> +#include <qpid/messaging/Receiver.h> +#include <qpid/messaging/Sender.h> +#include <qpid/messaging/Session.h> +#include <qpid/messaging/Message.h> +#include <qpid/messaging/FailoverUpdates.h> +#include <qpid/Options.h> +#include <qpid/log/Logger.h> +#include <qpid/log/Options.h> +#include "qpid/log/Statement.h" +#include "qpid/sys/Time.h" +#include "qpid/sys/Runnable.h" +#include "qpid/sys/Thread.h" +#include "qpid/sys/SystemInfo.h" + +#include <iostream> +#include <memory> +#include <stdlib.h> + +using namespace qpid::messaging; +using namespace qpid::types; +using namespace std; + +namespace qpid { +namespace tests { + +struct Options : public qpid::Options +{ + bool help; + std::string url; + std::string address; + std::string connectionOptions; + uint messages; + uint capacity; + uint ackFrequency; + bool failoverUpdates; + qpid::log::Options log; + uint senders; + uint receivers; + uint groupSize; + bool printReport; + std::string groupKey; + bool durable; + bool allowDuplicates; + bool randomizeSize; + bool stickyConsumer; + uint timeout; + uint interleave; + std::string prefix; + uint sendRate; + + Options(const std::string& argv0=std::string()) + : qpid::Options("Options"), + help(false), + url("amqp:tcp:127.0.0.1"), + messages(10000), + capacity(1000), + ackFrequency(100), + failoverUpdates(false), + log(argv0), + senders(2), + receivers(2), + groupSize(10), + printReport(false), + groupKey("qpid.no_group"), + durable(false), + allowDuplicates(false), + randomizeSize(false), + stickyConsumer(false), + timeout(10), + interleave(1), + sendRate(0) + { + addOptions() + ("ack-frequency", qpid::optValue(ackFrequency, "N"), "Ack frequency (0 implies none of the messages will get accepted)") + ("address,a", qpid::optValue(address, "ADDRESS"), "address to send and receive from") + ("allow-duplicates", qpid::optValue(allowDuplicates), "Ignore the delivery of duplicated messages") + ("broker,b", qpid::optValue(url, "URL"), "url of broker to connect to") + ("capacity", qpid::optValue(capacity, "N"), "Pre-fetch window (0 implies no pre-fetch)") + ("connection-options", qpid::optValue(connectionOptions, "OPTIONS"), "options for the connection") + ("durable", qpid::optValue(durable, "yes|no"), "Mark messages as durable.") + ("failover-updates", qpid::optValue(failoverUpdates), "Listen for membership updates distributed via amq.failover") + ("group-key", qpid::optValue(groupKey, "KEY"), "Key of the message header containing the group identifier.") + ("group-prefix", qpid::optValue(prefix, "STRING"), "Add 'prefix' to the start of all generated group identifiers.") + ("group-size", qpid::optValue(groupSize, "N"), "Number of messages per a group.") + ("interleave", qpid::optValue(interleave, "N"), "Simultaineously interleave messages from N different groups.") + ("messages,m", qpid::optValue(messages, "N"), "Number of messages to send per each sender.") + ("receivers,r", qpid::optValue(receivers, "N"), "Number of message consumers.") + ("randomize-group-size", qpid::optValue(randomizeSize), "Randomize the number of messages per group to [1...group-size].") + ("send-rate", qpid::optValue(sendRate,"N"), "Send at rate of N messages/second. 0 means send as fast as possible.") + ("senders,s", qpid::optValue(senders, "N"), "Number of message producers.") + ("sticky-consumers", qpid::optValue(stickyConsumer), "If set, verify that all messages in a group are consumed by the same client [TBD].") + ("timeout", qpid::optValue(timeout, "N"), "Fail with a stall error should all consumers remain idle for timeout seconds.") + ("print-report", qpid::optValue(printReport), "Dump message group statistics to stdout.") + ("help", qpid::optValue(help), "print this usage statement"); + add(log); + //("check-redelivered", qpid::optValue(checkRedelivered), "Fails with exception if a duplicate is not marked as redelivered (only relevant when ignore-duplicates is selected)") + //("tx", qpid::optValue(tx, "N"), "batch size for transactions (0 implies transaction are not used)") + //("rollback-frequency", qpid::optValue(rollbackFrequency, "N"), "rollback frequency (0 implies no transaction will be rolledback)") + } + + bool parse(int argc, char** argv) + { + try { + qpid::Options::parse(argc, argv); + if (address.empty()) throw qpid::Exception("Address must be specified!"); + if (senders == 0 && receivers == 0) throw qpid::Exception("No senders and No receivers?"); + if (messages == 0) throw qpid::Exception("The message count cannot be zero."); + qpid::log::Logger::instance().configure(log); + if (help) { + std::cout << *this << std::endl << std::endl + << "Verifies the behavior of grouped messages." << std::endl; + return false; + } else { + return true; + } + } catch (const std::exception& e) { + std::cerr << *this << std::endl << std::endl << e.what() << std::endl; + return false; + } + } +}; + +const string EOS("eos"); +const string SN("sn"); + + +// class that monitors group state across all publishers and consumers. tracks the next +// expected sequence for each group, and total messages consumed. +class GroupChecker +{ + qpid::sys::Mutex lock; + + uint consumerCt; + uint producerCt; + uint totalMsgs; + uint totalMsgsConsumed; + uint totalMsgsPublished; + bool allowDuplicates; + uint duplicateMsgs; + + typedef std::map<std::string, uint> SequenceMap; + SequenceMap sequenceMap; + + // Statistics - for each group, store the names of all clients that consumed messages + // from that group, and the number of messages consumed per client. + typedef std::map<std::string, uint> ClientCounter; + typedef std::map<std::string, ClientCounter> GroupStatistics; + GroupStatistics statistics; + +public: + + GroupChecker( uint messages, uint consumers, uint producers, bool d) : + consumerCt(consumers), producerCt(producers), + totalMsgs(0), totalMsgsConsumed(0), totalMsgsPublished(0), allowDuplicates(d), + duplicateMsgs(0) + { + // if consumering only - we a draining a queue of 'messages' queued messages. + if (producerCt != 0) { + totalMsgs = producers * messages; + } else { + totalMsgs = messages; + } + } + + bool checkSequence( const std::string& groupId, + uint sequence, const std::string& client ) + { + qpid::sys::Mutex::ScopedLock l(lock); + + QPID_LOG(debug, "Client " << client << " has received " << groupId << ":" << sequence); + + GroupStatistics::iterator gs = statistics.find(groupId); + if (gs == statistics.end()) { + statistics[groupId][client] = 1; + } else { + gs->second[client]++; + } + // now verify + SequenceMap::iterator s = sequenceMap.find(groupId); + if (s == sequenceMap.end()) { + QPID_LOG(debug, "Client " << client << " thinks this is the first message from group " << groupId << ":" << sequence); + // if duplication allowed, it is possible that the last msg(s) of an old sequence are redelivered on reconnect. + // in this case, set the sequence from the first msg. + sequenceMap[groupId] = (allowDuplicates) ? sequence : 0; + s = sequenceMap.find(groupId); + } else if (sequence < s->second) { + duplicateMsgs++; + QPID_LOG(debug, "Client " << client << " thinks this message is a duplicate! " << groupId << ":" << sequence); + return allowDuplicates; + } + totalMsgsConsumed++; + return sequence == s->second++; + } + + void sendingSequence( const std::string& groupId, + uint sequence, bool eos, + const std::string& client ) + { + qpid::sys::Mutex::ScopedLock l(lock); + ++totalMsgsPublished; + + QPID_LOG(debug, "Client " << client << " sending " << groupId << ":" << sequence << + ((eos) ? " (last)" : "")); + } + + bool eraseGroup( const std::string& groupId, const std::string& name ) + { + qpid::sys::Mutex::ScopedLock l(lock); + QPID_LOG(debug, "Deleting group " << groupId << " (by client " << name << ")"); + return sequenceMap.erase( groupId ) == 1; + } + + uint getNextExpectedSequence( const std::string& groupId ) + { + qpid::sys::Mutex::ScopedLock l(lock); + return sequenceMap[groupId]; + } + + bool allMsgsPublished() // true when done publishing msgs + { + qpid::sys::Mutex::ScopedLock l(lock); + return (producerCt == 0 || totalMsgsPublished >= totalMsgs); + } + + bool allMsgsConsumed() // true when done consuming msgs + { + qpid::sys::Mutex::ScopedLock l(lock); + return (consumerCt == 0 || + (totalMsgsConsumed >= totalMsgs && sequenceMap.size() == 0)); + } + + uint getTotalMessages() + { + return totalMsgs; + } + + uint getConsumedTotal() + { + qpid::sys::Mutex::ScopedLock l(lock); + return totalMsgsConsumed; + } + + uint getPublishedTotal() + { + qpid::sys::Mutex::ScopedLock l(lock); + return totalMsgsPublished; + } + + ostream& print(ostream& out) + { + qpid::sys::Mutex::ScopedLock l(lock); + out << "Total Published: " << totalMsgsPublished << ", Total Consumed: " << totalMsgsConsumed << + ", Duplicates detected: " << duplicateMsgs << std::endl; + out << "Total Groups: " << statistics.size() << std::endl; + unsigned long consumers = 0; + for (GroupStatistics::iterator gs = statistics.begin(); gs != statistics.end(); ++gs) { + out << " GroupId: " << gs->first; + consumers += gs->second.size(); // # of consumers that processed this group + if (gs->second.size() == 1) + out << " completely consumed by a single client." << std::endl; + else + out << " consumed by " << gs->second.size() << " different clients." << std::endl; + + for (ClientCounter::iterator cc = gs->second.begin(); cc != gs->second.end(); ++cc) { + out << " Client: " << cc->first << " consumed " << cc->second << " messages from the group." << std::endl; + } + } + out << "Average # of consumers per group: " << ((statistics.size() != 0) ? (double(consumers)/statistics.size()) : 0) << std::endl; + return out; + } +}; + + +namespace { + // rand() is not thread safe. Create a singleton obj to hold a lock while calling + // rand() so it can be called safely by multiple concurrent clients. + class Randomizer { + qpid::sys::Mutex lock; + public: + uint operator()(uint max) { + qpid::sys::Mutex::ScopedLock l(lock); + return (rand() % max) + 1; + } + }; + + static Randomizer randomizer; +} + + +// tag each generated message with a group identifer +// +class GroupGenerator { + + const std::string groupPrefix; + const uint groupSize; + const bool randomizeSize; + const uint interleave; + + uint groupSuffix; + uint total; + + struct GroupState { + std::string id; + const uint size; + uint count; + GroupState( const std::string& i, const uint s ) + : id(i), size(s), count(0) {} + }; + typedef std::list<GroupState> GroupList; + GroupList groups; + GroupList::iterator current; + + // add a new group identifier to the list + void newGroup() { + std::ostringstream groupId(groupPrefix, ios_base::out|ios_base::ate); + groupId << std::string(":") << groupSuffix++; + uint size = (randomizeSize) ? randomizer(groupSize) : groupSize; + QPID_LOG(trace, "New group: GROUPID=[" << groupId.str() << "] size=" << size << " this=" << this); + GroupState group( groupId.str(), size ); + groups.push_back( group ); + } + +public: + GroupGenerator( const std::string& prefix, + const uint t, + const uint size, + const bool randomize, + const uint i) + : groupPrefix(prefix), groupSize(size), + randomizeSize(randomize), interleave(i), groupSuffix(0), total(t) + { + QPID_LOG(trace, "New group generator: PREFIX=[" << prefix << "] total=" << total << " size=" << size << " rand=" << randomize << " interleave=" << interleave << " this=" << this); + for (uint i = 0; i < 1 || i < interleave; ++i) { + newGroup(); + } + current = groups.begin(); + } + + bool genGroup(std::string& groupId, uint& seq, bool& eos) + { + if (!total) return false; + --total; + if (current == groups.end()) + current = groups.begin(); + groupId = current->id; + seq = current->count++; + if (current->count == current->size) { + QPID_LOG(trace, "Last msg for " << current->id << ", " << current->count << " this=" << this); + eos = true; + if (total >= interleave) { // need a new group to replace this one + newGroup(); + groups.erase(current++); + } else ++current; + } else { + ++current; + eos = total < interleave; // mark eos on the last message of each group + } + QPID_LOG(trace, "SENDING GROUPID=[" << groupId << "] seq=" << seq << " eos=" << eos << " this=" << this); + return true; + } +}; + + + +class Client : public qpid::sys::Runnable +{ +public: + typedef boost::shared_ptr<Client> shared_ptr; + enum State {ACTIVE, DONE, FAILURE}; + Client( const std::string& n, const Options& o ) : name(n), opts(o), state(ACTIVE), stopped(false) {} + virtual ~Client() {} + State getState() { return state; } + void testFailed( const std::string& reason ) { state = FAILURE; error << "Client '" << name << "' failed: " << reason; } + void clientDone() { if (state == ACTIVE) state = DONE; } + qpid::sys::Thread& getThread() { return thread; } + const std::string getErrorMsg() { return error.str(); } + void stop() {stopped = true;} + const std::string& getName() { return name; } + +protected: + const std::string name; + const Options& opts; + qpid::sys::Thread thread; + ostringstream error; + State state; + bool stopped; +}; + + +class Consumer : public Client +{ + GroupChecker& checker; + +public: + Consumer(const std::string& n, const Options& o, GroupChecker& c ) : Client(n, o), checker(c) {}; + virtual ~Consumer() {}; + + void run() + { + Connection connection; + try { + connection = Connection(opts.url, opts.connectionOptions); + connection.open(); + std::auto_ptr<FailoverUpdates> updates(opts.failoverUpdates ? new FailoverUpdates(connection) : 0); + Session session = connection.createSession(); + Receiver receiver = session.createReceiver(opts.address); + receiver.setCapacity(opts.capacity); + Message msg; + uint count = 0; + + while (!stopped) { + if (receiver.fetch(msg, Duration::SECOND)) { // msg retrieved + qpid::types::Variant::Map& properties = msg.getProperties(); + std::string groupId = properties[opts.groupKey]; + uint groupSeq = properties[SN]; + bool eof = properties[EOS]; + + QPID_LOG(trace, "RECVING GROUPID=[" << groupId << "] seq=" << groupSeq << " eos=" << eof << " name=" << name); + + qpid::sys::usleep(10); + + if (!checker.checkSequence( groupId, groupSeq, name )) { + ostringstream msg; + msg << "Check sequence failed. Group=" << groupId << " rcvd seq=" << groupSeq << " expected=" << checker.getNextExpectedSequence( groupId ); + testFailed( msg.str() ); + break; + } else if (eof) { + if (!checker.eraseGroup( groupId, name )) { + ostringstream msg; + msg << "Erase group failed. Group=" << groupId << " rcvd seq=" << groupSeq; + testFailed( msg.str() ); + break; + } + } + + ++count; + if (opts.ackFrequency && (count % opts.ackFrequency == 0)) { + session.acknowledge(); + } + // Clear out message properties & content for next iteration. + msg = Message(); // TODO aconway 2010-12-01: should be done by fetch + } else if (checker.allMsgsConsumed()) // timed out, nothing else to do? + break; + } + session.acknowledge(); + session.close(); + connection.close(); + } catch(const std::exception& error) { + ostringstream msg; + msg << "consumer error: " << error.what(); + testFailed( msg.str() ); + connection.close(); + } + clientDone(); + QPID_LOG(trace, "Consuming client " << name << " completed."); + } +}; + + + +class Producer : public Client +{ + GroupChecker& checker; + GroupGenerator generator; + +public: + Producer(const std::string& n, const Options& o, GroupChecker& c) + : Client(n, o), checker(c), + generator( n, o.messages, o.groupSize, o.randomizeSize, o.interleave ) + {}; + virtual ~Producer() {}; + + void run() + { + Connection connection; + try { + connection = Connection(opts.url, opts.connectionOptions); + connection.open(); + std::auto_ptr<FailoverUpdates> updates(opts.failoverUpdates ? new FailoverUpdates(connection) : 0); + Session session = connection.createSession(); + Sender sender = session.createSender(opts.address); + if (opts.capacity) sender.setCapacity(opts.capacity); + Message msg; + msg.setDurable(opts.durable); + std::string groupId; + uint seq; + bool eos; + uint sent = 0; + + qpid::sys::AbsTime start = qpid::sys::now(); + int64_t interval = 0; + if (opts.sendRate) interval = qpid::sys::TIME_SEC/opts.sendRate; + + while (!stopped && generator.genGroup(groupId, seq, eos)) { + msg.getProperties()[opts.groupKey] = groupId; + msg.getProperties()[SN] = seq; + msg.getProperties()[EOS] = eos; + checker.sendingSequence( groupId, seq, eos, name ); + + sender.send(msg); + ++sent; + + if (opts.sendRate) { + qpid::sys::AbsTime waitTill(start, sent*interval); + int64_t delay = qpid::sys::Duration(qpid::sys::now(), waitTill); + if (delay > 0) qpid::sys::usleep(delay/qpid::sys::TIME_USEC); + } + } + session.sync(); + session.close(); + connection.close(); + } catch(const std::exception& error) { + ostringstream msg; + msg << "producer '" << name << "' error: " << error.what(); + testFailed(msg.str()); + connection.close(); + } + clientDone(); + QPID_LOG(trace, "Producing client " << name << " completed."); + } +}; + + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char ** argv) +{ + int status = 0; + try { + Options opts; + if (opts.parse(argc, argv)) { + + GroupChecker state( opts.messages, + opts.receivers, + opts.senders, + opts.allowDuplicates); + std::vector<Client::shared_ptr> clients; + + if (opts.randomizeSize) srand((unsigned int)qpid::sys::SystemInfo::getProcessId()); + + // fire off the producers && consumers + for (size_t j = 0; j < opts.senders; ++j) { + ostringstream name; + name << opts.prefix << "P_" << j; + clients.push_back(Client::shared_ptr(new Producer( name.str(), opts, state ))); + clients.back()->getThread() = qpid::sys::Thread(*clients.back()); + } + for (size_t j = 0; j < opts.receivers; ++j) { + ostringstream name; + name << opts.prefix << "C_" << j; + clients.push_back(Client::shared_ptr(new Consumer( name.str(), opts, state ))); + clients.back()->getThread() = qpid::sys::Thread(*clients.back()); + } + + // wait for all pubs/subs to finish.... or for consumers to fail or stall. + uint stalledTime = 0; + bool clientFailed = false; + while (!clientFailed && (!state.allMsgsPublished() || !state.allMsgsConsumed())) { + uint lastCount; + + lastCount = state.getConsumedTotal(); + qpid::sys::usleep( 1000000 ); + + // check each client for failures + for (std::vector<Client::shared_ptr>::iterator i = clients.begin(); + i != clients.end(); ++i) { + QPID_LOG(debug, "Client " << (*i)->getName() << " state=" << (*i)->getState()); + if ((*i)->getState() == Client::FAILURE) { + QPID_LOG(error, argv[0] << ": test failed with client error: " << (*i)->getErrorMsg()); + clientFailed = true; + break; // exit test. + } + } + + // check for stalled consumers + if (!clientFailed && !state.allMsgsConsumed()) { + if (lastCount == state.getConsumedTotal()) { + if (++stalledTime >= opts.timeout) { + clientFailed = true; + break; // exit test + } + } else { + stalledTime = 0; + } + } + QPID_LOG(debug, "Consumed to date = " << state.getConsumedTotal() << + " Published to date = " << state.getPublishedTotal() << + " total=" << state.getTotalMessages()); + } + + if (clientFailed) { + if (stalledTime >= opts.timeout) { + QPID_LOG(error, argv[0] << ": test failed due to stalled consumer." ); + status = 2; + } else { + status = 1; + } + } + + // Wait for started threads. + for (std::vector<Client::shared_ptr>::iterator i = clients.begin(); + i != clients.end(); ++i) { + (*i)->stop(); + (*i)->getThread().join(); + } + + if (opts.printReport && !status) state.print(std::cout); + } else status = 4; + } catch(const std::exception& error) { + QPID_LOG(error, argv[0] << ": " << error.what()); + status = 3; + } + QPID_LOG(trace, "TEST DONE [" << status << "]"); + + return status; +} diff --git a/qpid/cpp/src/tests/multiq_perftest b/qpid/cpp/src/tests/multiq_perftest new file mode 100755 index 0000000000..9673dd2e6d --- /dev/null +++ b/qpid/cpp/src/tests/multiq_perftest @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +exec `dirname $0`/run_perftest 10000 --mode shared --qt 16 diff --git a/qpid/cpp/src/tests/perfdist b/qpid/cpp/src/tests/perfdist new file mode 100755 index 0000000000..4049b410ff --- /dev/null +++ b/qpid/cpp/src/tests/perfdist @@ -0,0 +1,87 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + + +# +# Distributed perftest. +# Runs perftest clients on multiple hosts using ssh. +# + +set -e +usage() { +cat <<EOF +usage: $0 <perftest-args> -- <client-hosts ...> [ --- <broker hosts...> ] +Client & broker hosts can also be set in env vars CLIENTS and BROKERS. + +Run perftest clients on the client hosts against brokers on the broker +hosts Clients are assigned to client hosts round robin: publishers +first, then subscribers. If there are multiple brokers (for cluster +tests) clients connect to them round robin. + +Broker hosts can be listed with -b in perftest-args or after --- +at the end of the arguments. + +Error: $* +EOF +exit 1 +} + +TESTDIR=${TESTDIR:-$PWD} # Absolute path to test exes on all hosts. + +collect() { eval $COLLECT=\""\$$COLLECT $*"\"; } +NPUBS=1 +NSUBS=1 +COLLECT=ARGS +while test $# -gt 0; do + case $1 in + --publish|--subscribe|--setup|--control) usage "Don't pass perftest action flags: $1" ;; + --npubs) collect $1 $2; NPUBS=$2; shift 2 ;; + --nsubs) collect $1 $2; NSUBS=$2; shift 2 ;; + -s|--summary) collect $1; QUIET=yes; shift 1 ;; + -b|--broker) BROKERS="$BROKERS $2"; shift 2;; + --) COLLECT=CLIENTARG; shift ;; + ---) COLLECT=BROKERARG; shift;; + *) collect $1; shift ;; + esac +done + +CLIENTS=${CLIENTARG:-$CLIENTS} +if [ -z "$CLIENTS" ]; then usage "No client hosts listed after --"; fi +BROKERS=${BROKERARG:-$BROKERS} +if [ -z "$BROKERS" ]; then usage "No brokers specified"; fi + +PERFTEST="$TESTDIR/perftest $ARGS" + +CLIENTS=($CLIENTS) +BROKERS=($BROKERS) +start() { + CLIENT=${CLIENTS[i % ${#CLIENTS[*]}]} + BROKER=${BROKERS[i % ${#BROKERS[*]}]} + ARGS="$* --broker $BROKER" + cmd="ssh -n $CLIENT $PERFTEST $ARGS" + test -z "$QUIET" && echo "Client $i: $cmd" + $cmd & +} + +$PERFTEST --setup -b ${BROKERS[0]} +for (( i=0 ; i < $NPUBS ; ++i)); do start --publish; done +for (( ; i < $NPUBS+$NSUBS ; ++i)); do start --subscribe; done +$PERFTEST --control -b ${BROKERS[0]} diff --git a/qpid/cpp/src/tests/ping_broker b/qpid/cpp/src/tests/ping_broker new file mode 100755 index 0000000000..bdf48f3358 --- /dev/null +++ b/qpid/cpp/src/tests/ping_broker @@ -0,0 +1,134 @@ +#!/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. +# + +import os +from optparse import OptionParser, OptionGroup +import sys +import locale +import socket +import re +from qpid.messaging import Connection + +home = os.environ.get("QPID_TOOLS_HOME", os.path.normpath("/usr/share/qpid-tools")) +sys.path.append(os.path.join(home, "python")) + +from qpidtoollibs import BrokerAgent +from qpidtoollibs import Display, Header, Sorter, YN, Commas, TimeLong + + +class Config: + def __init__(self): + self._host = "localhost" + self._connTimeout = 10 + +config = Config() +conn_options = {} + +def OptionsAndArguments(argv): + """ Set global variables for options, return arguments """ + + global config + global conn_options + + usage = "%prog [options]" + + parser = OptionParser(usage=usage) + + parser.add_option("-b", "--broker", action="store", type="string", default="localhost", metavar="<url>", + help="URL of the broker to query") + parser.add_option("-t", "--timeout", action="store", type="int", default=10, metavar="<secs>", + help="Maximum time to wait for broker connection (in seconds)") + parser.add_option("--sasl-mechanism", action="store", type="string", metavar="<mech>", + help="SASL mechanism for authentication (e.g. EXTERNAL, ANONYMOUS, PLAIN, CRAM-MD5, DIGEST-MD5, GSSAPI). SASL automatically picks the most secure available mechanism - use this option to override.") + parser.add_option("--ssl-certificate", action="store", type="string", metavar="<cert>", help="Client SSL certificate (PEM Format)") + parser.add_option("--ssl-key", action="store", type="string", metavar="<key>", help="Client SSL private key (PEM Format)") + parser.add_option("--ssl-trustfile", action="store", type="string", metavar="<CA>", help="List of trusted CAs (PEM Format)") + parser.add_option("--ssl-skip-hostname-check", action="store_true", + help="Do not validate hostname in peer certificate") + parser.add_option("--ha-admin", action="store_true", help="Allow connection to a HA backup broker.") + + opts, args = parser.parse_args(args=argv) + + config._host = opts.broker + config._connTimeout = opts.timeout + + if opts.sasl_mechanism: + conn_options['sasl_mechanisms'] = opts.sasl_mechanism + if opts.ssl_certificate: + conn_options['ssl_certfile'] = opts.ssl_certificate + if opts.ssl_key: + conn_options['ssl_key'] = opts.ssl_key + if opts.ssl_trustfile: + conn_options['ssl_trustfile'] = opts.ssl_trustfile + if opts.ssl_skip_hostname_check: + conn_options['ssl_skip_hostname_check'] = True + if opts.ha_admin: + conn_options['client_properties'] = {'qpid.ha-admin' : 1} + return args + +class BrokerManager: + def __init__(self): + self.brokerName = None + self.connection = None + self.broker = None + self.cluster = None + + def SetBroker(self, brokerUrl): + self.url = brokerUrl + self.connection = Connection.establish(self.url, **conn_options) + self.broker = BrokerAgent(self.connection) + + def Disconnect(self): + """ Release any allocated brokers. Ignore any failures as the tool is + shutting down. + """ + try: + connection.close() + except: + pass + + def Ping(self, args): + for sequence in range(10): + result = self.broker.echo(sequence, "ECHO BODY") + if result['sequence'] != sequence: + raise Exception("Invalid Sequence") + + +def main(argv=None): + + args = OptionsAndArguments(argv) + bm = BrokerManager() + + try: + bm.SetBroker(config._host) + bm.Ping(args) + bm.Disconnect() + return 0 + except KeyboardInterrupt: + print + except Exception,e: + print "Failed: %s - %s" % (e.__class__.__name__, e) + + bm.Disconnect() # try to deallocate brokers + return 1 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/qpid/cpp/src/tests/policies.py b/qpid/cpp/src/tests/policies.py new file mode 100644 index 0000000000..ec0191f91e --- /dev/null +++ b/qpid/cpp/src/tests/policies.py @@ -0,0 +1,209 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from qpid.tests.messaging.implementation import * +from qpid.tests.messaging import VersionTest + +class Mgmt: + """ + Simple QMF management utility (qpidtoollibs uses + qpid.messaging.Message rather than swigged version) + """ + def __init__(self, conn): + self.conn = conn + self.sess = self.conn.session() + self.reply_to = "qmf.default.topic/direct.%s;{node:{type:topic}, link:{x-declare:{auto-delete:True,exclusive:True}}}" % \ + str(uuid4()) + self.reply_rx = self.sess.receiver(self.reply_to) + self.reply_rx.capacity = 10 + self.tx = self.sess.sender("qmf.default.direct/broker") + self.next_correlator = 1 + + def list(self, class_name): + props = {'method' : 'request', + 'qmf.opcode' : '_query_request', + 'x-amqp-0-10.app-id' : 'qmf2'} + correlator = str(self.next_correlator) + self.next_correlator += 1 + + content = {'_what' : 'OBJECT', + '_schema_id' : {'_class_name' : class_name.lower()}} + + message = Message(content, reply_to=self.reply_to, correlation_id=correlator, + properties=props, subject="broker") + self.tx.send(message) + + + response = self.reply_rx.fetch(10) + if response.properties['qmf.opcode'] != '_query_response': + raise Exception("bad response") + items = [] + done = False + while not done: + for item in response.content: + items.append(item['_values']) + if 'partial' in response.properties: + response = self.reply_rx.fetch(10) + else: + done = True + self.sess.acknowledge() + return items + + def do_qmf_method(self, method, arguments, addr="org.apache.qpid.broker:broker:amqp-broker", timeout=10): + props = {'method' : 'request', + 'qmf.opcode' : '_method_request', + 'x-amqp-0-10.app-id' : 'qmf2'} + correlator = str(self.next_correlator) + self.next_correlator += 1 + + content = {'_object_id' : {'_object_name' : addr}, + '_method_name' : method, + '_arguments' : arguments} + + message = Message(content, reply_to=self.reply_to, correlation_id=correlator, + properties=props, subject="broker") + self.tx.send(message) + response = self.reply_rx.fetch(timeout) + self.sess.acknowledge() + if response.properties['qmf.opcode'] == '_exception': + raise Exception("Exception from Agent: %r" % response.content['_values']) + if response.properties['qmf.opcode'] != '_method_response': + raise Exception("bad response: %r" % response.properties) + return response.content['_arguments'] + + def create(self, _type, name, properties={}): + return self.do_qmf_method('create', {'type': _type, 'name': name, 'properties': properties}) + + def delete(self, _type, name): + return self.do_qmf_method('delete', {'type': _type, 'name': name}) + + +class PoliciesTests (VersionTest): + """ + Tests for node policies with qpidd + """ + + def do_simple_queue_test(self, pattern, name, properties={}, autodeleted=True): + mgmt = self.create_connection("amqp0-10", True) + agent = Mgmt(mgmt) + agent.create('QueuePolicy', pattern, properties) + try: + snd = self.ssn.sender(name) + msgs = [Message(content=s, subject = s) for s in ['a','b','c','d']] + for m in msgs: snd.send(m) + snd.close() + + for expected in msgs: + rcv = self.ssn.receiver(name) + msg = rcv.fetch(0) + assert msg.content == expected.content, (msg.content, expected.content) + self.ssn.acknowledge() + rcv.close() #close after each message to ensure queue isn't deleted with messages in it + self.ssn.close() + self.conn.close() + + matched = [q for q in agent.list("Queue") if q['name'] == name] + if autodeleted: + # ensure that queue is no longer there (as empty and unused) + assert len(matched) == 0, (matched) + else: + # ensure that queue is still there though empty and unused + assert len(matched) == 1, (matched) + finally: + agent.delete('QueuePolicy', pattern) + mgmt.close() + + def test_queue(self): + self.do_simple_queue_test("queue-*", "queue-1") + + def test_queue_not_autodeleted(self): + self.do_simple_queue_test("permanent-queue-*", "permanent-queue-1", {'auto-delete':False}, False) + + def test_queue_manual_delete(self): + self.do_simple_queue_test("permanent-queue-*", "permanent-queue-1", {'qpid.lifetime-policy':'manual'}, False) + + def test_queue_delete_if_unused_and_empty(self): + self.do_simple_queue_test("queue-*", "queue-1", {'qpid.lifetime-policy':'delete-if-unused-and-empty'}, True) + + def do_simple_topic_test(self, pattern, name, properties={}, autodeleted=True): + mgmt = self.create_connection("amqp0-10", True) + agent = Mgmt(mgmt) + agent.create('TopicPolicy', pattern, properties) + try: + snd = self.ssn.sender(name) + rcv1 = self.ssn.receiver(name) + rcv2 = self.ssn.receiver(name) + + msgs = [Message(content=s, subject = s) for s in ['a','b','c','d']] + for m in msgs: snd.send(m) + + for rcv in [rcv1, rcv2]: + for expected in msgs: + msg = rcv.fetch(0) + assert msg.content == expected.content, (msg.content, expected.content) + self.ssn.acknowledge() + rcv1.close() + rcv2.close() + snd.close() + + matched = [e for e in agent.list("Exchange") if e['name'] == name] + if autodeleted: + # ensure that exchange is no longer there (as it is now unused) + assert len(matched) == 0, (matched) + else: + # ensure that exchange has not been autodeleted in spite of being unused + assert len(matched) == 1, (matched) + finally: + agent.delete('TopicPolicy', pattern) + mgmt.close() + + def test_topic(self): + self.do_simple_topic_test('fanout-*', 'fanout-1', {'exchange-type':'fanout'}) + + def test_topic_not_autodelete(self): + self.do_simple_topic_test('permanent-fanout-*', 'permanent-fanout-1', {'exchange-type':'fanout', 'auto-delete':False}, False) + + def test_topic_manual_delete(self): + self.do_simple_topic_test('permanent-fanout-*', 'permanent-fanout-1', {'exchange-type':'fanout', 'qpid.lifetime-policy':'manual'}, False) + + def test_topic_delete_if_unused(self): + self.do_simple_topic_test('fanout-*', 'fanout-1', {'exchange-type':'fanout', 'qpid.lifetime-policy':'delete-if-unused'}, True) + + def test_mgmt(self): + mgmt = self.create_connection("amqp0-10", True) + agent = Mgmt(mgmt) + agent.create('QueuePolicy', 'queue-*') + agent.create('QueuePolicy', 'alt.queue.*') + agent.create('TopicPolicy', 'topic-*') + try: + queues = [q['name'] for q in agent.list("QueuePolicy")] + topics = [t['name'] for t in agent.list("TopicPolicy")] + assert 'queue-*' in queues, (queues) + assert 'alt.queue.*' in queues, (queues) + + try: + agent.delete('TopicPolicy', 'queue-*') + assert False, ('Deletion of policy using wrong type should fail') + except: None + + finally: + agent.delete('QueuePolicy', 'queue-*') + agent.delete('QueuePolicy', 'alt.queue.*') + agent.delete('TopicPolicy', 'topic-*') + mgmt.close() diff --git a/qpid/cpp/src/tests/policy.acl b/qpid/cpp/src/tests/policy.acl new file mode 100644 index 0000000000..4c13ac75c1 --- /dev/null +++ b/qpid/cpp/src/tests/policy.acl @@ -0,0 +1,20 @@ +# +# 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. +# + +acl allow all all diff --git a/qpid/cpp/src/tests/publish.cpp b/qpid/cpp/src/tests/publish.cpp new file mode 100644 index 0000000000..3f456e7588 --- /dev/null +++ b/qpid/cpp/src/tests/publish.cpp @@ -0,0 +1,135 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include <algorithm> +#include <iostream> +#include <memory> +#include <sstream> +#include <vector> + +#include "TestOptions.h" +#include "qpid/client/Connection.h" +#include "qpid/client/Message.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/client/SubscriptionManager.h" + +using namespace qpid; +using namespace qpid::client; +using namespace qpid::sys; +using namespace std; + +namespace qpid { +namespace tests { + +typedef vector<string> StringSet; + +struct Args : public qpid::TestOptions { + uint size; + uint count; + bool durable; + string destination; + string routingKey; + bool summary; + bool id; + + Args() : size(256), count(1000), durable(true), routingKey("publish-consume"), summary(false), id(false) { + addOptions() + ("size", optValue(size, "N"), "message size") + ("count", optValue(count, "N"), "number of messages to publish") + ("durable", optValue(durable, "yes|no"), "use durable messages") + ("destination", optValue(destination, "<exchange name>"), "destination to publish to") + ("routing-key", optValue(routingKey, "<key>"), "routing key to publish with") + ("summary,s", optValue(summary), "Output only the rate.") + ("id", optValue(id), "Add unique correlation ID"); + } +}; + +Args opts; + +struct Client +{ + Connection connection; + AsyncSession session; + + Client() + { + opts.open(connection); + session = connection.newSession(); + } + + // Cheap hex calculation, avoid expensive ostrstream and string + // creation to generate correlation ids in message loop. + char hex(char i) { return i<10 ? '0'+i : 'A'+i-10; } + void hex(char i, string& s) { + s[0]=hex(i>>24); s[1]=hex(i>>16); s[2]=hex(i>>8); s[3]=i; + } + + void publish() + { + AbsTime begin=now(); + Message msg(string(opts.size, 'X'), opts.routingKey); + string correlationId = "0000"; + if (opts.durable) + msg.getDeliveryProperties().setDeliveryMode(framing::PERSISTENT); + + for (uint i = 0; i < opts.count; i++) { + if (opts.id) { + hex(i+1, correlationId); + msg.getMessageProperties().setCorrelationId(correlationId); + } + session.messageTransfer(arg::destination=opts.destination, + arg::content=msg, + arg::acceptMode=1); + } + session.sync(); + AbsTime end=now(); + double secs(double(Duration(begin,end))/TIME_SEC); + if (opts.summary) cout << opts.count/secs << endl; + else cout << "Time: " << secs << "s Rate: " << opts.count/secs << endl; + } + + ~Client() + { + try{ + session.close(); + connection.close(); + } catch(const exception& e) { + cout << e.what() << endl; + } + } +}; + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char** argv) +{ + try { + opts.parse(argc, argv); + Client client; + client.publish(); + return 0; + } catch(const exception& e) { + cout << e.what() << endl; + } + return 1; +} diff --git a/qpid/cpp/src/tests/python_tests b/qpid/cpp/src/tests/python_tests new file mode 100755 index 0000000000..a36839a43c --- /dev/null +++ b/qpid/cpp/src/tests/python_tests @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Run the python tests. +source $QPID_TEST_COMMON +ensure_python_tests +QPID_PORT=${QPID_PORT:-5672} +PYTHON_TESTS=${PYTHON_TESTS:-$*} +FAILING=${FAILING:-/dev/null} + +if [ ! -d $QPID_TESTS ]; then + echo "SKIPPED python tests: test code not found" + exit 0 +fi + +python $QPID_PYTHON_TEST -m qpid_tests.broker_0_10 -m qpid.tests -b localhost:$QPID_PORT -I $FAILING $PYTHON_TESTS || exit 1 diff --git a/qpid/cpp/src/tests/python_tests.ps1 b/qpid/cpp/src/tests/python_tests.ps1 new file mode 100644 index 0000000000..f7caa8f75a --- /dev/null +++ b/qpid/cpp/src/tests/python_tests.ps1 @@ -0,0 +1,42 @@ +# +# 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. +# + +# Run the python tests; intended to be run by run_test.ps1 which sets up +# QPID_PORT +$srcdir = Split-Path $myInvocation.InvocationName +$PYTHON_DIR = "$srcdir\..\..\..\python" +if (!(Test-Path $PYTHON_DIR -pathType Container)) { + "Skipping python tests as python libs not found" + exit 1 +} + +. .\test_env.ps1 + +if (Test-Path env:FAILING) { + $fails = "-I $env:FAILING" +} +if (Test-Path env:PYTHON_TESTS) { + $tests = "$env:PYTHON_TESTS" +} +else { + $tests = "$args" +} + +python $PYTHON_DIR/qpid-python-test -m qpid_tests.broker_0_10 -m qpid.tests -b localhost:$env:QPID_PORT $fails $tests +exit $LASTEXITCODE diff --git a/qpid/cpp/src/tests/qpid-analyze-trace b/qpid/cpp/src/tests/qpid-analyze-trace new file mode 100755 index 0000000000..009fbc441c --- /dev/null +++ b/qpid/cpp/src/tests/qpid-analyze-trace @@ -0,0 +1,258 @@ +#!/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. +# + +import sys +from datetime import datetime +from optparse import OptionParser + +# Version of this tool software +MAJOR_VERSION = 1 +MINOR_VERSION = 1 +# === Version history === +# 2011-11-16 1.1: Bugfixs: +# QPID-3623 - Incorrect handling of transactions +# QPID-3624 - Replace argparse lib with optparse so tool can be used on Python 2.6. +# 2011-11-07 1.0: Initial checkin +# QPID-3579: Initial version checked in + + +# AMQP 0-10 commands - these increment the command counter +EXEC_COMMANDS = ["ExecutionSync", "ExecutionResult", "ExecutionException", "MessageTransfer", "MessageAccept", + "MessageReject", "MessageRelease", "MessageAcquire", "MessageResume", "MessageSubscribe", + "MessageCancel", "MessageSetFlowMode", "MessageFlow", "MessageFlush", "MessageStop", "TxSelect", + "TxCommit", "TxRollback", "DtxSelect", "DtxStart", "DtxEnd", "DtxCommit", "DtxForget", "DtxGetTimeout", + "DtxPrepare", "DtxRecover", "DtxRollback", "DtxSetTimeout", "ExchangeDeclare", "ExchangeDelete", + "ExchangeQuery", "ExchangeBind", "ExchangeUnbind", "ExchangeBound", "QueueDeclare", "QueueDelete", + "QueuePurge", "QueueQuery", "FileQos", "FileQosOk", "FileConsume", "FileConsumeOk", "FileCancel", + "FileOpen", "FileOpenOk", "FileStage", "FilePublish", "FileReturn", "FileDeliver", "FileAck", + "FileReject", "StreamQos", "StreamQosOk", "StreamConsume", "StreamConsumeOk", "StreamCancel", + "StreamPublish", "StreamReturn", "StreamDeliver"] +HEADER_STR = " -line ----------timestamp -----------connection ssn recv send- txn-- operation---------->" + +PROGRESS_LINES_PER_DOT = 100000 + +class LogLevel: + CRITICAL = (1, "critical") + ERROR = (2, "error") + WARNING = (3, "warning") + NOTICE = (4, "notice") + INFO = (5, "info") + DEBUG = (6, "debug") + TRACE = (7, "trace") + @staticmethod + def get_level(level): + if level == LogLevel.CRITICAL[1]: return LogLevel.CRITICAL + if level == LogLevel.ERROR[1]: return LogLevel.ERROR + if level == LogLevel.WARNING[1]: return LogLevel.WARNING + if level == LogLevel.NOTICE[1]: return LogLevel.NOTICE + if level == LogLevel.INFO[1]: return LogLevel.INFO + if level == LogLevel.DEBUG[1]: return LogLevel.DEBUG + if level == LogLevel.TRACE[1]: return LogLevel.TRACE + raise Exception("Unknown log level: %s" % level) + +class LogLine: + def __init__(self, line_no, line): + self.line_no = line_no + self.timestamp = datetime.strptime(line[:19], "%Y-%m-%d %H:%M:%S") + self.level = LogLevel.get_level(line[20:].split(" ")[0]) + self.line = line[21 + len(self.level[1]):].strip() + self.cmd_cnt = None + self.txn_cnt = None + def __str__(self): + if self.contains("RECV"): cnt_str = "R" + else: cnt_str = " S" + if self.cmd_cnt is not None: cnt_str += str(self.cmd_cnt) + set_index = self.find("{") + header_index = self.find("header") + content_index = self.find("content") + if self.txn_cnt is None: + txn_cnt_str = "" + else: + txn_cnt_str = "T%d" % self.txn_cnt + if header_index != -1 and header_index < set_index: op_str = " + " + self.line[header_index:self.line.rfind("]")] + elif content_index != -1 and set_index == -1: op_str = " + " + self.line[content_index:self.line.rfind("]")] + else: op_str = self.line[set_index+1:self.line.rfind("}")] + return " %7d %19s %22s %3d %-10s %-5s %s" % (self.line_no, self.timestamp.isoformat(" "), + self.get_identifier_remote_addr(), self.get_channel(), + cnt_str, txn_cnt_str, op_str) + def contains(self, string): + return self.line.find(string) != -1 + def find(self, string): + return self.line.find(string) + def get_channel(self): + return int(self.get_named_value("channel")) + def get_identifier(self): + return self.line.partition("[")[2].partition("]")[0] + def get_identifier_remote_addr(self): + return self.get_identifier().partition("-")[2] + def get_named_value(self, name): + return self.line.partition("%s=" % name)[2].partition(";")[0] + def get_msg_accept_range(self): + str_nums = self.get_named_value("transfers").strip(" {[]}").split(",") + return range(int(str_nums[0]), int(str_nums[1]) + 1) + def is_log_level(self, level): + if self.level is None: return None + return level[0] == self.level[0] + def is_frame(self): + return self.contains("Frame[") + +class ConnectionProperty: + def __init__(self, line): + self.addr = line.get_identifier_remote_addr() + self.channel = line.get_channel() + self.ops = [line] + def add_op(self, line): + self.ops.append(line) + +class Connection(ConnectionProperty): + def __init__(self, line): + ConnectionProperty.__init__(self, line) + self.session_list = [] # Keeps session creation order + self.session_dict = {} # For looking up by channel no. + def __str__(self): + return "Connection %s (ops=%d; sessions=%d):" % (self.addr, len(self.ops), len(self.session_dict)) + def add_session(self, session): + self.session_list.append(session) + self.session_dict[session.channel] = session + def get_session(self, channel): + return self.session_dict[channel] + +class Session(ConnectionProperty): + def __init__(self, line): + ConnectionProperty.__init__(self, line) + self.name = line.get_named_value("name") + self.send_cnt = 0 + self.recv_cnt = 0 + self.txn_flag = False + self.txn_cnt = 0 + self.recv_cmds = {} # For looking up by cmd no + self.send_cmds = {} # For looking up by cmd no + def __str__(self): + if self.txn_flag: + return " + Session %d (name=%s send-cmds=%d recv-cmds=%d txns=%d):" % (self.channel, self.name, + self.send_cnt, self.recv_cnt, + self.txn_cnt) + return " + Session %d (name=%s send-cmds=%d recv-cmds=%d non-txn):" % (self.channel, self.name, self.send_cnt, + self.recv_cnt) + def incr_recv_cnt(self, line): + self.recv_cmds[self.recv_cnt] = line + self.recv_cnt += 1 + def incr_send_cnt(self, line): + self.send_cmds[self.send_cnt] = line + self.send_cnt += 1 + def set_send_txn_cnt(self, cmd): + self.send_cmds[cmd].txn_cnt = self.txn_cnt + +class TraceAnalysis: + def __init__(self): + self.connection_list = [] # Keeps connection creation order + self.connection_dict = {} # For looking up by connection address + parser = OptionParser(usage="%prog [options] trace-file", version="%%prog %d.%d" % (MAJOR_VERSION, MINOR_VERSION), + description="A tool to structure and display Qpid broker trace logs.") + parser.add_option("--connection-summary", action="store_true", default=False, dest="connection_summary", + help="Hide connection details, provide one-line summary") + parser.add_option("--session-summary", action="store_true", default=False, dest="session_summary", + help="Hide session details, provide one-line summary") + parser.add_option("--summary", "-s", action="store_true", default=False, dest="summary", + help="Hide both connection and session details. Equivalent to --connection-summary and" + "--session-summary") + self.opts, self.args = parser.parse_args() + if len(self.args) == 0: raise Exception("Missing trace-file argument") + def analyze_trace(self): + lcnt = 0 + print "Reading trace file %s:" % self.args[0] + log_file = open(self.args[0], "r") + try: + for fline in log_file: + lcnt += 1 + try: + lline = LogLine(lcnt, fline) + if lline.is_log_level(LogLevel.TRACE) and lline.is_frame(): + if lline.contains("{ConnectionStartBody"): + conn = Connection(lline) + self.connection_list.append(conn) + self.connection_dict[conn.addr] = conn + elif lline.contains("{Connection"): + self.connection_dict[lline.get_identifier_remote_addr()].add_op(lline) + elif lline.contains("{SessionAttachBody"): + ssn = Session(lline) + self.connection_dict[ssn.addr].add_session(ssn) + else: + ssn = self.connection_dict[lline.get_identifier_remote_addr()].get_session(lline.get_channel()) + ssn.add_op(lline) + if lline.line[lline.find("{") + 1 : lline.find("Body")] in EXEC_COMMANDS: + if lline.contains("RECV"): + lline.cmd_cnt = ssn.recv_cnt + if ssn.txn_flag: + if lline.contains("MessageAcceptBody"): + lline.txn_cnt = ssn.txn_cnt + for cmd in lline.get_msg_accept_range(): + ssn.set_send_txn_cnt(cmd) + if lline.contains("MessageTransferBody"): lline.txn_cnt = ssn.txn_cnt + ssn.incr_recv_cnt(lline) + elif lline.contains("SEND") or lline.contains("SENT"): + lline.cmd_cnt = ssn.send_cnt + ssn.incr_send_cnt(lline) + # TODO: This treatment will probably break down for DTX + if lline.contains("xSelectBody"): + ssn.txn_flag = True + elif lline.contains("xCommitBody") or lline.contains("xRollbackBody"): + lline.txn_cnt = ssn.txn_cnt + ssn.txn_cnt += 1 + except KeyboardInterrupt, e: raise e + except: pass + if (lcnt + 1) % PROGRESS_LINES_PER_DOT == 0: + sys.stdout.write(".") + sys.stdout.flush() + finally: log_file.close() + if lcnt > PROGRESS_LINES_PER_DOT: print + print "Read and analyzed", lcnt, "lines." + def print_analysis(self): + if len(self.connection_list) > 0: + for c in self.connection_list: + print + print c + if not self.opts.connection_summary and not self.opts.summary: + print HEADER_STR + for o in c.ops: + print o + for s in c.session_list: + print s + if not self.opts.session_summary and not self.opts.summary: + print HEADER_STR + for o in s.ops: + print o + else: + print "No trace-level entries found in log." + +def check_python_version(major, minor, micro): + if sys.version_info < (major, minor, micro): + print "Incorrect Python version: %s found; >= %d.%d.%d needed." % (sys.version.split()[0], major, minor, micro) + sys.exit(-1) + +# === Main program === + +if __name__ == '__main__': + check_python_version(2, 4, 0) + t = TraceAnalysis() + t.analyze_trace() + t.print_analysis() +
\ No newline at end of file diff --git a/qpid/cpp/src/tests/qpid-build-rinstall b/qpid/cpp/src/tests/qpid-build-rinstall new file mode 100755 index 0000000000..beff7dffba --- /dev/null +++ b/qpid/cpp/src/tests/qpid-build-rinstall @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under onemake +# 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. +# + +# Run "make install"" locally then copy the install tree to each of $HOSTS +# Must be run in a configured qpid build directory. +# +test -f config.status || { echo "Not in a configured build directory."; usage; } +. src/tests/install_env.sh +set -ex +make && make -j1 install +rsynchosts $QPID_INSTALL_PREFIX diff --git a/qpid/cpp/src/tests/qpid-client-test.cpp b/qpid/cpp/src/tests/qpid-client-test.cpp new file mode 100644 index 0000000000..9198324f93 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-client-test.cpp @@ -0,0 +1,139 @@ +/* + * + * 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. + * + */ + +/** + * This file provides a simple test (and example) of basic + * functionality including declaring an exchange and a queue, binding + * these together, publishing a message and receiving that message + * asynchronously. + */ + +#include <iostream> + +#include "TestOptions.h" +#include "qpid/client/Connection.h" +#include "qpid/client/Message.h" +#include "qpid/client/Session.h" +#include "qpid/client/SubscriptionManager.h" + + +using namespace qpid; +using namespace qpid::client; +using namespace qpid::framing; +using std::string; + +namespace qpid { +namespace tests { + +struct Args : public TestOptions { + uint msgSize; + bool verbose; + + Args() : TestOptions("Simple test of Qpid c++ client; sends and receives a single message."), msgSize(26), verbose(false) + { + addOptions() + ("size", optValue(msgSize, "N"), "message size") + ("verbose", optValue(verbose), "print out some status messages"); + } +}; + +const std::string chars("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); + +std::string generateData(uint size) +{ + if (size < chars.length()) { + return chars.substr(0, size); + } + std::string data; + for (uint i = 0; i < (size / chars.length()); i++) { + data += chars; + } + data += chars.substr(0, size % chars.length()); + return data; +} + +void print(const std::string& text, const Message& msg) +{ + std::cout << text; + if (msg.getData().size() > 16) { + std::cout << msg.getData().substr(0, 16) << "..."; + } else { + std::cout << msg.getData(); + } + std::cout << std::endl; +} + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char** argv) +{ + try { + Args opts; + opts.parse(argc, argv); + + //Connect to the broker: + Connection connection; + opts.open(connection); + if (opts.verbose) std::cout << "Opened connection." << std::endl; + + //Create and open a session on the connection through which + //most functionality is exposed: + Session session = connection.newSession(); + if (opts.verbose) std::cout << "Opened session." << std::endl; + + + //'declare' the exchange and the queue, which will create them + //as they don't exist + session.exchangeDeclare(arg::exchange="MyExchange", arg::type="direct"); + if (opts.verbose) std::cout << "Declared exchange." << std::endl; + session.queueDeclare(arg::queue="MyQueue", arg::autoDelete=true, arg::exclusive=true); + if (opts.verbose) std::cout << "Declared queue." << std::endl; + + //now bind the queue to the exchange + session.exchangeBind(arg::exchange="MyExchange", arg::queue="MyQueue", arg::bindingKey="MyKey"); + if (opts.verbose) std::cout << "Bound queue to exchange." << std::endl; + + //create and send a message to the exchange using the routing + //key we bound our queue with: + Message msgOut(generateData(opts.msgSize)); + msgOut.getDeliveryProperties().setRoutingKey("MyKey"); + session.messageTransfer(arg::destination="MyExchange", arg::content=msgOut, arg::acceptMode=1); + if (opts.verbose) print("Published message: ", msgOut); + + // Using the SubscriptionManager, get the message from the queue. + SubscriptionManager subs(session); + Message msgIn = subs.get("MyQueue"); + if (msgIn.getData() == msgOut.getData()) + if (opts.verbose) std::cout << "Received the exepected message." << std::endl; + + //close the session & connection + session.close(); + if (opts.verbose) std::cout << "Closed session." << std::endl; + connection.close(); + if (opts.verbose) std::cout << "Closed connection." << std::endl; + return 0; + } catch(const std::exception& e) { + std::cout << e.what() << std::endl; + } + return 1; +} diff --git a/qpid/cpp/src/tests/qpid-cluster-benchmark b/qpid/cpp/src/tests/qpid-cluster-benchmark new file mode 100755 index 0000000000..f20ac6ac30 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-cluster-benchmark @@ -0,0 +1,64 @@ +#!/bin/sh +# +# 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. +# + +# Benchmark script for comparing cluster performance. + +# Default options +MESSAGES="-m 10000" +REPEAT="--repeat 10" +QUEUES="-q 6" +SENDERS="-s 3" +RECEIVERS="-r 3" +BROKERS= # Local broker +CLIENT_HOSTS= # No ssh, all clients are local +# Connection options +TCP_NODELAY=false +RECONNECT=true +HEARTBEAT=1 + +while getopts "m:f:n:b:q:s:r:c:h:i:txyv-" opt; do + case $opt in + b) BROKERS="-b $OPTARG";; + c) CLIENT_HOSTS="-c $OPTARG";; + h) HEARTBEAT=$OPTARG;; + i) RECONNECT=$OPTARG;; + m) MESSAGES="-m $OPTARG";; + n) REPEAT="--repeat $OPTARG";; + q) QUEUES="-q $OPTARG";; + r) RECEIVERS="-r $OPTARG";; + s) SENDERS="-s $OPTARG";; + t) TCP_NODELAY=true;; + v) OPTS="--verbose";; + x) SAVE_RECEIVED="--save-received";; + y) NO_DELETE="--no-delete";; + -) break ;; + *) echo "Unknown option"; exit 1;; + esac +done +shift $(($OPTIND-1)) + +CONNECTION_OPTIONS="--connection-options {tcp-nodelay:$TCP_NODELAY,reconnect:$RECONNECT,heartbeat:$HEARTBEAT}" + +BROKER=$(echo $BROKERS | sed s/,.*//) +run_test() { echo $*; shift; "$@"; echo; echo; echo; } + +OPTS="$OPTS $REPEAT $BROKERS --summarize $QUEUES $SENDERS $RECEIVERS $MESSAGES $CLIENT_HOSTS $SAVE_RECEIVED $CONNECTION_OPTIONS $NO_DELETE" + +run_test "Benchmark:" qpid-cpp-benchmark $OPTS "$@" diff --git a/qpid/cpp/src/tests/qpid-cpp-benchmark b/qpid/cpp/src/tests/qpid-cpp-benchmark new file mode 100755 index 0000000000..2d5ec711fe --- /dev/null +++ b/qpid/cpp/src/tests/qpid-cpp-benchmark @@ -0,0 +1,363 @@ +#!/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. +# + +import optparse, time, re, os + +try: + import qpid_messaging as qm +except ImportError: + qpid_messaging = None + import qpid.messaging as qm + +from threading import Thread +from subprocess import Popen, PIPE, STDOUT + +op = optparse.OptionParser(usage="usage: %prog [options]", + description="simple performance benchmarks") +op.add_option("-b", "--broker", default=[], action="append", type="str", + help="url of broker(s) to connect to, round robin on multiple brokers") +op.add_option("-c", "--client-host", default=[], action="append", type="str", + help="host(s) to run clients on via ssh, round robin on mulple hosts") +op.add_option("-q", "--queues", default=1, type="int", metavar="N", + help="create N queues (default %default)") +op.add_option("-s", "--senders", default=1, type="int", metavar="N", + help="start N senders per queue (default %default)") +op.add_option("-r", "--receivers", default=1, type="int", metavar="N", + help="start N receivers per queue (default %default)") +op.add_option("-m", "--messages", default=100000, type="int", metavar="N", + help="send N messages per sender (default %default)") +op.add_option("--queue-name", default="benchmark", metavar="NAME", + help="base name for queues (default %default)") +op.add_option("--send-rate", default=0, metavar="N", + help="send rate limited to N messages/second, 0 means no limit (default %default)") +op.add_option("--receive-rate", default=0, metavar="N", + help="receive rate limited to N messages/second, 0 means no limit (default %default)") +op.add_option("--content-size", default=1024, type="int", metavar="BYTES", + help="message size in bytes (default %default)") +op.add_option("--ack-frequency", default=100, metavar="N", type="int", + help="receiver ack's every N messages, 0 means unconfirmed (default %default)") +op.add_option("--tx", default=0, metavar="N", type="int", + help="Transaction batch size, 0 means no transactions") +op.add_option("--no-report-header", dest="report_header", default=True, + action="store_false", help="don't print header on report") +op.add_option("--summarize", default=False, action="store_true", + help="print summary statistics for multiple senders/receivers: total throughput, average latency") +op.add_option("--repeat", default=1, metavar="N", help="repeat N times", type="int") +op.add_option("--send-option", default=[], action="append", type="str", + help="Additional option for sending addresses") +op.add_option("--receive-option", default=[], action="append", type="str", + help="Additional option for receiving addresses") +op.add_option("--create-option", default=[], action="append", type="str", + help="Additional option for creating addresses") +op.add_option("--send-arg", default=[], action="append", type="str", + help="Additional argument for qpid-send") +op.add_option("--receive-arg", default=[], action="append", type="str", + help="Additional argument for qpid-receive") +op.add_option("--no-timestamp", dest="timestamp", default=True, + action="store_false", help="don't add a timestamp, no latency results") +op.add_option("--sequence", dest="sequence", default=False, + action="store_true", help="add a sequence number to each message") +op.add_option("--connection-options", type="str", + help="Connection options for senders & receivers") +op.add_option("--durable", default=False, action="store_true", + help="Use durable queues and messages") +op.add_option("-t", "--timeout", default=1.0, type="float", metavar="SECONDS", + help="Timeout for fetch operations (default %default)") +op.add_option("--save-received", default=False, action="store_true", + help="Save received message content to files <queuename>-receiver-<n>.msg") +op.add_option("--verbose", default=False, action="store_true", + help="Show commands executed") +op.add_option("--fill-drain", default=False, action="store_true", + help="First fill the queues, then drain them") +op.add_option("--qpid-send-path", default="", type="str", metavar="PATH", + help="path to qpid-send binary") +op.add_option("--qpid-receive-path", default="", type="str", metavar="PATH", + help="path to qpid-receive binary") + +single_quote_re = re.compile("'") +def posix_quote(string): + """ Quote a string for use as an argument in a posix shell""" + return "'" + single_quote_re.sub("\\'", string) + "'"; + +def ssh_command(host, command): + """ Convert command into an ssh command on host with quoting""" + return ["ssh", host] + [posix_quote(arg) for arg in command] + +class Clients: + def __init__(self): self.clients=[] + + def add(self, client): + self.clients.append(client) + return client + + def kill(self): + for c in self.clients: + try: c.kill() + except: pass + +class PopenCommand(Popen): + """Like Popen but you can query for the command""" + def __init__(self, command, *args, **kwargs): + self.command = command + Popen.__init__(self, command, *args, **kwargs) + +clients = Clients() + +def start_receive(queue, index, opts, ready_queue, broker, host): + address_opts=opts.receive_option + if opts.durable: address_opts += ["node:{durable:true}"] + address="%s;{%s}"%(queue,",".join(address_opts)) + msg_total=opts.senders*opts.messages + messages = msg_total/opts.receivers; + if (index < msg_total%opts.receivers): messages += 1 + if (messages == 0): return None + command = [os.path.join(opts.qpid_receive_path, "qpid-receive"), + "-b", broker, + "-a", address, + "-m", str(messages), + "--forever", + "--print-content=no", + "--receive-rate", str(opts.receive_rate), + "--report-total", + "--ack-frequency", str(opts.ack_frequency), + "--ready-address", "%s;{create:always}"%ready_queue, + "--report-header=no", + "--tx=%s" % opts.tx + ] + if opts.save_received: + command += ["--save-content=%s-receiver-%s.msg"%(queue,index)] + command += opts.receive_arg + if opts.connection_options: + command += ["--connection-options",opts.connection_options] + if host: command = ssh_command(host, command) + if opts.verbose: print "Receiver: ", command + return clients.add(PopenCommand(command, stdout=PIPE, stderr=PIPE)) + +def start_send(queue, opts, broker, host): + address="%s;{%s}"%(queue,",".join(opts.send_option + ["create:always"])) + command = [os.path.join(opts.qpid_send_path, "qpid-send"), + "-b", broker, + "-a", address, + "--messages", str(opts.messages), + "--content-size", str(opts.content_size), + "--send-rate", str(opts.send_rate), + "--report-total", + "--report-header=no", + "--timestamp=%s"%(opts.timestamp and "yes" or "no"), + "--sequence=%s"%(opts.sequence and "yes" or "no"), + "--durable=%d" % opts.durable, + "--tx=%s" % opts.tx + ] + command += opts.send_arg + if opts.connection_options: + command += ["--connection-options",opts.connection_options] + if host: command = ssh_command(host, command) + if opts.verbose: print "Sender: ", command + return clients.add(PopenCommand(command, stdout=PIPE, stderr=PIPE)) + +def error_msg(out, err): + return ("\n[stdout]\n%s\n[stderr]\n%s[end]"%(out, err)) + +def first_line(p): + out,err=p.communicate() + if p.returncode != 0: + raise Exception("Process exit %d: %s"%(p.returncode, error_msg(out,err))) + return out.split("\n")[0] + +def connect(broker, opts): + if opts.connection_options: + copts = dict([kv.strip().split(":") for kv in opts.connection_options.strip("{}").split(",")]) + else: + copts = {} + return qm.Connection.establish(broker, **copts) + +def drain(queue, session, opts): + """ + Drain a queue to make sure it is empty. Throw away the messages. + """ + if opts.verbose: print "Draining", queue + r = session.receiver(queue, capacity=1000) + n = 0 + try: + while True: + # FIXME aconway 2014-11-21: activemq broker does not respect the drain flag + # so fetch on an empty queue will hang forever, use get with timeout instead. + # r.fetch(timeout=0) + m = qm.Message() + r.get(timeout=opts.timeout) + n += 1 + if n % 500 == 0: r.session.acknowledge() + r.session.acknowledge() + except qm.Empty: + pass + r.close() + if opts.verbose: print "Drained", queue, n + +def clear_queues(queues, brokers, opts): + c = connect(brokers[0], opts) + for q in queues: + s = c.session() + need_drain = False + try: + s.sender("%s;{delete:always}"%(q)).close() + if opts.verbose: print "Deleted", q + except qm.NotFound: + s = c.session() + except qm.AddressError: + need_drain = True # AMQP 1.0 does not support delete, drain instead. + s = c.session() + address_opts = ["create:always"] + if opts.durable: address_opts += ["node:{durable:true}"] + address = "%s;{%s}"%(q, ",".join(opts.create_option + address_opts)) + if opts.verbose: print "Declaring", address + s.sender(address) + if need_drain: drain(q, s, opts) + c.close() + +def print_header(timestamp): + if timestamp: latency_header="\tl-min\tl-max\tl-avg\ttotal-tp" + else: latency_header="" + print "send-tp\trecv-tp%s"%latency_header + +def parse(parser, lines): # Parse sender/receiver output + return [map(lambda p: p[0](p[1]), zip(parser,line.split())) for line in lines] + +def parse_senders(senders): + return parse([int],[first_line(p) for p in senders]) + +def parse_receivers(receivers): + return parse([int,float,float,float],[first_line(p) for p in receivers if p]) + +def print_data(send_stats, recv_stats, total_tp): + for send,recv in map(None, send_stats, recv_stats): + line="" + if send: line += "%d"%send[0] + if recv: + line += "\t%d"%recv[0] + if len(recv) == 4: line += "\t%.2f\t%.2f\t%.2f"%tuple(recv[1:]) + if total_tp is not None: + line += "\t%d"%total_tp + total_tp = None + print line + +def print_summary(send_stats, recv_stats, total_tp): + def avg(s): sum(s) / len(s) + send_tp = sum([l[0] for l in send_stats]) + recv_tp = sum([l[0] for l in recv_stats]) + summary = "%d\t%d"%(send_tp, recv_tp) + if recv_stats and len(recv_stats[0]) == 4: + l_min = sum(l[1] for l in recv_stats)/len(recv_stats) + l_max = sum(l[2] for l in recv_stats)/len(recv_stats) + l_avg = sum(l[3] for l in recv_stats)/len(recv_stats) + summary += "\t%.2f\t%.2f\t%.2f"%(l_min, l_max, l_avg) + summary += "\t%d"%total_tp + print summary + + +class ReadyReceiver: + """A receiver for ready messages""" + def __init__(self, queue, broker, opts): + self.connection = connect(broker, opts) + self.receiver = self.connection.session().receiver(queue) + self.receiver.session.sync() + self.timeout=opts.timeout + + def wait(self, receivers): + try: + for i in receivers: self.receiver.fetch(self.timeout) + self.receiver.session.acknowledge() + self.connection.close() + except qm.Empty: + for r in receivers: + if (r.poll() is not None): + out,err=r.communicate() + raise Exception("Receiver error: %s\n%s" % + (" ".join(r.command), error_msg(out,err))) + raise Exception("Timed out waiting for receivers to be ready") + +def flatten(l): + return sum(map(lambda s: re.split(re.compile("\s*,\s*|\s+"), s), l), []) + +class RoundRobin: + def __init__(self,items): + self.items = items + self.index = 0 + + def next(self): + if not self.items: return None + ret = self.items[self.index] + self.index = (self.index+1)%len(self.items) + return ret + +def main(): + opts, args = op.parse_args() + opts.client_host = flatten(opts.client_host) + if not opts.broker: + if opts.client_host: + raise Exception("--broker must be specified if --client_host is.") + opts.broker = ["127.0.0.1"] # Deafult to local broker + opts.broker = flatten(opts.broker) + brokers = RoundRobin(opts.broker) + client_hosts = RoundRobin(opts.client_host) + send_out = "" + receive_out = "" + ready_queue="%s-ready"%(opts.queue_name) + queues = ["%s-%s"%(opts.queue_name, i) for i in xrange(opts.queues)] + try: + for i in xrange(opts.repeat): + clear_queues(queues+[ready_queue], opts.broker, opts) + ready_receiver = ReadyReceiver(ready_queue, opts.broker[0], opts) + + def start_receivers(): + return [ start_receive(q, j, opts, ready_queue, brokers.next(), client_hosts.next()) + for q in queues for j in xrange(opts.receivers) ] + + + def start_senders(): + return [ start_send(q, opts,brokers.next(), client_hosts.next()) + for q in queues for j in xrange(opts.senders) ] + + if opts.report_header and i == 0: print_header(opts.timestamp) + + if opts.fill_drain: + # First fill the queues, then drain them + start = time.time() + senders = start_senders() + for p in senders: p.wait() + receivers = start_receivers() + for p in receivers: p.wait() + else: + # Run senders and receivers in parallel + receivers = start_receivers() + ready_receiver.wait(filter(None, receivers)) # Wait for receivers ready + start = time.time() + senders = start_senders() + for p in senders + receivers: p.wait() + + total_sent = opts.queues * opts.senders * opts.messages + total_tp = total_sent / (time.time()-start) + send_stats=parse_senders(senders) + recv_stats=parse_receivers(receivers) + if opts.summarize: print_summary(send_stats, recv_stats, total_tp) + else: print_data(send_stats, recv_stats, total_tp) + finally: clients.kill() # No strays + +if __name__ == "__main__": main() + diff --git a/qpid/cpp/src/tests/qpid-ctrl b/qpid/cpp/src/tests/qpid-ctrl new file mode 100755 index 0000000000..4246c57898 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-ctrl @@ -0,0 +1,120 @@ +#!/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. +# + +import optparse +from qpid.messaging import * +from qpid.util import URL +from qpid.log import enable, DEBUG, WARN + +def nameval(st): + idx = st.find("=") + if idx >= 0: + name = st[0:idx] + value = st[idx+1:] + else: + name = st + value = None + return name, value + +def list_map_entries(m): + r = "" + for t in m: + r += "%s=%s " % (t, m[t]) + return r + +def get_qmfv2_result(m): + if m.properties['x-amqp-0-10.app-id'] == 'qmf2': + if m.properties['qmf.opcode'] == '_method_response': + return m.content['_arguments'] + elif m.properties['qmf.opcode'] == '_exception': + raise Exception("Error: %s" % list_map_entries(m.content['_values'])) + else: raise Exception("Invalid response received, unexpected opcode: %s" % m) + else: raise Exception("Invalid response received, not a qmfv2 method: %s" % m) + + +parser = optparse.OptionParser(usage="usage: %prog [options] COMMAND ...", + description="Invoke the specified command.") +parser.add_option("-b", "--broker", default="localhost", + help="connect to specified BROKER (default %default)") +parser.add_option("-c", "--class", dest="qmfclass", default="broker", + help="class of object on which command is being invoked (default %default)") +parser.add_option("-p", "--package", default="org.apache.qpid.broker", + help="package of object on which command is being invoked (default %default)") +parser.add_option("-i", "--id", default="amqp-broker", + help="identifier of object on which command is being invoked (default %default)") +parser.add_option("-a", "--address", default="qmf.default.direct/broker", + help="address to send commands to (default %default)") +parser.add_option("-t", "--timeout", type="float", default=5, + help="timeout in seconds to wait for response before exiting (default %default)") +parser.add_option("-v", dest="verbose", action="store_true", + help="enable logging") + +opts, args = parser.parse_args() + +if opts.verbose: + enable("qpid", DEBUG) +else: + enable("qpid", WARN) + +if args: + command = args.pop(0) +else: + parser.error("command is required") + + +conn = Connection(opts.broker) +try: + conn.open() + ssn = conn.session() + snd = ssn.sender(opts.address) + reply_to = "qmf.default.direct/%s; {node: {type: topic}}" % str(uuid4()) + rcv = ssn.receiver(reply_to) + + object_name = "%s:%s:%s" % (opts.package, opts.qmfclass, opts.id) + method_name = command + arguments = {} + for a in args: + name, val = nameval(a) + if val[0] == '{' or val[0] == '[': + arguments[name] = eval(val) + else: + arguments[name] = val + content = { + "_object_id": {"_object_name": object_name}, + "_method_name": method_name, + "_arguments": arguments + } + msg = Message(reply_to=reply_to, content=content) + msg.properties["x-amqp-0-10.app-id"] = "qmf2" + msg.properties["qmf.opcode"] = "_method_request" + snd.send(msg) + + try: + print list_map_entries(get_qmfv2_result(rcv.fetch(timeout=opts.timeout))) + except Empty: + print "No response received!" + except Exception, e: + print e +except ReceiverError, e: + print e +except KeyboardInterrupt: + pass + +conn.close() diff --git a/qpid/cpp/src/tests/qpid-latency-test.cpp b/qpid/cpp/src/tests/qpid-latency-test.cpp new file mode 100644 index 0000000000..a03963467b --- /dev/null +++ b/qpid/cpp/src/tests/qpid-latency-test.cpp @@ -0,0 +1,480 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + + +#include <algorithm> +#include <limits> +#include <iostream> +#include <memory> +#include <sstream> +#include <vector> + +#include "TestOptions.h" +#include "qpid/sys/Thread.h" +#include "qpid/client/Connection.h" +#include "qpid/client/Message.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/sys/Time.h" + +using namespace qpid; +using namespace qpid::client; +using namespace qpid::sys; +using std::string; + +namespace qpid { +namespace tests { + +typedef std::vector<std::string> StringSet; + +struct Args : public qpid::TestOptions { + uint size; + uint count; + uint rate; + bool sync; + uint reportFrequency; + uint timeLimit; + uint concurrentConnections; + uint prefetch; + uint ack; + bool cumulative; + bool csv; + bool durable; + string base; + bool singleConnect; + + Args() : size(256), count(1000), rate(0), reportFrequency(1000), + timeLimit(0), concurrentConnections(1), + prefetch(100), ack(0), + durable(false), base("latency-test"), singleConnect(false) + + { + addOptions() + + ("size", optValue(size, "N"), "message size") + ("concurrentTests", optValue(concurrentConnections, "N"), "number of concurrent test setups, will create another publisher,\ + subcriber, queue, and connections") + ("single-connection", optValue(singleConnect, "yes|no"), "Use one connection for multiple sessions.") + ("count", optValue(count, "N"), "number of messages to send") + ("rate", optValue(rate, "N"), "target message rate (causes count to be ignored)") + ("sync", optValue(sync), "send messages synchronously") + ("report-frequency", optValue(reportFrequency, "N"), + "number of milliseconds to wait between reports (ignored unless rate specified)") + ("time-limit", optValue(timeLimit, "N"), + "test duration, in seconds") + ("prefetch", optValue(prefetch, "N"), "prefetch count (0 implies no flow control, and no acking)") + ("ack", optValue(ack, "N"), "Ack frequency in messages (defaults to half the prefetch value)") + ("durable", optValue(durable, "yes|no"), "use durable messages") + ("csv", optValue(csv), "print stats in csv format (rate,min,max,avg)") + ("cumulative", optValue(cumulative), "cumulative stats in csv format") + ("queue-base-name", optValue(base, "<name>"), "base name for queues"); + } +}; + +const std::string chars("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); + +Args opts; +double c_min, c_avg, c_max; +Connection globalConnection; + +uint64_t current_time() +{ + return Duration::FromEpoch(); +} + +struct Stats +{ + Mutex lock; + uint count; + double minLatency; + double maxLatency; + double totalLatency; + + Stats(); + void update(double l); + void print(); + void reset(); +}; + +class Client : public Runnable +{ +protected: + Connection* connection; + Connection localConnection; + AsyncSession session; + Thread thread; + string queue; + +public: + Client(const string& q); + virtual ~Client(); + + void start(); + void join(); + void run(); + virtual void test() = 0; +}; + +class Receiver : public Client, public MessageListener +{ + SubscriptionManager mgr; + uint count; + Stats& stats; + +public: + Receiver(const string& queue, Stats& stats); + void test(); + void received(Message& msg); + Stats getStats(); + uint getCount() { return count; } + void stop() { mgr.stop(); mgr.cancel(queue); } +}; + + +class Sender : public Client +{ + string generateData(uint size); + void sendByRate(); + void sendByCount(); + Receiver& receiver; + const string data; + +public: + Sender(const string& queue, Receiver& receiver); + void test(); +}; + + +class Test +{ + const string queue; + Stats stats; + Receiver receiver; + Sender sender; + AbsTime begin; + +public: + Test(const string& q) : queue(q), receiver(queue, stats), sender(queue, receiver), begin(now()) {} + void start(); + void join(); + void report(); +}; + + +Client::Client(const string& q) : queue(q) +{ + if (opts.singleConnect){ + connection = &globalConnection; + if (!globalConnection.isOpen()) opts.open(globalConnection); + }else{ + connection = &localConnection; + opts.open(localConnection); + } + session = connection->newSession(); +} + +void Client::start() +{ + thread = Thread(this); +} + +void Client::join() +{ + thread.join(); +} + +void Client::run() +{ + try{ + test(); + } catch(const std::exception& e) { + std::cout << "Error in receiver: " << e.what() << std::endl; + } +} + +Client::~Client() +{ + try{ + session.close(); + connection->close(); + } catch(const std::exception& e) { + std::cout << "Error in receiver: " << e.what() << std::endl; + } +} + +Receiver::Receiver(const string& q, Stats& s) : Client(q), mgr(session), count(0), stats(s) +{ + session.queueDeclare(arg::queue=queue, arg::durable=opts.durable, arg::autoDelete=true); + uint msgCount = session.queueQuery(arg::queue=queue).get().getMessageCount(); + if (msgCount) { + std::cout << "Warning: found " << msgCount << " msgs on " << queue << ". Purging..." << std::endl; + session.queuePurge(arg::queue=queue); + session.sync(); + } + SubscriptionSettings settings; + if (opts.prefetch) { + settings.autoAck = (opts.ack ? opts.ack : (opts.prefetch / 2)); + settings.flowControl = FlowControl::messageWindow(opts.prefetch); + } else { + settings.acceptMode = ACCEPT_MODE_NONE; + settings.flowControl = FlowControl::unlimited(); + } + mgr.subscribe(*this, queue, settings); +} + +void Receiver::test() +{ + mgr.run(); + mgr.cancel(queue); +} + +void Receiver::received(Message& msg) +{ + ++count; + uint64_t receivedAt = current_time(); + uint64_t sentAt = msg.getDeliveryProperties().getTimestamp(); + + stats.update(((double) (receivedAt - sentAt)) / TIME_MSEC); + + if (!opts.rate && count >= opts.count) { + mgr.stop(); + } +} + +void Stats::update(double latency) +{ + Mutex::ScopedLock l(lock); + count++; + minLatency = std::min(minLatency, latency); + maxLatency = std::max(maxLatency, latency); + totalLatency += latency; +} + +Stats::Stats() : count(0), minLatency(std::numeric_limits<double>::max()), maxLatency(0), totalLatency(0) {} + +void Stats::print() +{ + static bool already_have_stats = false; + uint value; + + if (opts.rate) + value = opts.rate; + else + value = opts.count; + Mutex::ScopedLock l(lock); + double aux_avg = (totalLatency / count); + if (!opts.cumulative) { + if (!opts.csv) { + if (count) { + std::cout << "Latency(ms): min=" << minLatency << ", max=" << + maxLatency << ", avg=" << aux_avg; + } else { + std::cout << "Stalled: no samples for interval"; + } + } else { + if (count) { + std::cout << value << "," << minLatency << "," << maxLatency << + "," << aux_avg; + } else { + std::cout << value << "," << minLatency << "," << maxLatency << + ", Stalled"; + } + } + } else { + if (count) { + if (already_have_stats) { + c_avg = (c_min + aux_avg) / 2; + if (c_min > minLatency) c_min = minLatency; + if (c_max < maxLatency) c_max = maxLatency; + } else { + c_avg = aux_avg; + c_min = minLatency; + c_max = maxLatency; + already_have_stats = true; + } + std::cout << value << "," << c_min << "," << c_max << + "," << c_avg; + } else { + std::cout << "Stalled: no samples for interval"; + } + } +} + +void Stats::reset() +{ + Mutex::ScopedLock l(lock); + count = 0; + totalLatency = maxLatency = 0; + minLatency = std::numeric_limits<double>::max(); +} + +Sender::Sender(const string& q, Receiver& receiver) : Client(q), receiver(receiver), data(generateData(opts.size)) {} + +void Sender::test() +{ + if (opts.rate) sendByRate(); + else sendByCount(); +} + +void Sender::sendByCount() +{ + Message msg(data, queue); + if (opts.durable) { + msg.getDeliveryProperties().setDeliveryMode(framing::PERSISTENT); + } + + for (uint i = 0; i < opts.count; i++) { + uint64_t sentAt(current_time()); + msg.getDeliveryProperties().setTimestamp(sentAt); + async(session).messageTransfer(arg::content=msg, arg::acceptMode=1); + if (opts.sync) session.sync(); + } + session.sync(); +} + +void Sender::sendByRate() +{ + Message msg(data, queue); + if (opts.durable) { + msg.getDeliveryProperties().setDeliveryMode(framing::PERSISTENT); + } + uint64_t interval = TIME_SEC/opts.rate; + int64_t timeLimit = opts.timeLimit * TIME_SEC; + uint64_t sent = 0; + AbsTime start = now(); + AbsTime last = start; + while (true) { + AbsTime sentAt=now(); + msg.getDeliveryProperties().setTimestamp(Duration::FromEpoch()); + async(session).messageTransfer(arg::content=msg, arg::acceptMode=1); + if (opts.sync) session.sync(); + ++sent; + if (Duration(last, sentAt) > (opts.reportFrequency*TIME_MSEC)) { + Duration t(start, now()); + //check rate actually achieved thus far + if (t/TIME_SEC) { + uint actualRate = sent / (t/TIME_SEC); + //report inability to stay within 1% of desired rate + if (actualRate < opts.rate && opts.rate - actualRate > opts.rate/100) { + std::cerr << "WARNING: Desired send rate: " << opts.rate << ", actual send rate: " << actualRate << std::endl; + } + } + last = sentAt; + } + + AbsTime waitTill(start, sent*interval); + Duration delay(sentAt, waitTill); + if (delay > 0) + sys::usleep(delay / TIME_USEC); + if (timeLimit != 0 && Duration(start, now()) > timeLimit) { + session.sync(); + receiver.stop(); + break; + } + } +} + +string Sender::generateData(uint size) +{ + if (size < chars.length()) { + return chars.substr(0, size); + } + std::string data; + for (uint i = 0; i < (size / chars.length()); i++) { + data += chars; + } + data += chars.substr(0, size % chars.length()); + return data; +} + + +void Test::start() +{ + receiver.start(); + begin = AbsTime(now()); + sender.start(); +} + +void Test::join() +{ + sender.join(); + receiver.join(); + AbsTime end = now(); + Duration time(begin, end); + double msecs(time / TIME_MSEC); + if (!opts.csv) { + std::cout << "Sent " << receiver.getCount() << " msgs through " << queue + << " in " << msecs << "ms (" << (receiver.getCount() * 1000 / msecs) << " msgs/s) "; + } + stats.print(); + std::cout << std::endl; +} + +void Test::report() +{ + stats.print(); + std::cout << std::endl; + stats.reset(); +} + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char** argv) +{ + try { + opts.parse(argc, argv); + if (opts.cumulative) + opts.csv = true; + + Connection localConnection; + AsyncSession session; + + boost::ptr_vector<Test> tests(opts.concurrentConnections); + for (uint i = 0; i < opts.concurrentConnections; i++) { + std::ostringstream out; + out << opts.base << "-" << (i+1); + tests.push_back(new Test(out.str())); + } + for (boost::ptr_vector<Test>::iterator i = tests.begin(); i != tests.end(); i++) { + i->start(); + } + if (opts.rate && !opts.timeLimit) { + while (true) { + qpid::sys::usleep(opts.reportFrequency * 1000); + //print latency report: + for (boost::ptr_vector<Test>::iterator i = tests.begin(); i != tests.end(); i++) { + i->report(); + } + } + } else { + for (boost::ptr_vector<Test>::iterator i = tests.begin(); i != tests.end(); i++) { + i->join(); + } + } + + return 0; + } catch(const std::exception& e) { + std::cout << e.what() << std::endl; + } + return 1; +} diff --git a/qpid/cpp/src/tests/qpid-perftest.cpp b/qpid/cpp/src/tests/qpid-perftest.cpp new file mode 100644 index 0000000000..7b9738772c --- /dev/null +++ b/qpid/cpp/src/tests/qpid-perftest.cpp @@ -0,0 +1,760 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include "TestOptions.h" +#include "qpid/OptionsTemplates.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/client/Connection.h" +#include "qpid/client/Completion.h" +#include "qpid/client/Message.h" +#include "qpid/framing/FieldTable.h" +#include "qpid/sys/Time.h" +#include "qpid/sys/Thread.h" + +#include <boost/lexical_cast.hpp> +#include <boost/bind.hpp> +#include <boost/function.hpp> +#include <boost/ptr_container/ptr_vector.hpp> + +#include <iostream> +#include <sstream> +#include <numeric> +#include <algorithm> +#include <math.h> + + +using namespace std; +using namespace qpid; +using namespace client; +using namespace sys; +using boost::lexical_cast; +using boost::bind; + +namespace qpid { +namespace tests { + +enum Mode { SHARED, FANOUT, TOPIC }; +const char* modeNames[] = { "shared", "fanout", "topic" }; + +// istream/ostream ops so Options can read/display Mode. +istream& operator>>(istream& in, Mode& mode) { + string s; + in >> s; + int i = find(modeNames, modeNames+3, s) - modeNames; + if (i >= 3) throw Exception("Invalid mode: "+s); + mode = Mode(i); + return in; +} + +ostream& operator<<(ostream& out, Mode mode) { + return out << modeNames[mode]; +} + +struct Opts : public TestOptions { + + // Actions + bool setup, control, publish, subscribe; + + // Queue policy + uint32_t queueMaxCount; + uint64_t queueMaxSize; + std::string baseName; + bool queueDurable; + + // Publisher + size_t pubs; + size_t count ; + size_t size; + size_t headers; + bool confirm; + bool durable; + bool uniqueData; + bool syncPub; + + // Subscriber + size_t subs; + size_t ack; + + // General + size_t qt; + bool singleConnect; + size_t iterations; + Mode mode; + bool summary; + uint32_t intervalSub; + uint32_t intervalPub; + size_t tx; + size_t txPub; + size_t txSub; + bool commitAsync; + + static const std::string helpText; + + Opts() : + TestOptions(helpText), + setup(false), control(false), publish(false), subscribe(false), baseName("qpid-perftest"), + pubs(1), count(500000), size(1024), headers(0), confirm(true), durable(false), uniqueData(false), syncPub(false), + subs(1), ack(0), + qt(1),singleConnect(false), iterations(1), mode(SHARED), summary(false), + intervalSub(0), intervalPub(0), tx(0), txPub(0), txSub(0), commitAsync(false) + { + addOptions() + ("setup", optValue(setup), "Create shared queues.") + ("control", optValue(control), "Run test, print report.") + ("publish", optValue(publish), "Publish messages.") + ("subscribe", optValue(subscribe), "Subscribe for messages.") + + ("mode", optValue(mode, "shared|fanout|topic"), "Test mode." + "\nshared: --qt queues, --npubs publishers and --nsubs subscribers per queue.\n" + "\nfanout: --npubs publishers, --nsubs subscribers, fanout exchange." + "\ntopic: --qt topics, --npubs publishers and --nsubs subscribers per topic.\n") + + ("npubs", optValue(pubs, "N"), "Create N publishers.") + ("count", optValue(count, "N"), "Each publisher sends N messages.") + ("size", optValue(size, "BYTES"), "Size of messages in bytes.") + ("headers", optValue(headers, "N"), "Number of headers to add to each message.") + ("pub-confirm", optValue(confirm, "yes|no"), "Publisher use confirm-mode.") + ("durable", optValue(durable, "yes|no"), "Publish messages as durable.") + ("unique-data", optValue(uniqueData, "yes|no"), "Make data for each message unique.") + ("sync-publish", optValue(syncPub, "yes|no"), "Wait for confirmation of each message before sending the next one.") + + ("nsubs", optValue(subs, "N"), "Create N subscribers.") + ("sub-ack", optValue(ack, "N"), "N>0: Subscriber acks batches of N.\n" + "N==0: Subscriber uses unconfirmed mode") + + ("qt", optValue(qt, "N"), "Create N queues or topics.") + ("single-connection", optValue(singleConnect, "yes|no"), "Use one connection for multiple sessions.") + + ("iterations", optValue(iterations, "N"), "Desired number of iterations of the test.") + ("summary,s", optValue(summary), "Summary output: pubs/sec subs/sec transfers/sec Mbytes/sec") + + ("queue-max-count", optValue(queueMaxCount, "N"), "queue policy: count to trigger 'flow to disk'") + ("queue-max-size", optValue(queueMaxSize, "N"), "queue policy: accumulated size to trigger 'flow to disk'") + ("base-name", optValue(baseName, "NAME"), "base name used for queues or topics") + ("queue-durable", optValue(queueDurable, "N"), "Make queue durable (implied if durable set)") + + ("interval_sub", optValue(intervalSub, "ms"), ">=0 delay between msg consume") + ("interval_pub", optValue(intervalPub, "ms"), ">=0 delay between msg publish") + + ("tx", optValue(tx, "N"), "if non-zero, the transaction batch size for publishing and consuming") + ("pub-tx", optValue(txPub, "N"), "if non-zero, the transaction batch size for publishing") + ("async-commit", optValue(commitAsync, "yes|no"), "Don't wait for completion of commit") + ("sub-tx", optValue(txSub, "N"), "if non-zero, the transaction batch size for consuming"); + } + + // Computed values + size_t totalPubs; + size_t totalSubs; + size_t transfers; + size_t subQuota; + + void parse(int argc, char** argv) { + TestOptions::parse(argc, argv); + switch (mode) { + case SHARED: + if (count % subs) { + count += subs - (count % subs); + cout << "WARNING: Adjusted --count to " << count + << " the next multiple of --nsubs" << endl; + } + totalPubs = pubs*qt; + totalSubs = subs*qt; + subQuota = (pubs*count)/subs; + break; + case FANOUT: + if (qt != 1) cerr << "WARNING: Fanout mode, ignoring --qt=" + << qt << endl; + qt=1; + totalPubs = pubs; + totalSubs = subs; + subQuota = totalPubs*count; + break; + case TOPIC: + totalPubs = pubs*qt; + totalSubs = subs*qt; + subQuota = pubs*count; + break; + } + transfers=(totalPubs*count) + (totalSubs*subQuota); + if (tx) { + if (txPub) { + cerr << "WARNING: Using overriden tx value for publishers: " << txPub << std::endl; + } else { + txPub = tx; + } + if (txSub) { + cerr << "WARNING: Using overriden tx value for subscribers: " << txSub << std::endl; + } else { + txSub = tx; + } + } + } +}; + +const std::string Opts::helpText= +"There are two ways to use qpid-perftest: single process or multi-process.\n\n" +"If none of the --setup, --publish, --subscribe or --control options\n" +"are given qpid-perftest will run a single-process test.\n" +"For a multi-process test first run:\n" +" qpid-perftest --setup <other options>\n" +"and wait for it to complete. The remaining process should run concurrently::\n" +"Run --npubs times: qpid-perftest --publish <other options>\n" +"Run --nsubs times: qpid-perftest --subscribe <other options>\n" +"Run once: qpid-perftest --control <other options>\n" +"Note the <other options> must be identical for all processes.\n"; + +Opts opts; +Connection globalConnection; + +std::string fqn(const std::string& name) +{ + ostringstream fqn; + fqn << opts.baseName << "_" << name; + return fqn.str(); +} + +struct Client : public Runnable { + Connection* connection; + Connection localConnection; + AsyncSession session; + Thread thread; + + Client() { + if (opts.singleConnect){ + connection = &globalConnection; + if (!globalConnection.isOpen()) opts.open(globalConnection); + }else{ + connection = &localConnection; + opts.open(localConnection); + } + session = connection->newSession(); + } + + ~Client() { + try { + if (connection->isOpen()) { + session.close(); + connection->close(); + } + } catch (const std::exception& e) { + std::cerr << "Error in shutdown: " << e.what() << std::endl; + } + } +}; + +struct Setup : public Client { + + void queueInit(string name, bool durable=false, const framing::FieldTable& settings=framing::FieldTable()) { + session.queueDeclare(arg::queue=name, arg::durable=durable, arg::arguments=settings); + session.queuePurge(arg::queue=name); + session.sync(); + } + + void run() { + queueInit(fqn("pub_start")); + queueInit(fqn("pub_done")); + queueInit(fqn("sub_ready")); + queueInit(fqn("sub_done")); + if (opts.iterations > 1) queueInit(fqn("sub_iteration")); + if (opts.mode==SHARED) { + framing::FieldTable settings;//queue policy settings + settings.setInt("qpid.max_count", opts.queueMaxCount); + settings.setInt("qpid.max_size", opts.queueMaxSize); + for (size_t i = 0; i < opts.qt; ++i) { + ostringstream qname; + qname << opts.baseName << i; + queueInit(qname.str(), opts.durable || opts.queueDurable, settings); + } + } + } +}; + +void expect(string actual, string expect) { + if (expect != actual) + throw Exception("Expecting "+expect+" but received "+actual); + +} + +double secs(Duration d) { return double(d)/TIME_SEC; } +double secs(AbsTime start, AbsTime finish) { + return secs(Duration(start,finish)); +} + + +// Collect rates & print stats. +class Stats { + vector<double> values; + double sum; + + public: + Stats() : sum(0) {} + + // Functor to collect rates. + void operator()(const string& data) { + try { + double d=lexical_cast<double>(data); + values.push_back(d); + sum += d; + } catch (const std::exception&) { + throw Exception("Bad report: "+data); + } + } + + double mean() const { + return sum/values.size(); + } + + double stdev() const { + if (values.size() <= 1) return 0; + double avg = mean(); + double ssq = 0; + for (vector<double>::const_iterator i = values.begin(); + i != values.end(); ++i) { + double x=*i; + x -= avg; + ssq += x*x; + } + return sqrt(ssq/(values.size()-1)); + } + + ostream& print(ostream& out) { + ostream_iterator<double> o(out, "\n"); + copy(values.begin(), values.end(), o); + out << "Average: " << mean(); + if (values.size() > 1) + out << " (std.dev. " << stdev() << ")"; + return out << endl; + } +}; + + +// Manage control queues, collect and print reports. +struct Controller : public Client { + + SubscriptionManager subs; + + Controller() : subs(session) {} + + /** Process messages from queue by applying a functor. */ + void process(size_t n, string queue, + boost::function<void (const string&)> msgFn) + { + if (!opts.summary) + cout << "Processing " << n << " messages from " + << queue << " " << flush; + LocalQueue lq; + subs.setFlowControl(n, SubscriptionManager::UNLIMITED, false); + subs.subscribe(lq, queue); + for (size_t i = 0; i < n; ++i) { + if (!opts.summary) cout << "." << flush; + msgFn(lq.pop().getData()); + } + if (!opts.summary) cout << " done." << endl; + } + + void process(size_t n, LocalQueue lq, string queue, + boost::function<void (const string&)> msgFn) + { + session.messageFlow(queue, 0, n); + if (!opts.summary) + cout << "Processing " << n << " messages from " + << queue << " " << flush; + for (size_t i = 0; i < n; ++i) { + if (!opts.summary) cout << "." << flush; + msgFn(lq.pop().getData()); + } + if (!opts.summary) cout << " done." << endl; + } + + void send(size_t n, string queue, string data) { + if (!opts.summary) + cout << "Sending " << data << " " << n << " times to " << queue + << endl; + Message msg(data, queue); + for (size_t i = 0; i < n; ++i) + session.messageTransfer(arg::content=msg, arg::acceptMode=1); + } + + void run() { // Controller + try { + // Wait for subscribers to be ready. + process(opts.totalSubs, fqn("sub_ready"), boost::bind(expect, _1, "ready")); + + LocalQueue pubDone; + LocalQueue subDone; + subs.setFlowControl(0, SubscriptionManager::UNLIMITED, false); + subs.subscribe(pubDone, fqn("pub_done")); + subs.subscribe(subDone, fqn("sub_done")); + + double txrateTotal(0); + double mbytesTotal(0); + double pubRateTotal(0); + double subRateTotal(0); + + for (size_t j = 0; j < opts.iterations; ++j) { + AbsTime start=now(); + send(opts.totalPubs, fqn("pub_start"), "start"); // Start publishers + if (j) { + send(opts.totalSubs, fqn("sub_iteration"), "next"); // Start subscribers on next iteration + } + + Stats pubRates; + Stats subRates; + + process(opts.totalPubs, pubDone, fqn("pub_done"), boost::ref(pubRates)); + process(opts.totalSubs, subDone, fqn("sub_done"), boost::ref(subRates)); + + AbsTime end=now(); + double time=secs(start, end); + if (time <= 0.0) { + throw Exception("ERROR: Test completed in zero seconds. Try again with a larger message count."); + } + double txrate=opts.transfers/time; + double mbytes=(txrate*opts.size)/(1024*1024); + + if (!opts.summary) { + cout << endl << "Total " << opts.transfers << " transfers of " + << opts.size << " bytes in " + << time << " seconds." << endl; + cout << endl << "Publish transfers/sec: " << endl; + pubRates.print(cout); + cout << endl << "Subscribe transfers/sec: " << endl; + subRates.print(cout); + cout << endl + << "Total transfers/sec: " << txrate << endl + << "Total Mbytes/sec: " << mbytes << endl; + } + else { + cout << pubRates.mean() << "\t" + << subRates.mean() << "\t" + << txrate << "\t" + << mbytes << endl; + } + + txrateTotal += txrate; + mbytesTotal += mbytes; + pubRateTotal += pubRates.mean(); + subRateTotal += subRates.mean(); + } + if (opts.iterations > 1) { + cout << "Averages: "<< endl + << (pubRateTotal / opts.iterations) << "\t" + << (subRateTotal / opts.iterations) << "\t" + << (txrateTotal / opts.iterations) << "\t" + << (mbytesTotal / opts.iterations) << endl; + } + } + catch (const std::exception& e) { + cout << "Controller exception: " << e.what() << endl; + } + } +}; + + +struct PublishThread : public Client { + string destination; + string routingKey; + + PublishThread() {}; + + PublishThread(string key, string dest=string()) { + destination=dest; + routingKey=key; + } + + void run() { // Publisher + try { + string data; + size_t offset(0); + if (opts.uniqueData) { + offset = 5; + data += "data:";//marker (requested for latency testing tool scripts) + data += string(sizeof(size_t), 'X');//space for seq no + data += session.getId().str(); + if (opts.size > data.size()) { + data += string(opts.size - data.size(), 'X'); + } else if(opts.size < data.size()) { + cout << "WARNING: Increased --size to " << data.size() + << " to honour --unique-data" << endl; + } + } else { + size_t msgSize=max(opts.size, sizeof(size_t)); + data = string(msgSize, 'X'); + } + + Message msg(data, routingKey); + if (opts.durable) + msg.getDeliveryProperties().setDeliveryMode(framing::PERSISTENT); + if (opts.headers) { + for (size_t i = 0; i < opts.headers; ++i) { + std::stringstream h; + h << "hdr" << i; + msg.getMessageProperties().getApplicationHeaders().setString(h.str(), h.str()); + } + } + + if (opts.txPub){ + session.txSelect(); + } + SubscriptionManager subs(session); + LocalQueue lq; + subs.setFlowControl(0, SubscriptionManager::UNLIMITED, false); + Subscription cs = subs.subscribe(lq, fqn("pub_start")); + + for (size_t j = 0; j < opts.iterations; ++j) { + cs.grantMessageCredit(1); + expect(lq.pop().getData(), "start"); + AbsTime start=now(); + for (size_t i=0; i<opts.count; i++) { + // Stamp the iteration into the message data, avoid + // any heap allocation. + const_cast<std::string&>(msg.getData()).replace(offset, sizeof(size_t), + reinterpret_cast<const char*>(&i), sizeof(size_t)); + if (opts.syncPub) { + sync(session).messageTransfer( + arg::destination=destination, + arg::content=msg, + arg::acceptMode=1); + } else { + session.messageTransfer( + arg::destination=destination, + arg::content=msg, + arg::acceptMode=1); + } + if (opts.txPub && ((i+1) % opts.txPub == 0)){ + if (opts.commitAsync){ + session.txCommit(); + } else { + sync(session).txCommit(); + } + } + if (opts.intervalPub) + qpid::sys::usleep(opts.intervalPub*1000); + } + if (opts.confirm) session.sync(); + AbsTime end=now(); + double time=secs(start,end); + if (time <= 0.0) { + throw Exception("ERROR: Test completed in zero seconds. Try again with a larger message count."); + } + + // Send result to controller. + Message report(lexical_cast<string>(opts.count/time), fqn("pub_done")); + session.messageTransfer(arg::content=report, arg::acceptMode=1); + if (opts.txPub){ + sync(session).txCommit(); + } + } + session.close(); + } + catch (const std::exception& e) { + cout << "PublishThread exception: " << e.what() << endl; + } + } +}; + +struct SubscribeThread : public Client { + + string queue; + + SubscribeThread() {} + + SubscribeThread(string q) { queue = q; } + + SubscribeThread(string key, string ex) { + queue=session.getId().str(); // Unique name. + session.queueDeclare(arg::queue=queue, + arg::exclusive=true, + arg::autoDelete=true, + arg::durable=opts.durable); + session.exchangeBind(arg::queue=queue, + arg::exchange=ex, + arg::bindingKey=key); + } + + void verify(bool cond, const char* test, uint32_t expect, uint32_t actual) { + if (!cond) { + Message error( + QPID_MSG("Sequence error: expected n" << test << expect << " but got " << actual), + "sub_done"); + session.messageTransfer(arg::content=error, arg::acceptMode=1); + throw Exception(error.getData()); + } + } + + void run() { // Subscribe + try { + if (opts.txSub) sync(session).txSelect(); + SubscriptionManager subs(session); + SubscriptionSettings settings; + settings.autoAck = opts.txSub ? opts.txSub : opts.ack; + settings.acceptMode = (opts.txSub || opts.ack ? ACCEPT_MODE_EXPLICIT : ACCEPT_MODE_NONE); + settings.flowControl = FlowControl::messageCredit(opts.subQuota); + LocalQueue lq; + Subscription subscription = subs.subscribe(lq, queue, settings); + // Notify controller we are ready. + session.messageTransfer(arg::content=Message("ready", fqn("sub_ready")), arg::acceptMode=1); + if (opts.txSub) { + if (opts.commitAsync) session.txCommit(); + else sync(session).txCommit(); + } + + LocalQueue iterationControl; + if (opts.iterations > 1) { + subs.subscribe(iterationControl, fqn("sub_iteration"), SubscriptionSettings(FlowControl::messageCredit(0))); + } + + for (size_t j = 0; j < opts.iterations; ++j) { + if (j > 0) { + //need to wait here until all subs are done + session.messageFlow(fqn("sub_iteration"), 0, 1); + iterationControl.pop(); + + //need to allocate some more credit for subscription + session.messageFlow(queue, 0, opts.subQuota); + } + Message msg; + AbsTime start=now(); + size_t expect=0; + for (size_t i = 0; i < opts.subQuota; ++i) { + msg=lq.pop(); + if (opts.txSub && ((i+1) % opts.txSub == 0)) { + if (opts.commitAsync) session.txCommit(); + else sync(session).txCommit(); + } + if (opts.intervalSub) + qpid::sys::usleep(opts.intervalSub*1000); + // TODO aconway 2007-11-23: check message order for. + // multiple publishers. Need an array of counters, + // one per publisher and a publisher ID in the + // message. Careful not to introduce a lot of overhead + // here, e.g. no std::map, std::string etc. + // + // For now verify order only for a single publisher. + size_t offset = opts.uniqueData ? 5 /*marker is 'data:'*/ : 0; + size_t n; + memcpy (&n, reinterpret_cast<const char*>(msg.getData().data() + offset), + sizeof(n)); + if (opts.pubs == 1) { + if (opts.subs == 1 || opts.mode == FANOUT) verify(n==expect, "==", expect, n); + else verify(n>=expect, ">=", expect, n); + expect = n+1; + } + } + if (opts.txSub || opts.ack) + subscription.accept(subscription.getUnaccepted()); + if (opts.txSub) { + if (opts.commitAsync) session.txCommit(); + else sync(session).txCommit(); + } + AbsTime end=now(); + + // Report to publisher. + Message result(lexical_cast<string>(opts.subQuota/secs(start,end)), + fqn("sub_done")); + session.messageTransfer(arg::content=result, arg::acceptMode=1); + if (opts.txSub) sync(session).txCommit(); + } + session.close(); + } + catch (const std::exception& e) { + cout << "SubscribeThread exception: " << e.what() << endl; + } + } +}; + +} + +template po::value_semantic* create_value(tests::Mode& val, const std::string& arg); + +} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char** argv) { + int exitCode = 0; + boost::ptr_vector<Client> subs(opts.subs); + boost::ptr_vector<Client> pubs(opts.pubs); + + try { + opts.parse(argc, argv); + + string exchange; + switch (opts.mode) { + case FANOUT: exchange="amq.fanout"; break; + case TOPIC: exchange="amq.topic"; break; + case SHARED: break; + } + + bool singleProcess= + (!opts.setup && !opts.control && !opts.publish && !opts.subscribe); + if (singleProcess) + opts.setup = opts.control = opts.publish = opts.subscribe = true; + + if (opts.setup) Setup().run(); // Set up queues + + // Start pubs/subs for each queue/topic. + for (size_t i = 0; i < opts.qt; ++i) { + ostringstream key; + key << opts.baseName << i; // Queue or topic name. + if (opts.publish) { + size_t n = singleProcess ? opts.pubs : 1; + for (size_t j = 0; j < n; ++j) { + pubs.push_back(new PublishThread(key.str(), exchange)); + pubs.back().thread=Thread(pubs.back()); + } + } + if (opts.subscribe) { + size_t n = singleProcess ? opts.subs : 1; + for (size_t j = 0; j < n; ++j) { + if (opts.mode==SHARED) + subs.push_back(new SubscribeThread(key.str())); + else + subs.push_back(new SubscribeThread(key.str(),exchange)); + subs.back().thread=Thread(subs.back()); + } + } + } + + if (opts.control) Controller().run(); + } + catch (const std::exception& e) { + cout << endl << e.what() << endl; + exitCode = 1; + } + + // Wait for started threads. + if (opts.publish) { + for (boost::ptr_vector<Client>::iterator i=pubs.begin(); + i != pubs.end(); + ++i) + i->thread.join(); + } + + if (opts.subscribe) { + for (boost::ptr_vector<Client>::iterator i=subs.begin(); + i != subs.end(); + ++i) + i->thread.join(); + } + return exitCode; +} diff --git a/qpid/cpp/src/tests/qpid-ping.cpp b/qpid/cpp/src/tests/qpid-ping.cpp new file mode 100644 index 0000000000..40e6a0f671 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-ping.cpp @@ -0,0 +1,94 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + + */ + +#include <qpid/messaging/Address.h> +#include <qpid/messaging/Connection.h> +#include "qpid/messaging/Duration.h" +#include <qpid/messaging/Message.h> +#include <qpid/messaging/Sender.h> +#include <qpid/messaging/Receiver.h> +#include <qpid/messaging/Session.h> +#include <qpid/Msg.h> +#include <qpid/Options.h> +#include <qpid/types/Uuid.h> +#include <string> +#include <iostream> + +using namespace std; +using namespace qpid::messaging; +using qpid::types::Uuid; + +namespace { + +struct PingOptions : public qpid::Options { + string url; + string address; + string message; + string connectionOptions; + double timeout; // Timeout in seconds. + bool quiet; // No output + + PingOptions() : + url("127.0.0.1"), + address(Uuid(true).str()+";{create:always}"), + message(Uuid(true).str()), + timeout(1), + quiet(false) + { + using qpid::optValue; + addOptions() + ("broker,b", qpid::optValue(url, "URL"), "url of broker to connect to.") + ("address,a", qpid::optValue(address, "ADDRESS"), "address to use.") + ("message,m", optValue(message, "MESSAGE"), "message text to send.") + ("connection-options", optValue(connectionOptions, "OPTIONS"), "options for the connection.") + ("timeout,t", optValue(timeout, "SECONDS"), "Max time to wait.") + ("quiet,q", optValue(quiet), "Don't print anything to stderr/stdout."); + } +}; + +} // namespace + +int main(int argc, char** argv) { + Connection connection; + try { + PingOptions opts; + opts.parse(argc, argv); + connection = Connection(opts.url, opts.connectionOptions); + connection.open(); + if (!opts.quiet) cout << "Opened connection." << endl; + Session s = connection.createSession(); + s.createSender(opts.address).send(Message(opts.message)); + if (!opts.quiet) cout << "Sent message." << endl; + Message m = s.createReceiver(opts.address). + fetch(Duration(uint64_t(opts.timeout*1000))); + if (m.getContent() != opts.message) + throw qpid::Exception(qpid::Msg() << "Expected " << opts.message + << " but received " << m.getContent()); + if (!opts.quiet) cout << "Received message." << endl; + connection.close(); + return 0; + } catch (const exception& e) { + cerr << "Error: " << e.what() << endl; + connection.close(); + return 1; + } +} diff --git a/qpid/cpp/src/tests/qpid-receive.cpp b/qpid/cpp/src/tests/qpid-receive.cpp new file mode 100644 index 0000000000..a71fd11fa7 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-receive.cpp @@ -0,0 +1,299 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include <qpid/messaging/Address.h> +#include <qpid/messaging/Connection.h> +#include <qpid/messaging/Receiver.h> +#include <qpid/messaging/Sender.h> +#include <qpid/messaging/Session.h> +#include <qpid/messaging/Message.h> +#include <qpid/messaging/FailoverUpdates.h> +#include <qpid/Options.h> +#include <qpid/log/Logger.h> +#include <qpid/log/Options.h> +#include "qpid/sys/Time.h" +#include "TestOptions.h" +#include "Statistics.h" + +#include <iostream> +#include <memory> + +using namespace qpid::messaging; +using namespace qpid::types; +using namespace std; + +namespace qpid { +namespace tests { + +struct Options : public qpid::Options +{ + bool help; + std::string url; + std::string address; + std::string connectionOptions; + int64_t timeout; + bool forever; + uint messages; + bool ignoreDuplicates; + bool verifySequence; + bool checkRedelivered; + uint capacity; + uint ackFrequency; + uint tx; + uint rollbackFrequency; + bool printContent; + bool printContentObjectType; + bool printHeaders; + bool failoverUpdates; + qpid::log::Options log; + bool reportTotal; + uint reportEvery; + bool reportHeader; + string readyAddress; + uint receiveRate; + std::string replyto; + bool noReplies; + + Options(const std::string& argv0=std::string()) + : qpid::Options("Options"), + help(false), + url("127.0.0.1"), + timeout(0), + forever(false), + messages(0), + ignoreDuplicates(false), + verifySequence(false), + checkRedelivered(false), + capacity(1000), + ackFrequency(100), + tx(0), + rollbackFrequency(0), + printContent(true), + printContentObjectType(false), + printHeaders(false), + failoverUpdates(false), + log(argv0), + reportTotal(false), + reportEvery(0), + reportHeader(true), + receiveRate(0), + noReplies(false) + { + addOptions() + ("broker,b", qpid::optValue(url, "URL"), "url of broker to connect to") + ("address,a", qpid::optValue(address, "ADDRESS"), "address to receive from") + ("connection-options", qpid::optValue(connectionOptions, "OPTIONS"), "options for the connection") + ("timeout", qpid::optValue(timeout, "TIMEOUT"), "timeout in seconds to wait before exiting") + ("forever,f", qpid::optValue(forever), "ignore timeout and wait forever") + ("messages,m", qpid::optValue(messages, "N"), "Number of messages to receive; 0 means receive indefinitely") + ("ignore-duplicates", qpid::optValue(ignoreDuplicates), "Detect and ignore duplicates (by checking 'sn' header)") + ("verify-sequence", qpid::optValue(verifySequence), "Verify there are no gaps in the message sequence (by checking 'sn' header)") + ("check-redelivered", qpid::optValue(checkRedelivered), "Fails with exception if a duplicate is not marked as redelivered (only relevant when ignore-duplicates is selected)") + ("capacity", qpid::optValue(capacity, "N"), "Pre-fetch window (0 implies no pre-fetch)") + ("ack-frequency", qpid::optValue(ackFrequency, "N"), "Ack frequency (0 implies none of the messages will get accepted)") + ("tx", qpid::optValue(tx, "N"), "batch size for transactions (0 implies transaction are not used)") + ("rollback-frequency", qpid::optValue(rollbackFrequency, "N"), "rollback frequency (0 implies no transaction will be rolledback)") + ("print-content", qpid::optValue(printContent, "yes|no"), "print out message content") + ("print-object-type", qpid::optValue(printContentObjectType, "yes|no"), "print a description of the content's object type if relevant") + ("print-headers", qpid::optValue(printHeaders, "yes|no"), "print out message headers") + ("failover-updates", qpid::optValue(failoverUpdates), "Listen for membership updates distributed via amq.failover") + ("report-total", qpid::optValue(reportTotal), "Report total throughput and latency statistics") + ("report-every", qpid::optValue(reportEvery,"N"), "Report throughput and latency statistics every N messages.") + ("report-header", qpid::optValue(reportHeader, "yes|no"), "Headers on report.") + ("ready-address", qpid::optValue(readyAddress, "ADDRESS"), "send a message to this address when ready to receive") + ("receive-rate", qpid::optValue(receiveRate,"N"), "Receive at rate of N messages/second. 0 means receive as fast as possible.") + ("reply-to", qpid::optValue(replyto, "REPLY-TO"), "specify reply-to address on response messages") + ("ignore-reply-to", qpid::optValue(noReplies), "Do not send replies even if reply-to is set") + ("help", qpid::optValue(help), "print this usage statement"); + add(log); + } + + Duration getTimeout() + { + if (forever) return Duration::FOREVER; + else return Duration::SECOND*timeout; + + } + bool parse(int argc, char** argv) + { + try { + qpid::Options::parse(argc, argv); + if (address.empty()) throw qpid::Exception("Address must be specified!"); + qpid::log::Logger::instance().configure(log); + if (help) { + std::cout << *this << std::endl << std::endl + << "Drains messages from the specified address" << std::endl; + return false; + } else { + return true; + } + } catch (const std::exception& e) { + std::cerr << *this << std::endl << std::endl << e.what() << std::endl; + return false; + } + } +}; + +const string EOS("eos"); +const string SN("sn"); + +/** Check for duplicate or dropped messages by sequence number */ +class SequenceTracker +{ + public: + SequenceTracker(const Options& o) : opts(o), lastSn(0) {} + + /** Return true if the message should be procesed, false if it should be ignored. */ + bool track(Message& message) { + if (!(opts.verifySequence || opts.ignoreDuplicates)) + return true; // Not checking sequence numbers. + uint sn = message.getProperties()[SN]; + bool duplicate = (sn <= lastSn); + bool dropped = (sn > lastSn+1); + if (opts.verifySequence && dropped) + throw Exception(QPID_MSG("Gap in sequence numbers " << lastSn << "-" << sn)); + bool ignore = duplicate && opts.ignoreDuplicates; + if (ignore && opts.checkRedelivered && !message.getRedelivered()) + throw qpid::Exception("duplicate sequence number received, message not marked as redelivered!"); + if (!duplicate) lastSn = sn; + return !ignore; + } + + private: + const Options& opts; + uint lastSn; +}; + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char ** argv) +{ + Connection connection; + try { + Options opts; + if (opts.parse(argc, argv)) { + connection = Connection(opts.url, opts.connectionOptions); + connection.open(); + std::auto_ptr<FailoverUpdates> updates(opts.failoverUpdates ? new FailoverUpdates(connection) : 0); + Session session = opts.tx ? connection.createTransactionalSession() : connection.createSession(); + Receiver receiver = session.createReceiver(opts.address); + receiver.setCapacity(std::min(opts.capacity, opts.messages)); + Message msg; + uint count = 0; + uint txCount = 0; + SequenceTracker sequenceTracker(opts); + Duration timeout = opts.getTimeout(); + bool done = false; + Reporter<ThroughputAndLatency> reporter(std::cout, opts.reportEvery, opts.reportHeader); + if (!opts.readyAddress.empty()) { + session.createSender(opts.readyAddress).send(msg); + if (opts.tx) + session.commit(); + } + // For receive rate calculation + qpid::sys::AbsTime start = qpid::sys::now(); + int64_t interval = 0; + if (opts.receiveRate) interval = qpid::sys::TIME_SEC/opts.receiveRate; + + std::map<std::string,Sender> replyTo; + + while (!done && receiver.fetch(msg, timeout)) { + reporter.message(msg); + if (sequenceTracker.track(msg)) { + if (msg.getContent() == EOS) { + done = true; + } else { + ++count; + if (opts.printHeaders) { + if (msg.getSubject().size()) std::cout << "Subject: " << msg.getSubject() << std::endl; + if (msg.getReplyTo()) std::cout << "ReplyTo: " << msg.getReplyTo() << std::endl; + if (msg.getMessageId().size()) std::cout << "MessageId: " << msg.getMessageId() << std::endl; + if (msg.getCorrelationId().size()) std::cout << "CorrelationId: " << msg.getCorrelationId() << std::endl; + if (msg.getUserId().size()) std::cout << "UserId: " << msg.getUserId() << std::endl; + if (msg.getTtl().getMilliseconds()) std::cout << "TTL: " << msg.getTtl().getMilliseconds() << std::endl; + if (msg.getPriority()) std::cout << "Priority: " << ((uint) msg.getPriority()) << std::endl; + if (msg.getDurable()) std::cout << "Durable: true" << std::endl; + if (msg.getRedelivered()) std::cout << "Redelivered: true" << std::endl; + std::cout << "Properties: " << msg.getProperties() << std::endl; + if (msg.getContentType().size()) std::cout << "ContentType: " << msg.getContentType() << std::endl; + std::cout << std::endl; + } + if (opts.printContent) { + if (!msg.getContentObject().isVoid()) { + if (opts.printContentObjectType) { + std::cout << "[Object: " << getTypeName(msg.getContentObject().getType()) << "]" << std::endl; + } + std::cout << msg.getContentObject() << std::endl; + } else { + std::cout << msg.getContent() << std::endl; + } + } + if (opts.messages && count >= opts.messages) done = true; + } + } + if (opts.tx && (count % opts.tx == 0)) { + if (opts.rollbackFrequency && (++txCount % opts.rollbackFrequency == 0)) { + session.rollback(); + } else { + session.commit(); + } + } else if (opts.ackFrequency && (count % opts.ackFrequency == 0)) { + session.acknowledge(); + } + if (msg.getReplyTo() && !opts.noReplies) { // Echo message back to reply-to address. + Sender& s = replyTo[msg.getReplyTo().str()]; + if (s.isNull()) { + s = session.createSender(msg.getReplyTo()); + s.setCapacity(opts.capacity); + replyTo[msg.getReplyTo().str()] = s; + } + msg.setReplyTo(Address(opts.replyto)); + s.send(msg); + } + if (opts.receiveRate) { + qpid::sys::AbsTime waitTill(start, count*interval); + int64_t delay = qpid::sys::Duration(qpid::sys::now(), waitTill); + if (delay > 0) qpid::sys::usleep(delay/qpid::sys::TIME_USEC); + } + } + if (opts.reportTotal) reporter.report(); + if (opts.tx) { + if (opts.rollbackFrequency && (++txCount % opts.rollbackFrequency == 0)) { + session.rollback(); + } else { + session.commit(); + } + } else if (opts.ackFrequency) { + session.acknowledge(); + } + session.close(); + connection.close(); + return 0; + } + return 1; + } catch(const std::exception& error) { + std::cerr << "qpid-receive: " << error.what() << std::endl; + connection.close(); + return 1; + } +} diff --git a/qpid/cpp/src/tests/qpid-send.cpp b/qpid/cpp/src/tests/qpid-send.cpp new file mode 100644 index 0000000000..bc0ad78601 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-send.cpp @@ -0,0 +1,465 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include <qpid/messaging/Address.h> +#include <qpid/messaging/Connection.h> +#include <qpid/messaging/Message.h> +#include <qpid/messaging/Sender.h> +#include <qpid/messaging/Receiver.h> +#include <qpid/messaging/Session.h> +#include <qpid/messaging/FailoverUpdates.h> +#include <qpid/sys/Time.h> +#include <qpid/sys/Monitor.h> +#include <qpid/sys/SystemInfo.h> +#include "TestOptions.h" +#include "Statistics.h" + +#include <fstream> +#include <iostream> +#include <memory> + +using std::string; +using std::ios_base; + +using qpid::messaging::Address; +using qpid::messaging::Connection; +using qpid::messaging::Duration; +using qpid::messaging::FailoverUpdates; +using qpid::messaging::Message; +using qpid::messaging::Receiver; +using qpid::messaging::Session; +using qpid::messaging::Sender; +using qpid::types::Exception; +using qpid::types::Uuid; +using qpid::types::Variant; + +namespace qpid { +namespace tests { + +typedef std::vector<std::string> string_vector; + +struct Options : public qpid::Options +{ + bool help; + std::string url; + std::string connectionOptions; + std::string address; + uint messages; + std::string id; + std::string replyto; + uint sendEos; + bool durable; + uint ttl; + uint priority; + std::string userid; + std::string correlationid; + string_vector properties; + string_vector entries; + std::string contentString; + uint contentSize; + bool contentStdin; + uint tx; + uint rollbackFrequency; + uint capacity; + bool failoverUpdates; + qpid::log::Options log; + bool reportTotal; + uint reportEvery; + bool reportHeader; + uint sendRate; + bool sequence; + bool timestamp; + std::string groupKey; + std::string groupPrefix; + uint groupSize; + bool groupRandSize; + uint groupInterleave; + + Options(const std::string& argv0=std::string()) + : qpid::Options("Options"), + help(false), + url("127.0.0.1"), + messages(1), + sendEos(0), + durable(false), + ttl(0), + priority(0), + contentString(), + contentSize(0), + contentStdin(false), + tx(0), + rollbackFrequency(0), + capacity(1000), + failoverUpdates(false), + log(argv0), + reportTotal(false), + reportEvery(0), + reportHeader(true), + sendRate(0), + sequence(true), + timestamp(true), + groupPrefix("GROUP-"), + groupSize(10), + groupRandSize(false), + groupInterleave(1) + { + addOptions() + ("broker,b", qpid::optValue(url, "URL"), "url of broker to connect to") + ("address,a", qpid::optValue(address, "ADDRESS"), "address to send to") + ("connection-options", qpid::optValue(connectionOptions, "OPTIONS"), "options for the connection") + ("messages,m", qpid::optValue(messages, "N"), "stop after N messages have been sent, 0 means no limit") + ("id,i", qpid::optValue(id, "ID"), "use the supplied id instead of generating one") + ("reply-to", qpid::optValue(replyto, "REPLY-TO"), "specify reply-to address") + ("send-eos", qpid::optValue(sendEos, "N"), "Send N EOS messages to mark end of input") + ("durable", qpid::optValue(durable, "yes|no"), "Mark messages as durable.") + ("ttl", qpid::optValue(ttl, "msecs"), "Time-to-live for messages, in milliseconds") + ("priority", qpid::optValue(priority, "PRIORITY"), "Priority for messages (higher value implies higher priority)") + ("property,P", qpid::optValue(properties, "NAME=VALUE"), "specify message property") + ("correlation-id", qpid::optValue(correlationid, "ID"), "correlation-id for message") + ("user-id", qpid::optValue(userid, "USERID"), "userid for message") + ("content-string", qpid::optValue(contentString, "CONTENT"), "use CONTENT as message content") + ("content-size", qpid::optValue(contentSize, "N"), "create an N-byte message content") + ("content-map,M", qpid::optValue(entries, "NAME=VALUE"), "specify entry for map content") + ("content-stdin", qpid::optValue(contentStdin), "read message content from stdin, one line per message") + ("capacity", qpid::optValue(capacity, "N"), "size of the senders outgoing message queue") + ("tx", qpid::optValue(tx, "N"), "batch size for transactions (0 implies transaction are not used)") + ("rollback-frequency", qpid::optValue(rollbackFrequency, "N"), "rollback frequency (0 implies no transaction will be rolledback)") + ("failover-updates", qpid::optValue(failoverUpdates), "Listen for membership updates distributed via amq.failover") + ("report-total", qpid::optValue(reportTotal), "Report total throughput statistics") + ("report-every", qpid::optValue(reportEvery,"N"), "Report throughput statistics every N messages") + ("report-header", qpid::optValue(reportHeader, "yes|no"), "Headers on report.") + ("send-rate", qpid::optValue(sendRate,"N"), "Send at rate of N messages/second. 0 means send as fast as possible.") + ("sequence", qpid::optValue(sequence, "yes|no"), "Add a sequence number messages property (required for duplicate/lost message detection)") + ("timestamp", qpid::optValue(timestamp, "yes|no"), "Add a time stamp messages property (required for latency measurement)") + ("group-key", qpid::optValue(groupKey, "KEY"), "Generate groups of messages using message header 'KEY' to hold the group identifier") + ("group-prefix", qpid::optValue(groupPrefix, "STRING"), "Generate group identifers with 'STRING' prefix (if group-key specified)") + ("group-size", qpid::optValue(groupSize, "N"), "Number of messages per a group (if group-key specified)") + ("group-randomize-size", qpid::optValue(groupRandSize), "Randomize the number of messages per group to [1...group-size] (if group-key specified)") + ("group-interleave", qpid::optValue(groupInterleave, "N"), "Simultaineously interleave messages from N different groups (if group-key specified)") + ("help", qpid::optValue(help), "print this usage statement"); + add(log); + } + + bool parse(int argc, char** argv) + { + try { + qpid::Options::parse(argc, argv); + if (address.empty()) throw qpid::Exception("Address must be specified!"); + qpid::log::Logger::instance().configure(log); + if (help) { + std::cout << *this << std::endl << std::endl + << "Sends messages to the specified address" << std::endl; + return false; + } else { + return true; + } + } catch (const std::exception& e) { + std::cerr << *this << std::endl << std::endl << e.what() << std::endl; + return false; + } + } + + static bool nameval(const std::string& in, std::string& name, std::string& value) + { + std::string::size_type i = in.find("="); + if (i == std::string::npos) { + name = in; + return false; + } else { + name = in.substr(0, i); + if (i+1 < in.size()) { + value = in.substr(i+1); + return true; + } else { + return false; + } + } + } + + static void setProperty(Message& message, const std::string& property) + { + std::string name; + std::string value; + if (nameval(property, name, value)) { + message.getProperties()[name].parse(value); + } else { + message.getProperties()[name] = Variant(); + } + } + + void setProperties(Message& message) const + { + for (string_vector::const_iterator i = properties.begin(); i != properties.end(); ++i) { + setProperty(message, *i); + } + } + + void setEntries(Variant::Map& content) const + { + for (string_vector::const_iterator i = entries.begin(); i != entries.end(); ++i) { + std::string name; + std::string value; + if (nameval(*i, name, value)) { + content[name] = value; + } else { + content[name] = Variant(); + } + } + } +}; + +const string EOS("eos"); +const string SN("sn"); +const string TS("ts"); + +class ContentGenerator { + public: + virtual ~ContentGenerator() {} + virtual bool setContent(Message& msg) = 0; + void setContentObject(Message& msg, const std::string& content, const std::string& encoding=std::string("utf8")) + { + Variant& obj = msg.getContentObject(); + obj = content; + obj.setEncoding(encoding); + } +}; + + +class GetlineContentGenerator : public ContentGenerator { + public: + virtual bool setContent(Message& msg) { + string content; + bool got = !!getline(std::cin, content); + if (got) { + setContentObject(msg, content); + } + return got; + } +}; + +class FixedContentGenerator : public ContentGenerator { + public: + FixedContentGenerator(const string& s) : content(s) {} + virtual bool setContent(Message& msg) { + setContentObject(msg, content); + return true; + } + private: + std::string content; +}; + +class MapContentGenerator : public ContentGenerator { + public: + MapContentGenerator(const Options& opt) : opts(opt) {} + virtual bool setContent(Message& msg) { + msg.getContentObject() = qpid::types::Variant::Map(); + opts.setEntries(msg.getContentObject().asMap()); + return true; + } + private: + const Options& opts; +}; + +// tag each generated message with a group identifer +// +class GroupGenerator { + public: + GroupGenerator(const std::string& key, + const std::string& prefix, + const uint size, + const bool randomize, + const uint interleave) + : groupKey(key), groupPrefix(prefix), groupSize(size), + randomizeSize(randomize), groupSuffix(0) + { + if (randomize) srand((unsigned int)qpid::sys::SystemInfo::getProcessId()); + + for (uint i = 0; i < 1 || i < interleave; ++i) { + newGroup(); + } + current = groups.begin(); + } + + void setGroupInfo(Message &msg) + { + if (current == groups.end()) + current = groups.begin(); + msg.getProperties()[groupKey] = current->id; + // std::cout << "SENDING GROUPID=[" << current->id << "]" << std::endl; + if (++(current->count) == current->size) { + newGroup(); + groups.erase(current++); + } else + ++current; + } + + private: + const std::string& groupKey; + const std::string& groupPrefix; + const uint groupSize; + const bool randomizeSize; + + uint groupSuffix; + + struct GroupState { + std::string id; + const uint size; + uint count; + GroupState( const std::string& i, const uint s ) + : id(i), size(s), count(0) {} + }; + typedef std::list<GroupState> GroupList; + GroupList groups; + GroupList::iterator current; + + void newGroup() { + std::ostringstream groupId(groupPrefix, ios_base::out|ios_base::ate); + groupId << groupSuffix++; + uint size = (randomizeSize) ? (rand() % groupSize) + 1 : groupSize; + // std::cout << "New group: GROUPID=[" << groupId.str() << "] size=" << size << std::endl; + GroupState group( groupId.str(), size ); + groups.push_back( group ); + } +}; + +}} // namespace qpid::tests + +using qpid::tests::Options; +using qpid::tests::Reporter; +using qpid::tests::Throughput; +using qpid::tests::ContentGenerator; +using qpid::tests::GroupGenerator; +using qpid::tests::GetlineContentGenerator; +using qpid::tests::MapContentGenerator; +using qpid::tests::FixedContentGenerator; +using qpid::tests::SN; +using qpid::tests::TS; +using qpid::tests::EOS; + +int main(int argc, char ** argv) +{ + Connection connection; + try { + Options opts; + if (opts.parse(argc, argv)) { + connection = Connection(opts.url, opts.connectionOptions); + connection.open(); + std::auto_ptr<FailoverUpdates> updates(opts.failoverUpdates ? new FailoverUpdates(connection) : 0); + Session session = opts.tx ? connection.createTransactionalSession() : connection.createSession(); + Sender sender = session.createSender(opts.address); + if (opts.capacity) sender.setCapacity(opts.capacity); + Message msg; + msg.setDurable(opts.durable); + if (opts.ttl) { + msg.setTtl(Duration(opts.ttl)); + } + if (opts.priority) { + msg.setPriority(opts.priority); + } + if (!opts.replyto.empty()) { + msg.setReplyTo(Address(opts.replyto)); + } + if (!opts.userid.empty()) msg.setUserId(opts.userid); + if (!opts.id.empty()) msg.setMessageId(opts.id); + if (!opts.correlationid.empty()) msg.setCorrelationId(opts.correlationid); + opts.setProperties(msg); + uint sent = 0; + uint txCount = 0; + Reporter<Throughput> reporter(std::cout, opts.reportEvery, opts.reportHeader); + + std::auto_ptr<ContentGenerator> contentGen; + if (opts.contentStdin) { + opts.messages = 0; // Don't limit # messages sent. + contentGen.reset(new GetlineContentGenerator); + } + else if (opts.entries.size() > 0) + contentGen.reset(new MapContentGenerator(opts)); + else if (opts.contentSize > 0) + contentGen.reset(new FixedContentGenerator(string(opts.contentSize, 'X'))); + else + contentGen.reset(new FixedContentGenerator(opts.contentString)); + + std::auto_ptr<GroupGenerator> groupGen; + if (!opts.groupKey.empty()) + groupGen.reset(new GroupGenerator(opts.groupKey, + opts.groupPrefix, + opts.groupSize, + opts.groupRandSize, + opts.groupInterleave)); + + qpid::sys::AbsTime start = qpid::sys::now(); + int64_t interval = 0; + if (opts.sendRate) interval = qpid::sys::TIME_SEC/opts.sendRate; + + while (contentGen->setContent(msg)) { + ++sent; + if (opts.sequence) + msg.getProperties()[SN] = sent; + if (groupGen.get()) + groupGen->setGroupInfo(msg); + + if (opts.timestamp) + msg.getProperties()[TS] = int64_t( + qpid::sys::Duration::FromEpoch()); + sender.send(msg); + reporter.message(msg); + + if (opts.tx && (sent % opts.tx == 0)) { + if (opts.rollbackFrequency && + (++txCount % opts.rollbackFrequency == 0)) + session.rollback(); + else + session.commit(); + } + if (opts.messages && sent >= opts.messages) break; + + if (opts.sendRate) { + qpid::sys::AbsTime waitTill(start, sent*interval); + int64_t delay = qpid::sys::Duration(qpid::sys::now(), waitTill); + if (delay > 0) qpid::sys::usleep(delay/qpid::sys::TIME_USEC); + } + } + if (opts.reportTotal) reporter.report(); + for (uint i = opts.sendEos; i > 0; --i) { + if (opts.sequence) + msg.getProperties()[SN] = ++sent; + msg.setContent(EOS); //TODO: add in ability to send digest or similar + sender.send(msg); + } + if (opts.tx) { + if (opts.rollbackFrequency && (++txCount % opts.rollbackFrequency == 0)) { + session.rollback(); + } else { + session.commit(); + } + } + session.sync(); + session.close(); + connection.close(); + return 0; + } + return 1; + } catch(const std::exception& error) { + std::cerr << "qpid-send: " << error.what() << std::endl; + connection.close(); + return 1; + } +} diff --git a/qpid/cpp/src/tests/qpid-src-rinstall b/qpid/cpp/src/tests/qpid-src-rinstall new file mode 100755 index 0000000000..c7473e9197 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-src-rinstall @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under onemake +# 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. +# + +# Copy the source tree and run "make install" on each of $HOSTS +# Must be run in a configured qpid build directory. + +absdir() { echo `cd $1 && pwd`; } + +test -f config.status || { echo "Not in a configured build directory."; } +CONFIGURE=`./config.status -V | grep '^configured by' | sed 's/^configured by \([^,]*\),.*$/\1/'` +CONFIG_OPTIONS=`./config.status -V | grep 'with options' | sed 's/^.*with options "\([^"]*\)".*$/\1/'` +set -ex +rsynchosts `absdir $(dirname $CONFIGURE)/..` # Copy cpp srcdir and siblings. +allhosts -bo rbuild.log "mkdir -p $PWD && cd $PWD && { test -f config.status || $CONFIGURE $CONFIG_OPTIONS; } && make && make -j1 install" diff --git a/qpid/cpp/src/tests/qpid-stream.cpp b/qpid/cpp/src/tests/qpid-stream.cpp new file mode 100644 index 0000000000..f02a484750 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-stream.cpp @@ -0,0 +1,193 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include <qpid/messaging/Connection.h> +#include <qpid/messaging/Message.h> +#include <qpid/messaging/Receiver.h> +#include <qpid/messaging/Sender.h> +#include <qpid/messaging/Session.h> +#include <qpid/sys/Runnable.h> +#include <qpid/sys/Thread.h> +#include <qpid/sys/Time.h> +#include <qpid/Options.h> +#include <iostream> +#include <string> + +using namespace qpid::messaging; +using namespace qpid::types; + +namespace qpid { +namespace tests { + +struct Args : public qpid::Options +{ + std::string url; + std::string address; + uint size; + uint rate; + bool durable; + uint receiverCapacity; + uint senderCapacity; + uint ackFrequency; + + Args() : + url("amqp:tcp:127.0.0.1:5672"), + address("test-queue"), + size(512), + rate(1000), + durable(false), + receiverCapacity(0), + senderCapacity(0), + ackFrequency(1) + { + addOptions() + ("url", qpid::optValue(url, "URL"), "Url to connect to.") + ("address", qpid::optValue(address, "ADDRESS"), "Address to stream messages through.") + ("size", qpid::optValue(size, "bytes"), "Message size in bytes (content only, not headers).") + ("rate", qpid::optValue(rate, "msgs/sec"), "Rate at which to stream messages.") + ("durable", qpid::optValue(durable, "true|false"), "Mark messages as durable.") + ("sender-capacity", qpid::optValue(senderCapacity, "N"), "Credit window (0 implies infinite window)") + ("receiver-capacity", qpid::optValue(receiverCapacity, "N"), "Credit window (0 implies infinite window)") + ("ack-frequency", qpid::optValue(ackFrequency, "N"), + "Ack frequency (0 implies none of the messages will get accepted)"); + } +}; + +Args opts; + +const std::string TS = "ts"; + +uint64_t timestamp(const qpid::sys::AbsTime& time) +{ + qpid::sys::Duration t(qpid::sys::EPOCH, time); + return t; +} + +struct Client : qpid::sys::Runnable +{ + virtual ~Client() {} + virtual void doWork(Session&) = 0; + + void run() + { + Connection connection(opts.url); + try { + connection.open(); + Session session = connection.createSession(); + doWork(session); + session.close(); + connection.close(); + } catch(const std::exception& error) { + std::cout << error.what() << std::endl; + connection.close(); + } + } + + qpid::sys::Thread thread; + + void start() { thread = qpid::sys::Thread(this); } + void join() { thread.join(); } +}; + +struct Publish : Client +{ + void doWork(Session& session) + { + Sender sender = session.createSender(opts.address); + if (opts.senderCapacity) sender.setCapacity(opts.senderCapacity); + Message msg(std::string(opts.size, 'X')); + uint64_t interval = qpid::sys::TIME_SEC / opts.rate; + uint64_t sent = 0, missedRate = 0; + qpid::sys::AbsTime start = qpid::sys::now(); + while (true) { + qpid::sys::AbsTime sentAt = qpid::sys::now(); + msg.getProperties()[TS] = timestamp(sentAt); + sender.send(msg); + ++sent; + qpid::sys::AbsTime waitTill(start, sent*interval); + qpid::sys::Duration delay(sentAt, waitTill); + if (delay < 0) { + ++missedRate; + } else { + qpid::sys::usleep(delay / qpid::sys::TIME_USEC); + } + } + } +}; + +struct Consume : Client +{ + void doWork(Session& session) + { + Message msg; + uint64_t received = 0; + double minLatency = std::numeric_limits<double>::max(); + double maxLatency = 0; + double totalLatency = 0; + Receiver receiver = session.createReceiver(opts.address); + if (opts.receiverCapacity) receiver.setCapacity(opts.receiverCapacity); + while (receiver.fetch(msg)) { + ++received; + if (opts.ackFrequency && (received % opts.ackFrequency == 0)) { + session.acknowledge(); + } + //calculate latency + uint64_t receivedAt = timestamp(qpid::sys::now()); + uint64_t sentAt = msg.getProperties()[TS].asUint64(); + double latency = ((double) (receivedAt - sentAt)) / qpid::sys::TIME_MSEC; + + //update avg, min & max + minLatency = std::min(minLatency, latency); + maxLatency = std::max(maxLatency, latency); + totalLatency += latency; + + if (received % opts.rate == 0) { + std::cout << "count=" << received + << ", avg=" << (totalLatency/received) + << ", min=" << minLatency + << ", max=" << maxLatency << std::endl; + } + } + } +}; + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char** argv) +{ + try { + opts.parse(argc, argv); + Publish publish; + Consume consume; + publish.start(); + consume.start(); + consume.join(); + publish.join(); + return 0; + } catch(const std::exception& error) { + std::cout << error.what() << std::endl; + } + return 1; +} + + diff --git a/qpid/cpp/src/tests/qpid-topic-listener.cpp b/qpid/cpp/src/tests/qpid-topic-listener.cpp new file mode 100644 index 0000000000..c42e76d760 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-topic-listener.cpp @@ -0,0 +1,209 @@ +/* + * + * 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. + * + */ + +/** + * This file provides one half of a test and example of a pub-sub + * style of interaction. See qpid-topic-publisher.cpp for the other half, + * in which the logic for publishing is defined. + * + * This file contains the listener logic. A listener will subscribe to + * a logical 'topic'. It will count the number of messages it receives + * and the time elapsed between the first one and the last one. It + * recognises two types of 'special' message that tell it to (a) send + * a report containing this information, (b) shutdown (i.e. stop + * listening). + */ + +#include "TestOptions.h" +#include "qpid/client/Connection.h" +#include "qpid/client/MessageListener.h" +#include "qpid/client/Session.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/sys/SystemInfo.h" +#include "qpid/sys/Time.h" +#include "qpid/framing/FieldValue.h" +#include <iostream> +#include <sstream> + +using namespace qpid; +using namespace qpid::client; +using namespace qpid::sys; +using namespace qpid::framing; +using namespace std; + +namespace qpid { +namespace tests { + +/** + * A message listener implementation in which the runtime logic is + * defined. + */ +class Listener : public MessageListener{ + Session session; + SubscriptionManager& mgr; + const string responseQueue; + const bool transactional; + bool init; + int count; + AbsTime start; + + void shutdown(); + void report(); +public: + Listener(const Session& session, SubscriptionManager& mgr, const string& reponseQueue, bool tx); + virtual void received(Message& msg); + Subscription subscription; +}; + +/** + * A utility class for managing the options passed in. + */ +struct Args : public qpid::TestOptions { + int ack; + bool transactional; + bool durable; + int prefetch; + string statusqueue; + + Args() : ack(0), transactional(false), durable(false), prefetch(0) { + addOptions() + ("ack", optValue(ack, "MODE"), "Ack frequency in messages (defaults to half the prefetch value)") + ("transactional", optValue(transactional), "Use transactions") + ("durable", optValue(durable), "subscribers should use durable queues") + ("prefetch", optValue(prefetch, "N"), "prefetch count (0 implies no flow control, and no acking)") + ("status-queue", optValue(statusqueue, "QUEUE-NAME"), "Message queue to put status messages on"); + } +}; + +Listener::Listener(const Session& s, SubscriptionManager& m, const string& _responseq, bool tx) : + session(s), mgr(m), responseQueue(_responseq), transactional(tx), init(false), count(0){} + +void Listener::received(Message& message){ + if(!init){ + start = now(); + count = 0; + init = true; + cout << "Batch started." << endl; + } + string type = message.getHeaders().getAsString("TYPE"); + + if(string("TERMINATION_REQUEST") == type){ + shutdown(); + }else if(string("REPORT_REQUEST") == type){ + subscription.accept(subscription.getUnaccepted()); // Accept everything upto this point + cout <<"Batch ended, sending report." << endl; + //send a report: + report(); + init = false; + }else if (++count % 1000 == 0){ + cout <<"Received " << count << " messages." << endl; + } +} + +void Listener::shutdown(){ + mgr.stop(); +} + +void Listener::report(){ + AbsTime finish = now(); + Duration time(start, finish); + stringstream reportstr; + reportstr << "Received " << count << " messages in " + << time/TIME_MSEC << " ms."; + Message msg(reportstr.str(), responseQueue); + msg.getHeaders().setString("TYPE", "REPORT"); + session.messageTransfer(arg::destination="amq.direct", arg::content=msg, arg::acceptMode=1); + if(transactional){ + sync(session).txCommit(); + } +} + +}} // namespace qpid::tests + +using namespace qpid::tests; + +/** + * The main routine creates a Listener instance and sets it up to + * consume from a private queue bound to the exchange with the + * appropriate topic name. + */ +int main(int argc, char** argv){ + try{ + Args args; + args.parse(argc, argv); + if(args.help) + cout << args << endl; + else { + Connection connection; + args.open(connection); + AsyncSession session = connection.newSession(); + + //declare exchange, queue and bind them: + session.queueDeclare(arg::queue="response"); + std::string control = "control_" + session.getId().str(); + if (args.durable) { + session.queueDeclare(arg::queue=control, arg::durable=true); + } else { + session.queueDeclare(arg::queue=control, arg::exclusive=true, arg::autoDelete=true); + } + session.exchangeBind(arg::exchange="amq.topic", arg::queue=control, arg::bindingKey="topic_control"); + + //set up listener + SubscriptionManager mgr(session); + Listener listener(session, mgr, "response", args.transactional); + SubscriptionSettings settings; + if (args.prefetch) { + settings.autoAck = (args.ack ? args.ack : (args.prefetch / 2)); + settings.flowControl = FlowControl::messageCredit(args.prefetch); + } else { + settings.acceptMode = ACCEPT_MODE_NONE; + settings.flowControl = FlowControl::unlimited(); + } + listener.subscription = mgr.subscribe(listener, control, settings); + session.sync(); + + if( args.statusqueue.length() > 0 ) { + stringstream msg_str; + msg_str << "qpid-topic-listener: " << qpid::sys::SystemInfo::getProcessId(); + session.messageTransfer(arg::content=Message(msg_str.str(), args.statusqueue)); + cout << "Ready status put on queue '" << args.statusqueue << "'" << endl; + } + + if (args.transactional) { + session.txSelect(); + } + + cout << "qpid-topic-listener: listening..." << endl; + mgr.run(); + if (args.durable) { + session.queueDelete(arg::queue=control); + } + session.close(); + cout << "closing connection" << endl; + connection.close(); + } + return 0; + } catch (const std::exception& error) { + cout << "qpid-topic-listener: " << error.what() << endl; + } + return 1; +} diff --git a/qpid/cpp/src/tests/qpid-topic-publisher.cpp b/qpid/cpp/src/tests/qpid-topic-publisher.cpp new file mode 100644 index 0000000000..f9107b90d0 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-topic-publisher.cpp @@ -0,0 +1,230 @@ +/* + * + * 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. + * + */ + +/** + * This file provides one half of a test and example of a pub-sub + * style of interaction. See qpid-topic-listener.cpp for the other half, in + * which the logic for subscribers is defined. + * + * This file contains the publisher logic. The publisher will send a + * number of messages to the exchange with the appropriate routing key + * for the logical 'topic'. Once it has done this it will then send a + * request that each subscriber report back with the number of message + * it has received and the time that elapsed between receiving the + * first one and receiving the report request. Once the expected + * number of reports are received, it sends out a request that each + * subscriber shutdown. + */ + +#include "TestOptions.h" +#include "qpid/client/Connection.h" +#include "qpid/client/MessageListener.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/sys/Monitor.h" +#include "qpid/sys/Time.h" +#include <cstdlib> +#include <iostream> + +using namespace qpid; +using namespace qpid::client; +using namespace qpid::sys; +using namespace std; + +namespace qpid { +namespace tests { + +/** + * The publishing logic is defined in this class. It implements + * message listener and can therfore be used to receive messages sent + * back by the subscribers. + */ +class Publisher { + AsyncSession session; + SubscriptionManager mgr; + LocalQueue queue; + const string controlTopic; + const bool transactional; + const bool durable; + + string generateData(int size); + +public: + Publisher(const AsyncSession& session, const string& controlTopic, bool tx, bool durable); + int64_t publish(int msgs, int listeners, int size); + void terminate(); +}; + +/** + * A utility class for managing the options passed in to the test + */ +struct Args : public TestOptions { + int messages; + int subscribers; + bool transactional; + bool durable; + int batches; + int delay; + int size; + string statusqueue; + + Args() : messages(1000), subscribers(1), + transactional(false), durable(false), + batches(1), delay(0), size(256) + { + addOptions() + ("messages", optValue(messages, "N"), "how many messages to send") + ("subscribers", optValue(subscribers, "N"), "how many subscribers to expect reports from") + ("transactional", optValue(transactional), "client should use transactions") + ("durable", optValue(durable), "messages should be durable") + ("batches", optValue(batches, "N"), "how many batches to run") + ("delay", optValue(delay, "SECONDS"), "Causes a delay between each batch") + ("size", optValue(size, "BYTES"), "size of the published messages") + ("status-queue", optValue(statusqueue, "QUEUE-NAME"), "Message queue to read status messages from"); + } +}; + +Publisher::Publisher(const AsyncSession& _session, const string& _controlTopic, bool tx, bool d) : + session(_session), mgr(session), controlTopic(_controlTopic), transactional(tx), durable(d) +{ + mgr.subscribe(queue, "response"); +} + +int64_t Publisher::publish(int msgs, int listeners, int size){ + Message msg(generateData(size), controlTopic); + if (durable) { + msg.getDeliveryProperties().setDeliveryMode(framing::PERSISTENT); + } + AbsTime start = now(); + + for(int i = 0; i < msgs; i++){ + session.messageTransfer(arg::content=msg, arg::destination="amq.topic", arg::acceptMode=1); + } + //send report request + Message reportRequest("", controlTopic); + reportRequest.getHeaders().setString("TYPE", "REPORT_REQUEST"); + session.messageTransfer(arg::content=reportRequest, arg::destination="amq.topic", arg::acceptMode=1); + if(transactional){ + sync(session).txCommit(); + } + //wait for a response from each listener (TODO, could log these) + for (int i = 0; i < listeners; i++) { + Message report = queue.pop(); + } + + if(transactional){ + sync(session).txCommit(); + } + + AbsTime finish = now(); + return Duration(start, finish); +} + +string Publisher::generateData(int size){ + string data; + for(int i = 0; i < size; i++){ + data += ('A' + (i / 26)); + } + return data; +} + +void Publisher::terminate(){ + //send termination request + Message terminationRequest("", controlTopic); + terminationRequest.getHeaders().setString("TYPE", "TERMINATION_REQUEST"); + session.messageTransfer(arg::content=terminationRequest, arg::destination="amq.topic", arg::acceptMode=1); + if(transactional){ + session.txCommit(); + } +} + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char** argv) { + try{ + Args args; + args.parse(argc, argv); + if(args.help) + cout << args << endl; + else { + Connection connection; + args.open(connection); + AsyncSession session = connection.newSession(); + + // If status-queue is defined, wait for all expected listeners to join in before we start + if( args.statusqueue.length() > 0 ) { + cout << "Waiting for " << args.subscribers << " listeners..." << endl; + SubscriptionManager statusSubs(session); + LocalQueue statusQ; + statusSubs.subscribe(statusQ, args.statusqueue); + for (int i = 0; i < args.subscribers; i++) { + Message m = statusQ.get(); + if( m.getData().find("topic_listener: ", 0) == 0 ) { + cout << "Listener " << (i+1) << " of " << args.subscribers + << " is ready (pid " << m.getData().substr(16, m.getData().length() - 16) + << ")" << endl; + } else { + throw Exception(QPID_MSG("Unexpected message received on status queue: " << m.getData())); + } + } + } + + if (args.transactional) { + session.txSelect(); + } + session.queueDeclare(arg::queue="response"); + session.exchangeBind(arg::exchange="amq.direct", arg::queue="response", arg::bindingKey="response"); + + Publisher publisher(session, "topic_control", args.transactional, args.durable); + + int batchSize(args.batches); + int64_t max(0); + int64_t min(0); + int64_t sum(0); + for(int i = 0; i < batchSize; i++){ + if(i > 0 && args.delay) qpid::sys::sleep(args.delay); + int64_t msecs = + publisher.publish(args.messages, + args.subscribers, + args.size) / TIME_MSEC; + if(!max || msecs > max) max = msecs; + if(!min || msecs < min) min = msecs; + sum += msecs; + cout << "Completed " << (i+1) << " of " << batchSize + << " in " << msecs << "ms" << endl; + } + publisher.terminate(); + int64_t avg = sum / batchSize; + if(batchSize > 1){ + cout << batchSize << " batches completed. avg=" << avg << + ", max=" << max << ", min=" << min << endl; + } + session.close(); + connection.close(); + } + return 0; + }catch(exception& error) { + cout << error.what() << endl; + } + return 1; +} diff --git a/qpid/cpp/src/tests/qpid-txtest.cpp b/qpid/cpp/src/tests/qpid-txtest.cpp new file mode 100644 index 0000000000..59ab905af7 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-txtest.cpp @@ -0,0 +1,342 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include <algorithm> +#include <iomanip> +#include <iostream> +#include <memory> +#include <sstream> +#include <vector> + +#include "TestOptions.h" +#include "qpid/client/Connection.h" +#include "qpid/client/Message.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/framing/Array.h" +#include "qpid/framing/Buffer.h" +#include "qpid/framing/FieldValue.h" +#include "qpid/framing/Uuid.h" +#include "qpid/sys/Thread.h" + +using namespace qpid; +using namespace qpid::client; +using namespace qpid::sys; +using std::string; + +namespace qpid { +namespace tests { + +typedef std::vector<std::string> StringSet; + +struct Args : public qpid::TestOptions { + bool init, transfer, check;//actions + uint size; + bool durable; + uint queues; + string base; + uint msgsPerTx; + uint txCount; + uint totalMsgCount; + bool dtx; + bool quiet; + + Args() : init(true), transfer(true), check(true), + size(256), durable(true), queues(2), + base("tx-test"), msgsPerTx(1), txCount(1), totalMsgCount(10), + dtx(false), quiet(false) + { + addOptions() + + ("init", optValue(init, "yes|no"), "Declare queues and populate one with the initial set of messages.") + ("transfer", optValue(transfer, "yes|no"), "'Move' messages from one queue to another using transactions to ensure no message loss.") + ("check", optValue(check, "yes|no"), "Check that the initial messages are all still available.") + ("size", optValue(size, "N"), "message size") + ("durable", optValue(durable, "yes|no"), "use durable messages") + ("queues", optValue(queues, "N"), "number of queues") + ("queue-base-name", optValue(base, "<name>"), "base name for queues") + ("messages-per-tx", optValue(msgsPerTx, "N"), "number of messages transferred per transaction") + ("tx-count", optValue(txCount, "N"), "number of transactions per 'agent'") + ("total-messages", optValue(totalMsgCount, "N"), "total number of messages in 'circulation'") + ("dtx", optValue(dtx, "yes|no"), "use distributed transactions") + ("quiet", optValue(quiet), "reduce output from test"); + } +}; + +const std::string chars("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); + +std::string generateData(uint size) +{ + if (size < chars.length()) { + return chars.substr(0, size); + } + std::string data; + for (uint i = 0; i < (size / chars.length()); i++) { + data += chars; + } + data += chars.substr(0, size % chars.length()); + return data; +} + +void generateSet(const std::string& base, uint count, StringSet& collection) +{ + for (uint i = 0; i < count; i++) { + std::ostringstream out; + out << base << "-" << (i+1); + collection.push_back(out.str()); + } +} + +Args opts; + +struct Client +{ + Connection connection; + AsyncSession session; + + Client() + { + opts.open(connection); + session = connection.newSession(); + } + + ~Client() + { + try{ + session.close(); + connection.close(); + } catch(const std::exception& e) { + std::cout << e.what() << std::endl; + } + } +}; + +struct Transfer : public Client, public Runnable +{ + std::string src; + std::string dest; + Thread thread; + framing::Xid xid; + + Transfer(const std::string& to, const std::string& from) : src(to), dest(from), xid(0x4c414e47, "", from) {} + + void run() + { + try { + + if (opts.dtx) session.dtxSelect(); + else session.txSelect(); + SubscriptionManager subs(session); + + LocalQueue lq; + SubscriptionSettings settings(FlowControl::messageWindow(opts.msgsPerTx)); + settings.autoAck = 0; // Disabled + Subscription sub = subs.subscribe(lq, src, settings); + + for (uint t = 0; t < opts.txCount; t++) { + Message in; + Message out("", dest); + if (opts.dtx) { + setNewXid(xid); + session.dtxStart(arg::xid=xid); + } + for (uint m = 0; m < opts.msgsPerTx; m++) { + in = lq.pop(); + std::string& data = in.getData(); + if (data.size() != opts.size) { + std::ostringstream oss; + oss << "Message size incorrect: size=" << in.getData().size() << "; expected " << opts.size; + throw std::runtime_error(oss.str()); + } + out.setData(data); + out.getMessageProperties().setCorrelationId(in.getMessageProperties().getCorrelationId()); + out.getDeliveryProperties().setDeliveryMode(in.getDeliveryProperties().getDeliveryMode()); + session.messageTransfer(arg::content=out, arg::acceptMode=1); + } + sub.accept(sub.getUnaccepted()); + if (opts.dtx) { + session.dtxEnd(arg::xid=xid); + session.dtxPrepare(arg::xid=xid); + session.dtxCommit(arg::xid=xid); + } else { + session.txCommit(); + } + session.sync(); + } + } catch(const std::exception& e) { + std::cout << "Transfer interrupted: " << e.what() << std::endl; + } + } + + void setNewXid(framing::Xid& xid) { + framing::Uuid uuid(true); + xid.setGlobalId(uuid.str()); + } +}; + +struct Controller : public Client +{ + StringSet ids; + StringSet queues; + + Controller() + { + generateSet(opts.base, opts.queues, queues); + generateSet("msg", opts.totalMsgCount, ids); + } + + void init() + { + //declare queues + for (StringSet::iterator i = queues.begin(); i != queues.end(); i++) { + session.queueDeclare(arg::queue=*i, arg::durable=opts.durable); + session.sync(); + } + + Message msg(generateData(opts.size), *queues.begin()); + if (opts.durable) { + msg.getDeliveryProperties().setDeliveryMode(framing::PERSISTENT); + } + + //publish messages + for (StringSet::iterator i = ids.begin(); i != ids.end(); i++) { + msg.getMessageProperties().setCorrelationId(*i); + session.messageTransfer(arg::content=msg, arg::acceptMode=1); + } + } + + void transfer() + { + boost::ptr_vector<Transfer> agents(opts.queues); + //launch transfer agents + for (StringSet::iterator i = queues.begin(); i != queues.end(); i++) { + StringSet::iterator next = i + 1; + if (next == queues.end()) next = queues.begin(); + + if (!opts.quiet) std::cout << "Transfering from " << *i << " to " << *next << std::endl; + agents.push_back(new Transfer(*i, *next)); + agents.back().thread = Thread(agents.back()); + } + + for (boost::ptr_vector<Transfer>::iterator i = agents.begin(); i != agents.end(); i++) { + i->thread.join(); + } + } + + int check() + { + SubscriptionManager subs(session); + + // Recover DTX transactions (if any) + if (opts.dtx) { + framing::DtxRecoverResult dtxRes = session.dtxRecover().get(); + const framing::Array& xidArr = dtxRes.getInDoubt(); + std::vector<std::string> inDoubtXids(xidArr.size()); + std::transform(xidArr.begin(), xidArr.end(), inDoubtXids.begin(), framing::Array::get<std::string, framing::Array::ValuePtr>); + + if (inDoubtXids.size()) { + if (!opts.quiet) std::cout << "Recovering DTX in-doubt transaction(s):" << std::endl; + framing::StructHelper decoder; + framing::Xid xid; + // abort even, commit odd transactions + for (unsigned i = 0; i < inDoubtXids.size(); i++) { + decoder.decode(xid, inDoubtXids[i]); + if (!opts.quiet) std::cout << (i%2 ? " * aborting " : " * committing "); + xid.print(std::cout); + std::cout << std::endl; + if (i%2) { + session.dtxRollback(arg::xid=xid); + } else { + session.dtxCommit(arg::xid=xid); + } + } + } + } + + StringSet drained; + //drain each queue and verify the correct set of messages are available + for (StringSet::iterator i = queues.begin(); i != queues.end(); i++) { + //subscribe, allocate credit and flushn + LocalQueue lq; + SubscriptionSettings settings(FlowControl::unlimited(), ACCEPT_MODE_NONE); + subs.subscribe(lq, *i, settings); + session.messageFlush(arg::destination=*i); + session.sync(); + + uint count(0); + while (!lq.empty()) { + Message m = lq.pop(); + //add correlation ids of received messages to drained + drained.push_back(m.getMessageProperties().getCorrelationId()); + ++count; + } + if (!opts.quiet) std::cout << "Drained " << count << " messages from " << *i << std::endl; + } + + sort(ids.begin(), ids.end()); + sort(drained.begin(), drained.end()); + + //check that drained == ids + StringSet missing; + set_difference(ids.begin(), ids.end(), drained.begin(), drained.end(), back_inserter(missing)); + + StringSet extra; + set_difference(drained.begin(), drained.end(), ids.begin(), ids.end(), back_inserter(extra)); + + if (missing.empty() && extra.empty()) { + std::cout << "All expected messages were retrieved." << std::endl; + return 0; + } else { + if (!missing.empty()) { + std::cout << "The following ids were missing:" << std::endl; + for (StringSet::iterator i = missing.begin(); i != missing.end(); i++) { + std::cout << " '" << *i << "'" << std::endl; + } + } + if (!extra.empty()) { + std::cout << "The following extra ids were encountered:" << std::endl; + for (StringSet::iterator i = extra.begin(); i != extra.end(); i++) { + std::cout << " '" << *i << "'" << std::endl; + } + } + return 1; + } + } +}; + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char** argv) +{ + try { + opts.parse(argc, argv); + Controller controller; + if (opts.init) controller.init(); + if (opts.transfer) controller.transfer(); + if (opts.check) return controller.check(); + return 0; + } catch(const std::exception& e) { + std::cout << e.what() << std::endl; + } + return 2; +} diff --git a/qpid/cpp/src/tests/qpid-txtest2.cpp b/qpid/cpp/src/tests/qpid-txtest2.cpp new file mode 100644 index 0000000000..58c48f9a8d --- /dev/null +++ b/qpid/cpp/src/tests/qpid-txtest2.cpp @@ -0,0 +1,363 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include <algorithm> +#include <iomanip> +#include <iostream> +#include <memory> +#include <sstream> +#include <vector> + +#include "qpid/messaging/Connection.h" +#include "qpid/messaging/Duration.h" +#include "qpid/messaging/Message.h" +#include "qpid/messaging/Receiver.h" +#include "qpid/messaging/Sender.h" +#include "qpid/messaging/Session.h" +#include <qpid/Options.h> +#include <qpid/log/Logger.h> +#include <qpid/log/Options.h> +#include "qpid/sys/Runnable.h" +#include "qpid/sys/Thread.h" + +using namespace qpid::messaging; +using namespace qpid::sys; + +namespace qpid { +namespace tests { + +typedef std::vector<std::string> StringSet; + +struct Options : public qpid::Options { + bool help; + bool init, transfer, check;//actions + uint size; + bool durable; + uint queues; + std::string base; + uint msgsPerTx; + uint txCount; + uint totalMsgCount; + bool dtx; + uint capacity; + std::string url; + std::string connectionOptions; + qpid::log::Options log; + uint port; + bool quiet; + double fetchTimeout; + + Options() : help(false), init(true), transfer(true), check(true), + size(256), durable(true), queues(2), + base("tx"), msgsPerTx(1), txCount(5), totalMsgCount(10), + capacity(1000), url("localhost"), port(0), quiet(false), fetchTimeout(5) + { + addOptions() + ("init", qpid::optValue(init, "yes|no"), "Declare queues and populate one with the initial set of messages.") + ("transfer", qpid::optValue(transfer, "yes|no"), "'Move' messages from one queue to another using transactions to ensure no message loss.") + ("check", qpid::optValue(check, "yes|no"), "Check that the initial messages are all still available.") + ("size", qpid::optValue(size, "N"), "message size") + ("durable", qpid::optValue(durable, "yes|no"), "use durable messages") + ("queues", qpid::optValue(queues, "N"), "number of queues") + ("queue-base-name", qpid::optValue(base, "<name>"), "base name for queues") + ("messages-per-tx", qpid::optValue(msgsPerTx, "N"), "number of messages transferred per transaction") + ("tx-count", qpid::optValue(txCount, "N"), "number of transactions per 'agent'") + ("total-messages", qpid::optValue(totalMsgCount, "N"), "total number of messages in 'circulation'") + ("capacity", qpid::optValue(capacity, "N"), "Pre-fetch window (0 implies no pre-fetch)") + ("broker,b", qpid::optValue(url, "URL"), "url of broker to connect to") + ("connection-options", qpid::optValue(connectionOptions, "OPTIONS"), "options for the connection") + ("port,p", qpid::optValue(port, "PORT"), "(for test compatibility only, use broker option instead)") + ("quiet", qpid::optValue(quiet), "reduce output from test") + ("fetch-timeout", qpid::optValue(fetchTimeout, "SECONDS"), "Timeout for transactional fetch") + ("help", qpid::optValue(help), "print this usage statement"); + add(log); + } + + bool parse(int argc, char** argv) + { + try { + qpid::Options::parse(argc, argv); + if (port) { + if (url == "localhost") { + std::stringstream u; + u << url << ":" << port; + url = u.str(); + } else { + std::cerr << *this << std::endl << std::endl + << "--port and --broker should not be specified together; specify full url in --broker option" << std::endl; + return false; + } + + } + qpid::log::Logger::instance().configure(log); + if (help) { + std::cout << *this << std::endl << std::endl + << "Transactionally moves messages between queues" << std::endl; + return false; + } + if (totalMsgCount < msgsPerTx) { + totalMsgCount = msgsPerTx; // Must have at least msgsPerTx total messages. + } + return true; + } catch (const std::exception& e) { + std::cerr << *this << std::endl << std::endl << e.what() << std::endl; + return false; + } + } +}; + +const std::string chars("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); + +std::string generateData(uint size) +{ + if (size < chars.length()) { + return chars.substr(0, size); + } + std::string data; + for (uint i = 0; i < (size / chars.length()); i++) { + data += chars; + } + data += chars.substr(0, size % chars.length()); + return data; +} + +void generateSet(const std::string& base, uint count, StringSet& collection) +{ + for (uint i = 0; i < count; i++) { + std::ostringstream digits; + digits << count; + std::ostringstream out; + out << base << "-" << std::setw(digits.str().size()) << std::setfill('0') << (i+1); + collection.push_back(out.str()); + } +} + +struct Client +{ + const Options& opts; + Connection connection; + Session session; + + Client(const Options& o, bool transactional=false) : opts(o), connection(opts.url, opts.connectionOptions) + { + connection.open(); + session = transactional ? connection.createTransactionalSession() : connection.createSession(); + } + + virtual ~Client() + { + try { + session.sync(); + session.close(); + connection.close(); + } catch(const std::exception& e) { + std::cout << "Client shutdown: " << e.what() << std::endl; + } + } +}; + +struct TransactionalClient : Client +{ + TransactionalClient(const Options& o) : Client(o, true) {} + virtual ~TransactionalClient() {} +}; + +struct Transfer : public TransactionalClient, public Runnable +{ + const std::string target; + const std::string source; + Thread thread; + bool failed; + + Transfer(const std::string& to, const std::string& from, const Options& opts) : TransactionalClient(opts), target(to), source(from), failed(false) {} + + void run() + { + try { + + Sender sender(session.createSender(target)); + Receiver receiver(session.createReceiver(source)); + receiver.setCapacity(opts.capacity); + for (uint t = 0; t < opts.txCount;) { + std::ostringstream id; + id << source << ">" << target << ":" << t+1; + try { + for (uint m = 0; m < opts.msgsPerTx; m++) { + Message msg = receiver.fetch(Duration::SECOND*uint64_t(opts.fetchTimeout)); + if (msg.getContentSize() != opts.size) { + std::ostringstream oss; + oss << "Message size incorrect: size=" << msg.getContentSize() << "; expected " << opts.size; + throw std::runtime_error(oss.str()); + } + sender.send(msg); + } + session.commit(); + t++; + if (!opts.quiet) std::cout << "Transaction " << id.str() << " of " << opts.txCount << " committed successfully" << std::endl; + } catch (const TransactionAborted&) { + std::cout << "Transaction " << id.str() << " of " << opts.txCount << " was aborted and will be retried" << std::endl; + session = connection.createTransactionalSession(); + sender = session.createSender(target); + receiver = session.createReceiver(source); + receiver.setCapacity(opts.capacity); + } + } + sender.close(); + receiver.close(); + } catch(const std::exception& e) { + failed = true; + QPID_LOG(error, "Transfer " << source << " to " << target << " interrupted: " << e.what()); + } + } +}; + +namespace { +const std::string CREATE_DURABLE("; {create:always, node:{durable:True}}"); +const std::string CREATE_NON_DURABLE("; {create:always}"); +} + +struct Controller : public Client +{ + StringSet ids; + StringSet queues; + + Controller(const Options& opts) : Client(opts) + { + generateSet(opts.base, opts.queues, queues); + generateSet("msg", opts.totalMsgCount, ids); + } + + void init() + { + Message msg(generateData(opts.size)); + msg.setDurable(opts.durable); + + for (StringSet::iterator i = queues.begin(); i != queues.end(); i++) { + std::string address = *i + (opts.durable ? CREATE_DURABLE : CREATE_NON_DURABLE); + + // Clear out any garbage on queues. + Receiver receiver = session.createReceiver(address); + Message rmsg; + uint count(0); + while (receiver.fetch(rmsg, Duration::IMMEDIATE)) ++count; + session.acknowledge(); + receiver.close(); + if (!opts.quiet) std::cout << "Cleaned up " << count << " messages from " << *i << std::endl; + + Sender sender = session.createSender(address); + if (i == queues.begin()) { + for (StringSet::iterator i = ids.begin(); i != ids.end(); i++) { + msg.setCorrelationId(*i); + sender.send(msg); + } + } + sender.close(); + } + } + + void transfer() + { + boost::ptr_vector<Transfer> agents(opts.queues); + //launch transfer agents + for (StringSet::iterator i = queues.begin(); i != queues.end(); i++) { + StringSet::iterator next = i + 1; + if (next == queues.end()) next = queues.begin(); + + if (!opts.quiet) std::cout << "Transfering from " << *i << " to " << *next << std::endl; + agents.push_back(new Transfer(*i, *next, opts)); + agents.back().thread = Thread(agents.back()); + } + + for (boost::ptr_vector<Transfer>::iterator i = agents.begin(); i != agents.end(); i++) + i->thread.join(); + for (boost::ptr_vector<Transfer>::iterator i = agents.begin(); i != agents.end(); i++) + if (i->failed) + throw std::runtime_error("Transfer agents failed"); + } + + int check() + { + StringSet drained; + //drain each queue and verify the correct set of messages are available + for (StringSet::iterator i = queues.begin(); i != queues.end(); i++) { + Receiver receiver = session.createReceiver(*i); + uint count(0); + Message msg; + while (receiver.fetch(msg, Duration::IMMEDIATE)) { + //add correlation ids of received messages to drained + drained.push_back(msg.getCorrelationId()); + ++count; + } + session.acknowledge(); + receiver.close(); + if (!opts.quiet) std::cout << "Drained " << count << " messages from " << *i << std::endl; + } + sort(ids.begin(), ids.end()); + sort(drained.begin(), drained.end()); + + //check that drained == ids + StringSet missing; + set_difference(ids.begin(), ids.end(), drained.begin(), drained.end(), back_inserter(missing)); + + StringSet extra; + set_difference(drained.begin(), drained.end(), ids.begin(), ids.end(), back_inserter(extra)); + + if (missing.empty() && extra.empty()) { + std::cout << "All expected messages were retrieved." << std::endl; + return 0; + } else { + if (!missing.empty()) { + std::cout << "The following ids were missing:" << std::endl; + for (StringSet::iterator i = missing.begin(); i != missing.end(); i++) { + std::cout << " '" << *i << "'" << std::endl; + } + } + if (!extra.empty()) { + std::cout << "The following extra ids were encountered:" << std::endl; + for (StringSet::iterator i = extra.begin(); i != extra.end(); i++) { + std::cout << " '" << *i << "'" << std::endl; + } + } + return 1; + } + } +}; +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char** argv) +{ + try { + Options opts; + if (opts.parse(argc, argv)) { + Controller controller(opts); + if (opts.init) controller.init(); + if (opts.transfer) controller.transfer(); + if (opts.check) return controller.check(); + return 0; + } + return 1; + } catch(const std::exception& e) { + std::cerr << argv[0] << ": " << e.what() << std::endl; + } + return 2; +} diff --git a/qpid/cpp/src/tests/qpidd-empty.conf b/qpid/cpp/src/tests/qpidd-empty.conf new file mode 100644 index 0000000000..cf3f19fba0 --- /dev/null +++ b/qpid/cpp/src/tests/qpidd-empty.conf @@ -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. +# + +# An empty configuration file. +# Used when running tests to avoid picking up configuration +# installed in the default place. diff --git a/qpid/cpp/src/tests/qpidd-p0 b/qpid/cpp/src/tests/qpidd-p0 new file mode 100755 index 0000000000..1f7807afd2 --- /dev/null +++ b/qpid/cpp/src/tests/qpidd-p0 @@ -0,0 +1,46 @@ +#!/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. +# + +# Wrapper script to allocate a port and fork a broker to listen on it. +# +# Instead of this: +# qpidd --port 0 <qpidd-args...> +# do this: +# qpidd-p0 <qpidd-args...> +# +# The port is bound by python code, and then handed over to the broker via the +# --socket-fd option. This avoids problems with the qpidd --port 0 option which +# ocassional fails with an "address in use" error. It's not clear why --port 0 +# doesn't work, it may be to do with the way qpidd binds a port to multiple +# addresses on a multi-homed host. +# + +import subprocess, socket, time, os, sys + +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +s.bind(("", 0)) +s.listen(5) +port = s.getsockname()[1] +print port +sys.stdout.flush() +if len(sys.argv) > 1: + cmd = sys.argv[1:] + ["--socket-fd", str(s.fileno()), "--listen-disable=tcp"] + os.execvp(sys.argv[1], cmd) diff --git a/qpid/cpp/src/tests/qpidd_qmfv2_tests.py b/qpid/cpp/src/tests/qpidd_qmfv2_tests.py new file mode 100755 index 0000000000..2b45cb6eea --- /dev/null +++ b/qpid/cpp/src/tests/qpidd_qmfv2_tests.py @@ -0,0 +1,278 @@ +#!/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. +# + +# Runs QMF tests against a broker running with QMFv1 disabled. This forces the +# broker to use QMFv2 only. This is necessary as if there is a bug in V2, some +# V1 operations may hide that (esp. asynchonous notifications) + + +import sys, shutil, os +from time import sleep +from brokertest import * +from qpid.messaging import Message +try: import qmf.console +except: print "Cannot import module qmf.console, skipping tests"; exit(0); + +import qpid.messaging, brokertest +brokertest.qm = qpid.messaging # TODO aconway 2014-04-04: Tests fail with SWIG client. + +class ConsoleTest(BrokerTest): + """ + Test QMFv2 support using the qmf.console library. + """ + PUB_INTERVAL=1 + + def setUp(self): + BrokerTest.setUp(self) + + def _startBroker(self, QMFv1=False ): + self._broker_is_v1 = QMFv1 + if self._broker_is_v1: + args = ["--mgmt-qmf1=yes", "--mgmt-qmf2=no"] + else: + args = ["--mgmt-qmf1=no", "--mgmt-qmf2=yes"] + + args.append("--mgmt-pub-interval=%d" % self.PUB_INTERVAL) + self.broker = BrokerTest.broker(self, args) + + + def _myStartQmf(self, broker, console=None): + # I manually set up the QMF session here rather than call the startQmf + # method from BrokerTest as I can guarantee the console library is used + # (assuming BrokerTest's implementation of startQmf could change) + self.qmf_session = qmf.console.Session(console) + self.qmf_broker = self.qmf_session.addBroker("%s:%s" % (broker.host(), + broker.port())) + + def _create_queue( self, q_name, args={} ): + broker = self.qmf_session.getObjects(_class="broker")[0] + result = broker.create("queue", q_name, args, False) + self.assertEqual(result.status, 0, result) + + + def _test_method_call(self): + """ Verify method calls work, and check the behavior of getObjects() + call + """ + self._myStartQmf( self.broker ) + self._create_queue( "fleabag", {"auto-delete":True} ) + + qObj = None + queues = self.qmf_session.getObjects(_class="queue") + for q in queues: + if q.name == "fleabag": + qObj = q + break + self.assertNotEqual(qObj, None, "Failed to get queue object") + #print qObj + + def _test_unsolicited_updates(self): + """ Verify that the Console callbacks work + """ + + class Handler(qmf.console.Console): + def __init__(self): + self.v1_oids = 0 + self.v1_events = 0 + self.v2_oids = 0 + self.v2_events = 0 + self.broker_info = [] + self.broker_conn = [] + self.newpackage = [] + self.newclass = [] + self.agents = [] + self.events = [] + self.updates = {} # holds the objects by OID + self.heartbeats = [] + + def brokerInfo(self, broker): + #print "brokerInfo:", broker + self.broker_info.append(broker) + def brokerConnected(self, broker): + #print "brokerConnected:", broker + self.broker_conn.append(broker) + def newPackage(self, name): + #print "newPackage:", name + self.newpackage.append(name) + def newClass(self, kind, classKey): + #print "newClass:", kind, classKey + self.newclass.append( (kind, classKey) ) + def newAgent(self, agent): + #print "newAgent:", agent + self.agents.append( agent ) + def event(self, broker, event): + #print "EVENT %s" % event + self.events.append(event) + if event.isV2: + self.v2_events += 1 + else: + self.v1_events += 1 + + def heartbeat(self, agent, timestamp): + #print "Heartbeat %s" % agent + self.heartbeats.append( (agent, timestamp) ) + + # generic handler for objectProps and objectStats + def _handle_obj_update(self, record): + oid = record.getObjectId() + if oid.isV2: + self.v2_oids += 1 + else: + self.v1_oids += 1 + + if oid not in self.updates: + self.updates[oid] = record + else: + self.updates[oid].mergeUpdate( record ) + + def objectProps(self, broker, record): + assert len(record.getProperties()), "objectProps() invoked with no properties?" + self._handle_obj_update(record) + + def objectStats(self, broker, record): + assert len(record.getStatistics()), "objectStats() invoked with no properties?" + self._handle_obj_update(record) + + handler = Handler() + self._myStartQmf( self.broker, handler ) + # this should force objectProps, queueDeclare Event callbacks + self._create_queue( "fleabag", {"auto-delete":True} ) + # this should force objectStats callback + self.broker.send_message( "fleabag", Message("Hi") ) + # and we should get a few heartbeats + sleep(self.PUB_INTERVAL) + self.broker.send_message( "fleabag", Message("Hi") ) + sleep(self.PUB_INTERVAL) + self.broker.send_message( "fleabag", Message("Hi") ) + sleep(self.PUB_INTERVAL * 2) + + assert handler.broker_info, "No BrokerInfo callbacks received" + assert handler.broker_conn, "No BrokerConnected callbacks received" + assert handler.newpackage, "No NewPackage callbacks received" + assert handler.newclass, "No NewClass callbacks received" + assert handler.agents, "No NewAgent callbacks received" + assert handler.events, "No event callbacks received" + assert handler.updates, "No updates received" + assert handler.heartbeats, "No heartbeat callbacks received" + + # now verify updates for queue "fleabag" were received, and the + # msgDepth statistic is correct + + msgs = 0 + for o in handler.updates.itervalues(): + key = o.getClassKey() + if key and key.getClassName() == "queue" and o.name == "fleabag": + assert o.msgDepth, "No update to msgDepth statistic!" + msgs = o.msgDepth + break + assert msgs == 3, "msgDepth statistics not accurate!" + + # verify that the published objects were of the correct QMF version + if self._broker_is_v1: + assert handler.v1_oids and handler.v2_oids == 0, "QMFv2 updates received while in V1-only mode!" + assert handler.v1_events and handler.v2_events == 0, "QMFv2 events received while in V1-only mode!" + else: + assert handler.v2_oids and handler.v1_oids == 0, "QMFv1 updates received while in V2-only mode!" + assert handler.v2_events and handler.v1_events == 0, "QMFv1 events received while in V2-only mode!" + + def _test_async_method(self): + class Handler (qmf.console.Console): + def __init__(self): + self.cv = Condition() + self.xmtList = {} + self.rcvList = {} + + def methodResponse(self, broker, seq, response): + self.cv.acquire() + try: + self.rcvList[seq] = response + finally: + self.cv.release() + + def request(self, broker, count): + self.count = count + for idx in range(count): + self.cv.acquire() + try: + seq = broker.echo(idx, "Echo Message", _async = True) + self.xmtList[seq] = idx + finally: + self.cv.release() + + def check(self): + if self.count != len(self.xmtList): + return "fail (attempted send=%d, actual sent=%d)" % (self.count, len(self.xmtList)) + lost = 0 + mismatched = 0 + for seq in self.xmtList: + value = self.xmtList[seq] + if seq in self.rcvList: + result = self.rcvList.pop(seq) + if result.sequence != value: + mismatched += 1 + else: + lost += 1 + spurious = len(self.rcvList) + if lost == 0 and mismatched == 0 and spurious == 0: + return "pass" + else: + return "fail (lost=%d, mismatch=%d, spurious=%d)" % (lost, mismatched, spurious) + + handler = Handler() + self._myStartQmf(self.broker, handler) + broker = self.qmf_session.getObjects(_class="broker")[0] + handler.request(broker, 20) + sleep(1) + self.assertEqual(handler.check(), "pass") + + def test_method_call(self): + self._startBroker() + self._test_method_call() + + def test_unsolicited_updates(self): + self._startBroker() + self._test_unsolicited_updates() + + def test_async_method(self): + self._startBroker() + self._test_async_method() + + # For now, include "QMFv1 only" tests. Once QMFv1 is deprecated, these can + # be removed + + def test_method_call_v1(self): + self._startBroker(QMFv1=True) + self._test_method_call() + + def test_unsolicited_updates_v1(self): + self._startBroker(QMFv1=True) + self._test_unsolicited_updates() + + def test_async_method_v1(self): + self._startBroker(QMFv1=True) + self._test_async_method() + + + +if __name__ == "__main__": + shutil.rmtree("brokertest.tmp", True) + os.execvp("qpid-python-test", + ["qpid-python-test", "-m", "qpidd_qmfv2_tests"] + sys.argv[1:]) + diff --git a/qpid/cpp/src/tests/queue_flow_limit_tests.py b/qpid/cpp/src/tests/queue_flow_limit_tests.py new file mode 100644 index 0000000000..83e31e979b --- /dev/null +++ b/qpid/cpp/src/tests/queue_flow_limit_tests.py @@ -0,0 +1,376 @@ +#!/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. +# + +import sys +from qpid.testlib import TestBase010 +from qpid.messaging import Connection +from threading import Thread +from time import sleep, time +from os import environ, popen + +class QueueFlowLimitTests(TestBase010): + + _timeout = 100 + + def __getattr__(self, name): + if name == "assertGreater": + return lambda a, b: self.failUnless(a > b) + else: + raise AttributeError + + def _create_queue(self, name, + stop_count=None, resume_count=None, + stop_size=None, resume_size=None, + max_size=None, max_count=None): + """ Create a queue with the given flow settings via the queue.declare + command. + """ + args={} + if (stop_count is not None): + args["qpid.flow_stop_count"] = stop_count; + if (resume_count is not None): + args["qpid.flow_resume_count"] = resume_count; + if (stop_size is not None): + args["qpid.flow_stop_size"] = stop_size; + if (resume_size is not None): + args["qpid.flow_resume_size"] = resume_size; + if (max_size is not None): + args["qpid.max_size"] = max_size; + if (max_count is not None): + args["qpid.max_count"] = max_count; + + broker = self.qmf.getObjects(_class="broker")[0] + rc = broker.create( "queue", name, args, True ) + self.assertEqual(rc.status, 0, rc) + + qs = self.qmf.getObjects(_class="queue") + for i in qs: + if i.name == name: + # verify flow settings + if (stop_count is not None): + self.assertEqual(i.arguments.get("qpid.flow_stop_count"), stop_count) + if (resume_count is not None): + self.assertEqual(i.arguments.get("qpid.flow_resume_count"), resume_count) + if (stop_size is not None): + self.assertEqual(i.arguments.get("qpid.flow_stop_size"), stop_size) + if (resume_size is not None): + self.assertEqual(i.arguments.get("qpid.flow_resume_size"), resume_size) + if (max_size is not None): + self.assertEqual(i.arguments.get("qpid.max_size"), max_size) + if (max_count is not None): + self.assertEqual(i.arguments.get("qpid.max_count"), max_count) + self.failIf(i.flowStopped) + return i.getObjectId() + self.fail("Unable to create queue '%s'" % name) + return None + + + def _delete_queue(self, name): + """ Delete a named queue + """ + broker = self.qmf.getObjects(_class="broker")[0] + rc = broker.delete( "queue", name, {} ) + self.assertEqual(rc.status, 0, rc) + + + def _start_qpid_send(self, queue, count, content="X", capacity=100): + """ Use the qpid-send client to generate traffic to a queue. + """ + command = "qpid-send" + \ + " -b" + " %s:%s" % (self.broker.host, self.broker.port) \ + + " -a " + str(queue) \ + + " --messages " + str(count) \ + + " --content-string " + str(content) \ + + " --capacity " + str(capacity) + return popen(command) + + def _start_qpid_receive(self, queue, count, timeout=5): + """ Use the qpid-receive client to consume from a queue. + Note well: prints one line of text to stdout for each consumed msg. + """ + command = "qpid-receive" + \ + " -b " + "%s:%s" % (self.broker.host, self.broker.port) \ + + " -a " + str(queue) \ + + " --messages " + str(count) \ + + " --timeout " + str(timeout) \ + + " --print-content yes" + return popen(command) + + def test_qpid_config_cmd(self): + """ Test the qpid-config command's ability to configure a queue's flow + control thresholds. + """ + tool = environ.get("QPID_CONFIG_EXEC") + if tool: + command = tool + \ + " --broker=%s:%s " % (self.broker.host, self.broker.port) \ + + "add queue test01 --flow-stop-count=999" \ + + " --flow-resume-count=55 --flow-stop-size=5000000" \ + + " --flow-resume-size=100000" + cmd = popen(command) + rc = cmd.close() + self.assertEqual(rc, None) + + # now verify the settings + self.startQmf(); + qs = self.qmf.getObjects(_class="queue") + for i in qs: + if i.name == "test01": + self.assertEqual(i.arguments.get("qpid.flow_stop_count"), 999) + self.assertEqual(i.arguments.get("qpid.flow_resume_count"), 55) + self.assertEqual(i.arguments.get("qpid.flow_stop_size"), 5000000) + self.assertEqual(i.arguments.get("qpid.flow_resume_size"), 100000) + self.failIf(i.flowStopped) + break; + self.assertEqual(i.name, "test01") + self._delete_queue("test01") + + + def test_flow_count(self): + """ Create a queue with count-based flow limit. Spawn several + producers which will exceed the limit. Verify limit exceeded. Consume + all messages. Verify flow control released. + """ + self.startQmf(); + oid = self._create_queue("test-q", stop_count=373, resume_count=229) + self.assertEqual(self.qmf.getObjects(_objectId=oid)[0].flowStoppedCount, 0) + + sndr1 = self._start_qpid_send("test-q", count=1213, content="XXX", capacity=50); + sndr2 = self._start_qpid_send("test-q", count=797, content="Y", capacity=13); + sndr3 = self._start_qpid_send("test-q", count=331, content="ZZZZZ", capacity=149); + totalMsgs = 1213 + 797 + 331 + + # wait until flow control is active + deadline = time() + self._timeout + while (not self.qmf.getObjects(_objectId=oid)[0].flowStopped) and \ + time() < deadline: + pass + self.failUnless(self.qmf.getObjects(_objectId=oid)[0].flowStopped) + depth = self.qmf.getObjects(_objectId=oid)[0].msgDepth + self.assertGreater(depth, 373) + + # now wait until the enqueues stop happening - ensure that + # not all msgs have been sent (senders are blocked) + sleep(1) + newDepth = self.qmf.getObjects(_objectId=oid)[0].msgDepth + while depth != newDepth: + depth = newDepth; + sleep(1) + newDepth = self.qmf.getObjects(_objectId=oid)[0].msgDepth + self.assertGreater(totalMsgs, depth) + + # drain the queue + rcvr = self._start_qpid_receive("test-q", + count=totalMsgs) + count = 0; + x = rcvr.readline() # prints a line for each received msg + while x: + count += 1; + x = rcvr.readline() + + sndr1.close(); + sndr2.close(); + sndr3.close(); + rcvr.close(); + + self.assertEqual(count, totalMsgs) + self.failIf(self.qmf.getObjects(_objectId=oid)[0].flowStopped) + self.failUnless(self.qmf.getObjects(_objectId=oid)[0].flowStoppedCount) + + self._delete_queue("test-q") + + + def test_flow_size(self): + """ Create a queue with size-based flow limit. Spawn several + producers which will exceed the limit. Verify limit exceeded. Consume + all messages. Verify flow control released. + """ + self.startQmf(); + oid = self._create_queue("test-q", stop_size=351133, resume_size=251143) + + sndr1 = self._start_qpid_send("test-q", count=1699, content="X"*439, capacity=53); + sndr2 = self._start_qpid_send("test-q", count=1129, content="Y"*631, capacity=13); + sndr3 = self._start_qpid_send("test-q", count=881, content="Z"*823, capacity=149); + totalMsgs = 1699 + 1129 + 881 + + # wait until flow control is active + deadline = time() + self._timeout + while (not self.qmf.getObjects(_objectId=oid)[0].flowStopped) and \ + time() < deadline: + pass + self.failUnless(self.qmf.getObjects(_objectId=oid)[0].flowStopped) + self.assertGreater(self.qmf.getObjects(_objectId=oid)[0].byteDepth, 351133) + + # now wait until the enqueues stop happening - ensure that + # not all msgs have been sent (senders are blocked) + depth = self.qmf.getObjects(_objectId=oid)[0].msgDepth + sleep(1) + newDepth = self.qmf.getObjects(_objectId=oid)[0].msgDepth + while depth != newDepth: + depth = newDepth; + sleep(1) + newDepth = self.qmf.getObjects(_objectId=oid)[0].msgDepth + self.assertGreater(totalMsgs, depth) + + # drain the queue + rcvr = self._start_qpid_receive("test-q", + count=totalMsgs) + count = 0; + x = rcvr.readline() # prints a line for each received msg + while x: + count += 1; + x = rcvr.readline() + + sndr1.close(); + sndr2.close(); + sndr3.close(); + rcvr.close(); + + self.assertEqual(count, totalMsgs) + self.failIf(self.qmf.getObjects(_objectId=oid)[0].flowStopped) + + self._delete_queue("test-q") + + + def verify_limit(self, testq): + """ run a limit check against the testq object + """ + + testq.mgmt = self.qmf.getObjects(_objectId=testq.oid)[0] + + # fill up the queue, waiting until flow control is active + sndr1 = self._start_qpid_send(testq.mgmt.name, count=testq.sendCount, content=testq.content) + deadline = time() + self._timeout + while (not testq.mgmt.flowStopped) and time() < deadline: + testq.mgmt.update() + + self.failUnless(testq.verifyStopped()) + + # now consume enough messages to drop below the flow resume point, and + # verify flow control is released. + rcvr = self._start_qpid_receive(testq.mgmt.name, count=testq.consumeCount) + rcvr.readlines() # prints a line for each received msg + rcvr.close(); + + # we should now be below the resume threshold + self.failUnless(testq.verifyResumed()) + + self._delete_queue(testq.mgmt.name) + sndr1.close(); + + + def test_default_flow_count(self): + """ Create a queue with count-based size limit, and verify the computed + thresholds using the broker's default ratios. + """ + class TestQ: + def __init__(self, oid): + # Use the broker-wide default flow thresholds of 80%/70% (see + # run_queue_flow_limit_tests) to base the thresholds off the + # queue's max_count configuration parameter + # max_count == 1000 -> stop == 800, resume == 700 + self.oid = oid + self.sendCount = 1000 + self.consumeCount = 301 # (send - resume) + 1 to reenable flow + self.content = "X" + self.mgmt = None + def verifyStopped(self): + self.mgmt.update() + return self.mgmt.flowStopped and (self.mgmt.msgDepth > 800) + def verifyResumed(self): + self.mgmt.update() + return (not self.mgmt.flowStopped) and (self.mgmt.msgDepth < 700) + + self.startQmf(); + oid = self._create_queue("test-X", max_count=1000) + self.verify_limit(TestQ(oid)) + + + def test_default_flow_size(self): + """ Create a queue with byte-based size limit, and verify the computed + thresholds using the broker's default ratios. + """ + class TestQ: + def __init__(self, oid): + # Use the broker-wide default flow thresholds of 80%/70% (see + # run_queue_flow_limit_tests) to base the thresholds off the + # queue's max_count configuration parameter + # max_size == 10000 -> stop == 8000 bytes, resume == 7000 bytes + self.oid = oid + self.sendCount = 2000 + self.consumeCount = 601 # (send - resume) + 1 to reenable flow + self.content = "XXXXX" # 5 bytes per message sent. + self.mgmt = None + def verifyStopped(self): + self.mgmt.update() + return self.mgmt.flowStopped and (self.mgmt.byteDepth > 8000) + def verifyResumed(self): + self.mgmt.update() + return (not self.mgmt.flowStopped) and (self.mgmt.byteDepth < 7000) + + self.startQmf(); + oid = self._create_queue("test-Y", max_size=10000) + self.verify_limit(TestQ(oid)) + + + def test_blocked_queue_delete(self): + """ Verify that blocked senders are unblocked when a queue that is flow + controlled is deleted. + """ + + class BlockedSender(Thread): + def __init__(self, tester, queue, count, capacity=10): + self.tester = tester + self.queue = queue + self.count = count + self.capacity = capacity + Thread.__init__(self) + self.done = False + self.start() + def run(self): + # spawn qpid-send + p = self.tester._start_qpid_send(self.queue, + self.count, + self.capacity) + p.close() # waits for qpid-send to complete + self.done = True + + self.startQmf(); + oid = self._create_queue("kill-q", stop_size=10, resume_size=2) + q = self.qmf.getObjects(_objectId=oid)[0] + self.failIf(q.flowStopped) + + sender = BlockedSender(self, "kill-q", count=100) + # wait for flow control + deadline = time() + self._timeout + while (not q.flowStopped) and time() < deadline: + q.update() + + self.failUnless(q.flowStopped) + self.failIf(sender.done) # sender blocked + + self._delete_queue("kill-q") + sender.join(5) + self.failIf(sender.isAlive()) + self.failUnless(sender.done) + + + + diff --git a/qpid/cpp/src/tests/queue_redirect.py b/qpid/cpp/src/tests/queue_redirect.py new file mode 100644 index 0000000000..8a7b4c244b --- /dev/null +++ b/qpid/cpp/src/tests/queue_redirect.py @@ -0,0 +1,317 @@ +#!/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. +# + +import sys +import qpid +from qpid.util import connect +from qpid.connection import Connection +from qpid.datatypes import uuid4 +from qpid.testlib import TestBase010 +from qmf.console import Session +from qpid.datatypes import Message +import qpid.messaging +from time import sleep +from os import environ, popen + +class ACLFile: + def __init__(self, policy='data_dir/policy.acl'): + self.f = open(policy,'w') + + def write(self,line): + self.f.write(line) + + def close(self): + self.f.close() + +class QueueredirectTests(TestBase010): + + def get_session(self, user, passwd): + socket = connect(self.broker.host, self.broker.port) + connection = Connection (sock=socket, username=user, password=passwd, + mechanism="PLAIN") + connection.start() + return connection.session(str(uuid4())) + + def reload_acl(self): + result = None + try: + self.broker_access.reloadAclFile() + except Exception, e: + result = str(e) + return result + + def get_acl_file(self): + return ACLFile(self.config.defines.get("policy-file", "data_dir/policy.acl")) + + def setUp(self): + aclf = self.get_acl_file() + aclf.write('acl allow all all\n') + aclf.close() + TestBase010.setUp(self) + self.startBrokerAccess() + self.reload_acl() + + def tearDown(self): + aclf = self.get_acl_file() + aclf.write('acl allow all all\n') + aclf.close() + self.reload_acl() + TestBase010.tearDown(self) + + + def redirect(self, srcQueue, tgtQueue, expectPass, failMessage): + try: + result = {} + result = self.broker_access.Redirect(srcQueue, tgtQueue) + if not expectPass: + self.fail("src:" + srcQueue + ", tgt:" + tgtQueue + " - " + failMessage) + except Exception, e: + if expectPass: + self.fail("src:" + srcQueue + ", tgt:" + tgtQueue + " - " + failMessage) + + def create_queue(self, session, name, autoDelete): + try: + session.queue_declare(queue=name, auto_delete=autoDelete) + except Exception, e: + self.fail("Should allow create queue " + name) + + def _start_qpid_send(self, queue, count, content="X", capacity=100): + """ Use the qpid-send client to generate traffic to a queue. + """ + command = "qpid-send" + \ + " -b" + " %s:%s" % (self.broker.host, self.broker.port) \ + + " -a " + str(queue) \ + + " --messages " + str(count) \ + + " --content-string " + str(content) \ + + " --capacity " + str(capacity) + return popen(command) + + def _start_qpid_receive(self, queue, count, timeout=5): + """ Use the qpid-receive client to consume from a queue. + Note well: prints one line of text to stdout for each consumed msg. + """ + command = "qpid-receive" + \ + " -b " + "%s:%s" % (self.broker.host, self.broker.port) \ + + " -a " + str(queue) \ + + " --messages " + str(count) \ + + " --timeout " + str(timeout) \ + + " --print-content yes" + return popen(command) + + + + #===================================== + # QT queue tests + #===================================== + + def test_010_deny_backing_up_a_nonexistant_queue(self): + session = self.get_session('bob','bob') + self.redirect("A010", "A010", False, "Should not allow redirect to non-existent queue A010") + session.close() + + def test_020_deny_destroy_redirect(self): + session = self.get_session('bob','bob') + self.create_queue(session, "A020", False) + self.redirect("A020", "", False, "Should not allow destroying redirect") + session.close() + + def test_030_deny_redirecting_to_nonexistent_queue(self): + session = self.get_session('bob','bob') + self.create_queue(session, "A030", False) + self.redirect("A030", "Axxx", False, "Should not allow redirect with non-existent queue Axxx") + session.close() + + def test_040_deny_queue_redirecting_to_itself(self): + session = self.get_session('bob','bob') + self.create_queue(session, "A040", False) + self.redirect("A040", "A040", False, "Should not allow redirect with itself") + session.close() + + def test_050_deny_redirecting_autodelete_queue(self): + session = self.get_session('bob','bob') + self.create_queue(session, "A050", True) + self.create_queue(session, "B050", False) + self.redirect("A050", "B050", False, "Should not allow redirect with autodelete source queue") + self.redirect("B050", "A050", False, "Should not allow redirect with autodelete target queue") + session.close() + + def test_100_create_redirect_queue_pair(self): + session = self.get_session('bob','bob') + self.create_queue(session, "A100", False) + self.create_queue(session, "B100", False) + self.redirect("A100", "B100", True, "Should allow redirect") + session.close() + + def test_110_deny_adding_second_redirect_to_queue(self): + session = self.get_session('bob','bob') + self.create_queue(session, "A110", False) + self.create_queue(session, "B110", False) + self.create_queue(session, "C110", False) + self.redirect("A110", "B110", True, "Should allow redirect") + self.redirect("A110", "C110", False, "Should deny second redirect") + self.redirect("C110", "B110", False, "Should deny second redirect") + session.close() + + def test_120_verify_redirect_to_target(self): + session = self.get_session('bob','bob') + self.create_queue(session, "A120", False) + self.create_queue(session, "B120", False) + + # Send messages to original queue + sndr1 = self._start_qpid_send("A120", count=5, content="A120-before-rebind"); + sndr1.close() + + # redirect + self.redirect("A120", "B120", True, "Should allow redirect") + + # Send messages to original queue + sndr2 = self._start_qpid_send("A120", count=3, content="A120-after-rebind"); + sndr2.close() + + # drain the queue + rcvr = self._start_qpid_receive("A120", + count=5) + count = 0; + x = rcvr.readline() # prints a line for each received msg + while x: +# print "Read from A120 " + x + count += 1; + x = rcvr.readline() + + self.assertEqual(count, 5) + + # drain the queue + rcvrB = self._start_qpid_receive("B120", + count=3) + count = 0; + x = rcvrB.readline() # prints a line for each received msg + while x: +# print "Read from B120 " + x + count += 1; + x = rcvrB.readline() + + self.assertEqual(count, 3) + + ###session.close() + + def test_140_verify_redirect_to_source(self): + session = self.get_session('bob','bob') + self.create_queue(session, "A140", False) + self.create_queue(session, "B140", False) + + # Send messages to target queue - these go onto B + sndr1 = self._start_qpid_send("B140", count=5, content="B140-before-rebind"); + sndr1.close() + + # redirect + self.redirect("A140", "B140", True, "Should allow redirect") + + # Send messages to target queue - these go onto A + sndr2 = self._start_qpid_send("B140", count=3, content="B140-after-rebind"); + sndr2.close() + + # drain the queue + rcvr = self._start_qpid_receive("B140", count=5) + count = 0; + x = rcvr.readline() # prints a line for each received msg + while x: + # print "Read from B140 " + x + count += 1; + x = rcvr.readline() + + self.assertEqual(count, 5) + + # drain the queue + rcvrB = self._start_qpid_receive("A140", count=3) + count = 0; + x = rcvrB.readline() # prints a line for each received msg + while x: + # print "Read from A140 " + x + count += 1; + x = rcvrB.readline() + + self.assertEqual(count, 3) + + ###session.close() + + def test_150_queue_deletion_destroys_redirect(self): + session = self.get_session('bob','bob') + self.create_queue(session, "A150", False) + self.create_queue(session, "B150", False) + self.create_queue(session, "C150", False) + + # redirect + self.redirect("A150", "B150", True, "Should allow redirect") + + self.redirect("A150", "C150", False, "A is already redirected") + + alice = BrokerAdmin(self.config.broker, "bob", "bob") + alice.delete_queue("B150") #should pass + + self.redirect("A150", "C150", True, "Should allow redirect") + session.close() + +############################################################################################## +class BrokerAdmin: + def __init__(self, broker, username=None, password=None): + self.connection = qpid.messaging.Connection(broker) + if username: + self.connection.username = username + self.connection.password = password + self.connection.sasl_mechanisms = "PLAIN" + self.connection.open() + self.session = self.connection.session() + self.sender = self.session.sender("qmf.default.direct/broker") + self.reply_to = "responses-#; {create:always}" + self.receiver = self.session.receiver(self.reply_to) + + def invoke(self, method, arguments): + content = { + "_object_id": {"_object_name": "org.apache.qpid.broker:broker:amqp-broker"}, + "_method_name": method, + "_arguments": arguments + } + request = qpid.messaging.Message(reply_to=self.reply_to, content=content) + request.properties["x-amqp-0-10.app-id"] = "qmf2" + request.properties["qmf.opcode"] = "_method_request" + self.sender.send(request) + response = self.receiver.fetch() + self.session.acknowledge() + if response.properties['x-amqp-0-10.app-id'] == 'qmf2': + if response.properties['qmf.opcode'] == '_method_response': + return response.content['_arguments'] + elif response.properties['qmf.opcode'] == '_exception': + raise Exception(response.content['_values']) + else: raise Exception("Invalid response received, unexpected opcode: %s" % response.properties['qmf.opcode']) + else: raise Exception("Invalid response received, not a qmfv2 method: %s" % response.properties['x-amqp-0-10.app-id']) + + def create_exchange(self, name, exchange_type=None, options={}): + properties = options + if exchange_type: properties["exchange_type"] = exchange_type + self.invoke("create", {"type": "exchange", "name":name, "properties":properties}) + + def create_queue(self, name, properties={}): + self.invoke("create", {"type": "queue", "name":name, "properties":properties}) + + def delete_exchange(self, name): + self.invoke("delete", {"type": "exchange", "name":name}) + + def delete_queue(self, name): + self.invoke("delete", {"type": "queue", "name":name}) diff --git a/qpid/cpp/src/tests/quick_perftest b/qpid/cpp/src/tests/quick_perftest new file mode 100755 index 0000000000..698af60324 --- /dev/null +++ b/qpid/cpp/src/tests/quick_perftest @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +exec `dirname $0`/run_test ./qpid-perftest --summary --count 100 diff --git a/qpid/cpp/src/tests/quick_topictest b/qpid/cpp/src/tests/quick_topictest new file mode 100755 index 0000000000..e44ec0f477 --- /dev/null +++ b/qpid/cpp/src/tests/quick_topictest @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + + +# Quick and quiet topic test for make check. +test -z "$srcdir" && srcdir=`dirname $0` +$srcdir/topictest -s2 -m2 -b1 > topictest.log 2>&1 || { + echo $0 FAILED: + cat topictest.log + exit 1 +} +rm topictest.log diff --git a/qpid/cpp/src/tests/quick_topictest.ps1 b/qpid/cpp/src/tests/quick_topictest.ps1 new file mode 100644 index 0000000000..8f5b2caff7 --- /dev/null +++ b/qpid/cpp/src/tests/quick_topictest.ps1 @@ -0,0 +1,30 @@ +# +# 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. +# + +# Quick and quiet topic test for make check. +[string]$me = $myInvocation.InvocationName +$srcdir = Split-Path $me +Invoke-Expression "$srcdir\topictest.ps1 -subscribers 2 -messages 2 -batches 1" > topictest.log 2>&1 +if (!$?) { + "$me FAILED:" + cat topictest.log + exit 1 +} +Remove-Item topictest.log +exit 0 diff --git a/qpid/cpp/src/tests/quick_txtest b/qpid/cpp/src/tests/quick_txtest new file mode 100755 index 0000000000..77e8556f1d --- /dev/null +++ b/qpid/cpp/src/tests/quick_txtest @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +exec `dirname $0`/run_test ./qpid-txtest --queues 4 --tx-count 10 --quiet diff --git a/qpid/cpp/src/tests/receiver.cpp b/qpid/cpp/src/tests/receiver.cpp new file mode 100644 index 0000000000..f1b462d6e4 --- /dev/null +++ b/qpid/cpp/src/tests/receiver.cpp @@ -0,0 +1,140 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include <qpid/client/FailoverManager.h> +#include <qpid/client/Session.h> +#include <qpid/client/Message.h> +#include <qpid/client/SubscriptionManager.h> +#include <qpid/client/SubscriptionSettings.h> +#include "TestOptions.h" + +#include <iostream> +#include <fstream> + + +using namespace qpid; +using namespace qpid::client; +using namespace qpid::framing; + +using namespace std; + +namespace qpid { +namespace tests { + +struct Args : public qpid::TestOptions +{ + string queue; + uint messages; + bool ignoreDuplicates; + uint creditWindow; + uint ackFrequency; + bool browse; + + Args() : queue("test-queue"), messages(0), ignoreDuplicates(false), creditWindow(0), ackFrequency(1), browse(false) + { + addOptions() + ("queue", qpid::optValue(queue, "QUEUE NAME"), "Queue from which to request messages") + ("messages", qpid::optValue(messages, "N"), "Number of messages to receive; 0 means receive indefinitely") + ("ignore-duplicates", qpid::optValue(ignoreDuplicates), "Detect and ignore duplicates (by checking 'sn' header)") + ("credit-window", qpid::optValue(creditWindow, "N"), "Credit window (0 implies infinite window)") + ("ack-frequency", qpid::optValue(ackFrequency, "N"), "Ack frequency (0 implies none of the messages will get accepted)") + ("browse", qpid::optValue(browse), "Browse rather than consuming"); + } +}; + +const string EOS("eos"); +const string SN("sn"); + +class Receiver : public MessageListener, public FailoverManager::Command +{ + public: + Receiver(const string& queue, uint messages, bool ignoreDuplicates, uint creditWindow, uint ackFrequency, bool browse); + void received(Message& message); + void execute(AsyncSession& session, bool isRetry); + private: + const string queue; + const uint count; + const bool skipDups; + SubscriptionSettings settings; + Subscription subscription; + uint processed; + uint lastSn; + + bool isDuplicate(Message& message); +}; + +Receiver::Receiver(const string& q, uint messages, bool ignoreDuplicates, uint creditWindow, uint ackFrequency, bool browse) : + queue(q), count(messages), skipDups(ignoreDuplicates), processed(0), lastSn(0) +{ + if (browse) settings.acquireMode = ACQUIRE_MODE_NOT_ACQUIRED; + if (creditWindow) settings.flowControl = FlowControl::messageWindow(creditWindow); + settings.autoAck = ackFrequency; +} + +void Receiver::received(Message& message) +{ + if (!(skipDups && isDuplicate(message))) { + bool eos = message.getData() == EOS; + if (!eos) std::cout << message.getData() << std::endl; + if (eos || ++processed == count) subscription.cancel(); + } +} + +bool Receiver::isDuplicate(Message& message) +{ + uint sn = message.getHeaders().getAsInt(SN); + if (lastSn < sn) { + lastSn = sn; + return false; + } else { + return true; + } +} + +void Receiver::execute(AsyncSession& session, bool /*isRetry*/) +{ + SubscriptionManager subs(session); + subscription = subs.subscribe(*this, queue, settings); + subs.run(); + if (settings.autoAck) { + subscription.accept(subscription.getUnaccepted()); + } +} + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char ** argv) +{ + Args opts; + try { + opts.parse(argc, argv); + FailoverManager connection(opts.con); + Receiver receiver(opts.queue, opts.messages, opts.ignoreDuplicates, opts.creditWindow, opts.ackFrequency, opts.browse); + connection.execute(receiver); + connection.close(); + return 0; + } catch(const std::exception& error) { + std::cerr << "Failure: " << error.what() << std::endl; + } + return 1; +} diff --git a/qpid/cpp/src/tests/reject_release.py b/qpid/cpp/src/tests/reject_release.py new file mode 100644 index 0000000000..d072b8aa78 --- /dev/null +++ b/qpid/cpp/src/tests/reject_release.py @@ -0,0 +1,65 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +from qpid.tests.messaging.implementation import * +from qpid.tests.messaging import VersionTest + +class RejectReleaseTests (VersionTest): + """ + Tests for reject and release with qpidd + """ + def test_reject(self): + name = str(uuid4()) + snd = self.ssn.sender("%s; {create:always, node:{properties:{alternate-exchange:amq.fanout}}}" % name) + rcv = self.ssn.receiver(name) + rcv2 = self.ssn.receiver("amq.fanout") + + msgs = [Message(content=s, subject = s) for s in ['a','b','c','d']] + + for m in msgs: snd.send(m) + + for expected in msgs: + msg = rcv.fetch(0) + assert msg.content == expected.content + self.ssn.reject(msg) + + for expected in msgs: + msg = rcv2.fetch(0) + assert msg.content == expected.content + + def test_release(self): + snd = self.ssn.sender("#") + rcv = self.ssn.receiver(snd.target) + + msgs = [Message(content=s, subject = s) for s in ['a','b','c','d']] + + for m in msgs: snd.send(m) + + msg = rcv.fetch(0) + assert msg.content == "a" + self.ssn.release(msg) + + msg = rcv.fetch(0) + assert msg.content == "a" + self.ssn.acknowledge(msg) + + msg = rcv.fetch(0) + assert msg.content == "b" + self.ssn.release(msg) + diff --git a/qpid/cpp/src/tests/replaying_sender.cpp b/qpid/cpp/src/tests/replaying_sender.cpp new file mode 100644 index 0000000000..a5549bfdf2 --- /dev/null +++ b/qpid/cpp/src/tests/replaying_sender.cpp @@ -0,0 +1,165 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include <qpid/client/FailoverManager.h> +#include <qpid/client/Session.h> +#include <qpid/client/AsyncSession.h> +#include <qpid/client/Message.h> +#include <qpid/client/MessageReplayTracker.h> +#include <qpid/Exception.h> + +#include <iostream> +#include <sstream> + +using namespace qpid; +using namespace qpid::client; +using namespace qpid::framing; + +using namespace std; + +namespace qpid { +namespace tests { + +class Sender : public FailoverManager::Command +{ + public: + Sender(const std::string& queue, uint count, uint reportFreq); + void execute(AsyncSession& session, bool isRetry); + uint getSent(); + + void setVerbosity ( int v ) { verbosity = v; } + void setPersistence ( int p ) { persistence = p; } + + private: + MessageReplayTracker sender; + const uint count; + uint sent; + const uint reportFrequency; + Message message; + int verbosity; + int persistence; + string queueName; +}; + +Sender::Sender(const std::string& queue, uint count_, uint reportFreq ) + : sender(10), + count(count_), + sent(0), + reportFrequency(reportFreq), + verbosity(0), + persistence(0), + queueName ( queue ) +{ + message.getDeliveryProperties().setRoutingKey(queueName.c_str()); +} + +const string SN("sn"); + +void Sender::execute(AsyncSession& session, bool isRetry) +{ + if (verbosity > 0) + std::cout << "replaying_sender " << (isRetry ? "first " : "re-") << "connect." << endl; + if (isRetry) sender.replay(session); + else sender.init(session); + while (sent < count) { + stringstream message_data; + message_data << ++sent; + message.setData(message_data.str()); + message.getHeaders().setInt(SN, sent); + if ( persistence ) + message.getDeliveryProperties().setDeliveryMode(PERSISTENT); + + sender.send(message); + if (count > reportFrequency && !(sent % reportFrequency)) { + if ( verbosity > 0 ) + std::cout << "Sender sent " + << sent + << " of " + << count + << " on queue " + << queueName + << std::endl; + } + } + message.setData("That's all, folks!"); + sender.send(message); + + if ( verbosity > 0 ) + std::cout << "SENDER COMPLETED\n"; +} + +uint Sender::getSent() +{ + return sent; +} + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char ** argv) +{ + ConnectionSettings settings; + + if ( argc != 8 ) + { + std::cerr << "Usage: replaying_sender host port n_messages report_frequency verbosity persistence queue_name\n"; + return 1; + } + + settings.host = argv[1]; + settings.port = atoi(argv[2]); + int n_messages = atoi(argv[3]); + int reportFrequency = atoi(argv[4]); + int verbosity = atoi(argv[5]); + int persistence = atoi(argv[6]); + char * queue_name = argv[7]; + + FailoverManager connection(settings); + Sender sender(queue_name, n_messages, reportFrequency ); + sender.setVerbosity ( verbosity ); + sender.setPersistence ( persistence ); + try { + connection.execute ( sender ); + if ( verbosity > 0 ) + { + std::cout << "Sender finished. Sent " + << sender.getSent() + << " messages on queue " + << queue_name + << endl; + } + connection.close(); + return 0; + } + catch(const std::exception& error) + { + cerr << "Sender (host: " + << settings.host + << " port: " + << settings.port + << " ) " + << " Failed: " + << error.what() + << std::endl; + } + return 1; +} diff --git a/qpid/cpp/src/tests/resuming_receiver.cpp b/qpid/cpp/src/tests/resuming_receiver.cpp new file mode 100644 index 0000000000..2e22a7c572 --- /dev/null +++ b/qpid/cpp/src/tests/resuming_receiver.cpp @@ -0,0 +1,193 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include <qpid/client/FailoverManager.h> +#include <qpid/client/Session.h> +#include <qpid/client/Message.h> +#include <qpid/client/SubscriptionManager.h> + +#include <iostream> +#include <fstream> + +using namespace qpid; +using namespace qpid::client; +using namespace qpid::framing; + +using namespace std; + + + +namespace qpid { +namespace tests { + +class Listener : public MessageListener, + public FailoverManager::Command, + public FailoverManager::ReconnectionStrategy +{ + public: + Listener ( int report_frequency = 1000, + int verbosity = 0, + char const * queue_name = "message_queue" ); + void received(Message& message); + void execute(AsyncSession& session, bool isRetry); + void check(); + void editUrlList(vector<Url>& urls); + private: + Subscription subscription; + uint count; + vector<int> received_twice; + uint lastSn; + bool gaps; + uint reportFrequency; + int verbosity; + bool done; + string queueName; +}; + + +Listener::Listener ( int freq, int verbosity, char const * name ) + : count(0), + lastSn(0), + gaps(false), + reportFrequency(freq), + verbosity(verbosity), + done(false), + queueName ( name ) +{} + +const std::string SN("sn"); + +void Listener::received(Message & message) +{ + if (message.getData() == "That's all, folks!") + { + done = true; + if(verbosity > 0 ) + { + cout << "Shutting down listener for " + << message.getDestination() << endl; + + cout << "Listener received " + << count + << " messages (" + << received_twice.size() + << " received_twice)" + << endl; + + } + subscription.cancel(); + if ( verbosity > 0 ) + cout << "LISTENER COMPLETED\n"; + + if ( ! gaps ) { + cout << "no gaps were detected\n"; + cout << received_twice.size() << " messages were received twice.\n"; + } + else { + cout << "gaps detected\n"; + for ( unsigned int i = 0; i < received_twice.size(); ++ i ) + cout << "received_twice " + << received_twice[i] + << endl; + } + } else { + uint sn = message.getHeaders().getAsInt(SN); + if (lastSn < sn) { + if (sn - lastSn > 1) { + cerr << "Error: gap in sequence between " << lastSn << " and " << sn << endl; + gaps = true; + } + lastSn = sn; + ++count; + if ( ! ( count % reportFrequency ) ) { + if ( verbosity > 0 ) + cout << "Listener has received " + << count + << " messages on queue " + << queueName + << endl; + } + } else { + received_twice.push_back ( sn ); + } + } +} + +void Listener::check() +{ + if (gaps) throw Exception("Detected gaps in sequence; messages appear to have been lost."); +} + +void Listener::execute(AsyncSession& session, bool isRetry) { + if (verbosity > 0) + cout << "resuming_receiver " << (isRetry ? "first " : "re-") << "connect." << endl; + if (!done) { + SubscriptionManager subs(session); + subscription = subs.subscribe(*this, queueName); + subs.run(); + } +} + +void Listener::editUrlList(vector<Url>& urls) +{ + /** + * A more realistic algorithm would be to search through the list + * for prefered hosts and ensure they come first in the list. + */ + if (urls.size() > 1) rotate(urls.begin(), urls.begin() + 1, urls.end()); +} + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char ** argv) +{ + ConnectionSettings settings; + + if ( argc != 6 ) + { + cerr << "Usage: resuming_receiver host port report_frequency verbosity queue_name\n"; + return 1; + } + + settings.host = argv[1]; + settings.port = atoi(argv[2]); + int reportFrequency = atoi(argv[3]); + int verbosity = atoi(argv[4]); + char * queue_name = argv[5]; + + Listener listener ( reportFrequency, verbosity, queue_name ); + FailoverManager connection(settings, &listener); + + try { + connection.execute(listener); + connection.close(); + listener.check(); + return 0; + } catch(const exception& error) { + cerr << "Receiver failed: " << error.what() << endl; + } + return 1; +} + + + diff --git a/qpid/cpp/src/tests/ring_queue_test b/qpid/cpp/src/tests/ring_queue_test new file mode 100755 index 0000000000..410ea30eb8 --- /dev/null +++ b/qpid/cpp/src/tests/ring_queue_test @@ -0,0 +1,174 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Test script for validating the behaviour of a ring queue + +QUEUE_NAME=ring-queue +LIMIT=100 +DURABLE=0 +MESSAGES=10000 +SENDERS=1 +RECEIVERS=1 +CONCURRENT=0 +BROKER_URL="-b ${QPID_BROKER:-localhost}:${QPID_PORT:-5672}" + +setup() { + if [[ $DURABLE -gt 0 ]]; then + EXTRA_ARGS=" --durable" + fi + qpid-config $BROKER_URL add queue $QUEUE_NAME --max-queue-count $LIMIT --limit-policy ring $EXTRA_ARGS +} + +send() { + datagen --count $MESSAGES | tee sender_${QUEUE_NAME}_${1} | sender --durable $DURABLE --routing-key $QUEUE_NAME +} + +receive() { + #TODO: allow a variety of receiver options to be passed in (ack-frequency, credit-window etc) + receiver --queue $QUEUE_NAME > receiver_${QUEUE_NAME}_${1} +} + +cleanup() { + rm -f sender_${QUEUE_NAME}_* receiver_${QUEUE_NAME}_* + qpid-config $BROKER_URL del queue $QUEUE_NAME --force +} + +log() { + echo $1 +} + +fail() { + echo $1 + FAILED=1 +} + +validate() { + if [[ $RECEIVERS -eq 0 ]]; then + #queue should have $LIMIT messages on it, but need to send an eos also + sender --routing-key $QUEUE_NAME --send-eos 1 < /dev/null + received=$(receiver --queue $QUEUE_NAME --browse | wc -l) + if [[ received -eq $(( $LIMIT - 1)) ]]; then + log "queue contains $LIMIT messages as expected" + else + fail "queue does not contain the expected $LIMIT messages (received $received)" + fi + elif [[ $CONCURRENT -eq 0 ]]; then + #sum of length of all output files should be equal to $LIMIT - $RECEIVERS (1 eos message each) + received=$(cat receiver_${QUEUE_NAME}_* | wc -l) + expected=$(( $LIMIT - $RECEIVERS )) + if [[ $received -eq $expected ]]; then + log "received $LIMIT messages as expected" + else + fail "received $received messages, expected $expected" + fi + #if there were only a single sender and receiver (executed serially) we can check the + #actual received contents + if [[ $RECEIVERS -eq 1 ]] && [[ $SENDERS -eq 1 ]]; then + tail -n $(($LIMIT - 1)) sender_${QUEUE_NAME}_1 | diff - receiver_${QUEUE_NAME}_1 || FAILED=1 + if [[ $FAILED -eq 1 ]]; then + fail "did not receive expected messages" + else + log "received messages matched expectations" + fi + fi + else + #multiple receivers, concurrent with senders; ring queue functionality cannot be validated in this case + if [[ $(cat receiver_${QUEUE_NAME}_* | wc -l) -le $(cat sender_${QUEUE_NAME}_* | wc -l) ]]; then + log "sent at least as many messages as were received" + else + #Note: if any receiver was browsing, this would be valid (would need to add 'sort | uniq') + # to pipeline above + fail "received more messages than were sent" + fi + fi + + if [[ $FAILED ]]; then + echo $(basename $0): FAILED + exit 1 + else + cleanup + fi +} + +run_test() { + if [[ $CONCURRENT -eq 0 ]]; then + echo "Starting $SENDERS senders followed by $RECEIVERS receivers " + else + echo "Starting $SENDERS senders concurrently with $RECEIVERS receivers" + fi + for ((i=1; i <= $SENDERS; i++)); do + send $i & + sender_pids[$i]=$! + done + if [[ $CONCURRENT -eq 0 ]] && [[ $RECEIVERS -gt 0 ]]; then + wait + sender --routing-key $QUEUE_NAME --send-eos $RECEIVERS < /dev/null + fi + for ((i=1; i <= $RECEIVERS; i++)); do + receive $i & + done + if [[ $CONCURRENT -gt 0 ]]; then + for ((i=1; i <= $SENDERS; i++)); do + wait ${sender_pids[$i]} + done + sender --routing-key $QUEUE_NAME --send-eos $RECEIVERS < /dev/null + fi + wait +} + +usage() { + cat <<EOF +$(basename $0): Test script for validating the behaviour of a ring queue + +Options: + -q <queue> the name of the queue to use + -s <senders> the number of senders to start + -r <receivers> the number of receivers to start + -l <limit> the limit for the ring queue + -m <messages> the number of messages to send + -c if specified, receivers will run concurrently with senders + -d if specified the queue and messages will be durable +EOF + exit 1 +} + +while getopts "s:r:m:u:dch" opt ; do + case $opt in + q) QUEUE_NAME=$OPTARG ;; + l) LIMIT=$OPTARG ;; + s) SENDERS=$OPTARG ;; + r) RECEIVERS=$OPTARG ;; + m) MESSAGES=$OPTARG ;; + d) DURABLE=1 ;; + c) CONCURRENT=1 ;; + h) usage;; + ?) usage;; + esac +done + +if [[ $SENDERS -gt 0 ]]; then + setup + run_test + validate +else + echo "Nothing can be done if there are no senders" +fi + diff --git a/qpid/cpp/src/tests/rsynchosts b/qpid/cpp/src/tests/rsynchosts new file mode 100755 index 0000000000..10e1081f76 --- /dev/null +++ b/qpid/cpp/src/tests/rsynchosts @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under onemake +# 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. +# + +absdir() { echo `cd $1 && pwd`; } + +abspath() { + if test -d "$1"; then absdir "$1"; + else echo $(absdir $(dirname "$1"))/$(basename "$1") + fi +} + +usage() { + echo "Usage: $(basename $0) [-l user] file [file...] +Synchronize the contents of each file or directory to the same absolute path on +each host in \$HOSTS. +" + exit 1 +} + +while getopts "l:" opt; do + case $opt in + l) RSYNC_USER="$OPTARG@" ;; + *) usage ;; + esac +done +shift `expr $OPTIND - 1` + +test "$*" || usage + +for f in $*; do FILES="$FILES $(abspath $f)" || exit 1; done + +OK_FILE=`mktemp` # Will be deleted if anything goes wrong. +trap "rm -f $OK_FILE" EXIT + +for h in $HOSTS; do + rsync -vaRO --delete $FILES $RSYNC_USER$h:/ || { echo "rsync to $h failed"; rm -f $OK_FILE; } & +done +wait +test -f $OK_FILE + diff --git a/qpid/cpp/src/tests/run_acl_tests b/qpid/cpp/src/tests/run_acl_tests new file mode 100755 index 0000000000..4bb9e7aa5d --- /dev/null +++ b/qpid/cpp/src/tests/run_acl_tests @@ -0,0 +1,166 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Run the acl tests. $srcdir is set by the Makefile. +source ./test_env.sh +DATA_DIR=`pwd`/data_dir +DATA_DIRI=`pwd`/data_diri +DATA_DIRU=`pwd`/data_diru +DATA_DIRQ=`pwd`/data_dirq + +trap stop_brokers INT TERM QUIT + +start_brokers() { + ../qpidd --daemon --port 0 --interface 127.0.0.1 --no-module-dir --data-dir $DATA_DIR --acl-file policy.acl --auth no --log-enable trace+:acl --log-to-file local.log > qpidd.port + LOCAL_PORT=`cat qpidd.port` + ../qpidd --daemon --port 0 --interface 127.0.0.1 --no-module-dir --data-dir $DATA_DIRI --acl-file policy.acl --auth no --connection-limit-per-ip 2 --log-to-file locali.log > qpiddi.port + LOCAL_PORTI=`cat qpiddi.port` + ../qpidd --daemon --port 0 --interface 127.0.0.1 --no-module-dir --data-dir $DATA_DIRU --acl-file policy.acl --auth no --connection-limit-per-user 2 --log-to-file localu.log > qpiddu.port + LOCAL_PORTU=`cat qpiddu.port` + ../qpidd --daemon --port 0 --interface 127.0.0.1 --no-module-dir --data-dir $DATA_DIRQ --acl-file policy.acl --auth no --max-queues-per-user 2 --log-to-file localq.log > qpiddq.port + LOCAL_PORTQ=`cat qpiddq.port` +} + +start_noacl_noauth_brokers() { + ../qpidd --daemon --port 0 --no-module-dir --data-dir $DATA_DIR --auth no --log-to-file local.log > qpidd.port + LOCAL_PORT=`cat qpidd.port` + ../qpidd --daemon --port 0 --no-module-dir --data-dir $DATA_DIRI --auth no --log-to-file locali.log > qpiddi.port + LOCAL_PORTI=`cat qpiddi.port` + ../qpidd --daemon --port 0 --no-module-dir --data-dir $DATA_DIRU --auth no --log-to-file localu.log > qpiddu.port + LOCAL_PORTU=`cat qpiddu.port` + ../qpidd --daemon --port 0 --no-module-dir --data-dir $DATA_DIRQ --auth no --log-to-file localq.log > qpiddq.port + LOCAL_PORTQ=`cat qpiddq.port` +} + +start_noacl_auth_brokers() { + sasl_config_file=$builddir/sasl_config + if [ ! -f $sasl_config_file ] ; then + echo Creating sasl database + . $srcdir/sasl_test_setup.sh + fi + ../qpidd --daemon --port 0 --interface 127.0.0.1 --no-module-dir --data-dir $DATA_DIR --auth yes --sasl-config=$sasl_config_file --log-to-file local.log > qpidd.port + LOCAL_PORT=`cat qpidd.port` + ../qpidd --daemon --port 0 --interface 127.0.0.1 --no-module-dir --data-dir $DATA_DIRI --auth yes --sasl-config=$sasl_config_file --log-to-file locali.log > qpiddi.port + LOCAL_PORTI=`cat qpiddi.port` + ../qpidd --daemon --port 0 --interface 127.0.0.1 --no-module-dir --data-dir $DATA_DIRU --auth yes --sasl-config=$sasl_config_file --log-to-file localu.log > qpiddu.port + LOCAL_PORTU=`cat qpiddu.port` + ../qpidd --daemon --port 0 --interface 127.0.0.1 --no-module-dir --data-dir $DATA_DIRQ --auth yes --sasl-config=$sasl_config_file --log-to-file localq.log > qpiddq.port + LOCAL_PORTQ=`cat qpiddq.port` +} + +stop_brokers() { + $QPIDD_EXEC --no-module-dir -q --port $LOCAL_PORT + $QPIDD_EXEC --no-module-dir -q --port $LOCAL_PORTI + $QPIDD_EXEC --no-module-dir -q --port $LOCAL_PORTU + $QPIDD_EXEC --no-module-dir -q --port $LOCAL_PORTQ +} + +delete_directories() { + rm -rf $DATA_DIR + rm -rf $DATA_DIRI + rm -rf $DATA_DIRU + rm -rf $DATA_DIRQ +} + +delete_logfiles() { + rm -rf local.log + rm -rf locali.log + rm -rf localu.log + rm -rf localq.log +} + +create_directories() { + mkdir -p $DATA_DIR + mkdir -p $DATA_DIRI + mkdir -p $DATA_DIRU + mkdir -p $DATA_DIRQ +} + +populate_directories() { + cp $srcdir/policy.acl $DATA_DIR + cp $srcdir/policy.acl $DATA_DIRI + cp $srcdir/policy.acl $DATA_DIRU + cp $srcdir/policy.acl $DATA_DIRQ +} + +test_loading_acl_from_absolute_path(){ + POLICY_FILE=$srcdir/policy.acl + rm -f temp.log + PORT=`../qpidd --daemon --port 0 --interface 127.0.0.1 --no-module-dir --no-data-dir --auth no --acl-file $POLICY_FILE -t --log-to-file temp.log 2>/dev/null` + ACL_FILE=`grep "notice ACL: Read file" temp.log | sed 's/^.*Read file //'` + $QPIDD_EXEC --no-module-dir -q --port $PORT + if test "$ACL_FILE" != "\"$POLICY_FILE\""; then + echo "unable to load policy file from an absolute path"; + return 1; + fi + rm temp.log +} + +test_noacl_deny_create_link() { + delete_logfiles + start_noacl_noauth_brokers + echo "Running no-acl, no-auth tests using brokers on ports $LOCAL_PORT, $LOCAL_PORTI, $LOCAL_PORTU, and $LOCAL_PORTQ" + $QPID_CONFIG_EXEC -a localhost:$LOCAL_PORT add exchange topic fed.topic + $QPID_CONFIG_EXEC -a localhost:$LOCAL_PORTI add exchange topic fed.topic + $QPID_ROUTE_EXEC dynamic add localhost:$LOCAL_PORT localhost:$LOCAL_PORTI fed.topic 2>/dev/null + sleep 2 + stop_brokers + grep -q "must specify ACL create link rules" local.log + if [ $? -eq 0 ] + then + echo "Test fail - Broker with auth=no should have allowed link creation"; + return 1; + fi + + delete_logfiles + start_noacl_auth_brokers + echo "Running no-acl, auth tests using brokers on ports $LOCAL_PORT, $LOCAL_PORTI, $LOCAL_PORTU, and $LOCAL_PORTQ" + $QPID_CONFIG_EXEC -a localhost:$LOCAL_PORT add exchange topic fed.topic + $QPID_CONFIG_EXEC -a localhost:$LOCAL_PORTI add exchange topic fed.topic + $QPID_ROUTE_EXEC dynamic add localhost:$LOCAL_PORT localhost:$LOCAL_PORTI fed.topic 2>/dev/null + sleep 2 + stop_brokers + grep -q "must specify ACL create link rules" local.log + if [ $? -ne 0 ] + then + echo "Test fail - Broker with no ACL and --auth=yes file did not deny link creation"; + return 1; + fi +} + +if test -d ${PYTHON_DIR} ; then + # run acl.py test file + delete_directories + create_directories + populate_directories + delete_logfiles + start_brokers + echo "Running acl tests using brokers on ports $LOCAL_PORT, $LOCAL_PORTI, $LOCAL_PORTU, and $LOCAL_PORTQ" + $QPID_PYTHON_TEST -b localhost:$LOCAL_PORT -m acl -Dport-i=$LOCAL_PORTI -Dport-u=$LOCAL_PORTU -Dport-q=$LOCAL_PORTQ || EXITCODE=1 + stop_brokers || EXITCODE=1 + # + test_loading_acl_from_absolute_path || EXITCODE=1 + # + test_noacl_deny_create_link || EXITCODE=1 + delete_directories + exit $EXITCODE +fi + diff --git a/qpid/cpp/src/tests/run_acl_tests.ps1 b/qpid/cpp/src/tests/run_acl_tests.ps1 new file mode 100644 index 0000000000..8279d87e54 --- /dev/null +++ b/qpid/cpp/src/tests/run_acl_tests.ps1 @@ -0,0 +1,99 @@ +# +# 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. +# + +# Run the acl tests. + +$srcdir = Split-Path $myInvocation.InvocationName +. .\test_env.ps1 +if (!(Test-Path $PYTHON_DIR -pathType Container)) { + "Skipping acl tests as python libs not found" + exit 1 +} + +$Global:BROKER_EXE = "" + +Function start_broker($acl_options) +{ + # Test runs from the tests directory but the broker executable is one level + # up, and most likely in a subdirectory from there based on what build type. + # Look around for it before trying to start it. + . $srcdir\find_prog.ps1 ..\qpidd.exe + if (!(Test-Path $prog)) { + "Cannot locate qpidd.exe" + exit 1 + } + $Global:BROKER_EXE = $prog + if (Test-Path qpidd.port) { + Remove-Item qpidd.port + } + $cmdline = "$prog --auth=no --no-module-dir --port=0 --log-to-file qpidd.log $acl_options | foreach { set-content qpidd.port `$_ }" + $cmdblock = $executioncontext.invokecommand.NewScriptBlock($cmdline) + . $srcdir\background.ps1 $cmdblock + # Wait for the broker to start + $wait_time = 0 + while (!(Test-Path qpidd.port) -and ($wait_time -lt 30)) { + Start-Sleep 2 + $wait_time += 2 + } + if (!(Test-Path qpidd.port)) { + "Timeout waiting for broker to start" + exit 1 + } + set-item -path env:BROKER_PORT -value (get-content -path qpidd.port -totalcount 1) +} + +Function stop_broker +{ + "Stopping $Global:BROKER_EXE" + Invoke-Expression "$Global:BROKER_EXE --no-module-dir -q --port $env:BROKER_PORT" | Write-Output + Remove-Item qpidd.port +} + +$DATA_DIR = [IO.Directory]::GetCurrentDirectory() + "\data_dir" +Remove-Item $DATA_DIR -recurse +New-Item $DATA_DIR -type directory +Copy-Item $srcdir\policy.acl $DATA_DIR +start_broker("--data-dir $DATA_DIR --acl-file policy.acl") +"Running acl tests using broker on port $env:BROKER_PORT" +Invoke-Expression "python $PYTHON_DIR/qpid-python-test -m acl -b localhost:$env:BROKER_PORT" | Out-Default +$RETCODE=$LASTEXITCODE +stop_broker + +# Now try reading the acl file from an absolute path. +Remove-Item qpidd.log +$policy_full_path = "$srcdir\policy.acl" +start_broker("--no-data-dir --acl-file $policy_full_path") +#test_loading_acl_from_absolute_path(){ +# POLICY_FILE=$srcdir/policy.acl +# rm -f temp.log +# PORT=`../qpidd --daemon --port 0 --no-module-dir --no-data-dir --auth no --load-module $ACL_LIB --acl-file $POLICY_FILE -t --log-to-file temp.log 2>/dev/null` +# ACL_FILE=`grep "notice Read ACL file" temp.log | sed 's/^.*Read ACL file //'` +# $QPIDD_EXEC --no-module-dir -q --port $PORT +# if test "$ACL_FILE" != "\"$POLICY_FILE\""; then +# echo "unable to load policy file from an absolute path"; +# return 1; +# fi +# rm temp.log +#} +# +# test_loading_acl_from_absolute_path || EXITCODE=1 +# rm -rf $DATA_DIR +# exit $EXITCODE +stop_broker +exit $RETCODE diff --git a/qpid/cpp/src/tests/run_cli_tests b/qpid/cpp/src/tests/run_cli_tests new file mode 100755 index 0000000000..1db99001a4 --- /dev/null +++ b/qpid/cpp/src/tests/run_cli_tests @@ -0,0 +1,81 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Run the cli-utility tests. + +source ./test_env.sh +CLI_DIR=$PYTHON_COMMANDS + +trap stop_brokers INT TERM QUIT + +# helper function to create test.xquery in the current directory, so +# that the python test program can find it. yes, it leaves a turd. +create_test_xquery() { + cat <<EOF > ./test.xquery + let \$w := ./weather + return \$w/station = 'Raleigh-Durham International Airport (KRDU)' + and \$w/temperature_f > 50 + and \$w/temperature_f - \$w/dewpoint > 5 + and \$w/wind_speed_mph > 7 + and \$w/wind_speed_mph < 20 +EOF +} + +start_brokers() { + # if the xml lib is present, use it. if not, disable any tests which + # look like they're xml related. + # if we start supporting xml on windows, it will need something similar + # here + if [ -f ../xml.so ] ; then + xargs="--load-module ../xml.so" + if [ ! -f test.xquery ] ; then + create_test_xquery + fi + targs="" + else + echo "Ignoring XML tests" + xargs="" + targs="--ignore=*xml*" + fi + + ../qpidd --daemon --port 0 --interface 127.0.0.1 --no-data-dir --no-module-dir --mgmt-publish no --auth no $xargs > qpidd.port + LOCAL_PORT=`cat qpidd.port` + ../qpidd --daemon --port 0 --interface 127.0.0.1 --no-data-dir --no-module-dir --mgmt-publish no --auth no $xargs > qpidd.port + REMOTE_PORT=`cat qpidd.port` +} + +stop_brokers() { + $QPIDD_EXEC --no-module-dir -q --port $LOCAL_PORT + $QPIDD_EXEC --no-module-dir -q --port $REMOTE_PORT +} + +if test -d ${PYTHON_DIR} ; then + start_brokers + echo "Running CLI tests using brokers on ports $LOCAL_PORT $REMOTE_PORT" + PYTHON_TESTS=${PYTHON_TESTS:-$*} + $QPID_PYTHON_TEST -m cli_tests -b localhost:$LOCAL_PORT -Dremote-port=$REMOTE_PORT -Dcli-dir=$CLI_DIR $targs $PYTHON_TESTS $@ + RETCODE=$? + stop_brokers + if test x$RETCODE != x0; then + echo "FAIL CLI tests"; exit 1; + fi +fi + diff --git a/qpid/cpp/src/tests/run_federation_sys_tests b/qpid/cpp/src/tests/run_federation_sys_tests new file mode 100755 index 0000000000..f5f1ae44d3 --- /dev/null +++ b/qpid/cpp/src/tests/run_federation_sys_tests @@ -0,0 +1,71 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Run the federation system tests. + +source ./test_env.sh + +MODULENAME=federation_sys + +# Test for long test +if [[ "$1" == "LONG_TEST" ]]; then + USE_LONG_TEST=1 + shift # get rid of this param so it is not treated as a test name +fi + +trap stop_brokers INT TERM QUIT + +SKIPTESTS="-i federation_sys.E_* -i federation_sys.F_* -i federation_sys.G_* -i federation_sys.H_*" +if [ -z ${USE_LONG_TEST} ]; then + SKIPTESTS="-i federation_sys.A_Long* -i federation_sys.B_Long* ${SKIPTESTS}" +fi +echo "WARNING: Tests using persistence will be ignored." +SKIPTESTS="${SKIPTESTS} -i federation_sys.C_* -i federation_sys.D_*" + +start_brokers() { + start_broker() { + ${QPIDD_EXEC} --daemon --port 0 --interface 127.0.0.1 --auth no --no-data-dir $1 > qpidd.port + PORT=`cat qpidd.port` + eval "$2=${PORT}" + } + start_broker "" LOCAL_PORT + start_broker "" REMOTE_PORT + rm qpidd.port +} + +stop_brokers() { + ${QPIDD_EXEC} -q --port ${LOCAL_PORT} + ${QPIDD_EXEC} -q --port ${REMOTE_PORT} +} + +if test -d ${PYTHON_DIR} ; then + start_brokers + echo "Running federation tests using brokers on local port ${LOCAL_PORT}, remote port ${REMOTE_PORT} (NOTE: clustering is DISABLED)" + if [ -z ${USE_LONG_TEST} ]; then + echo "NOTE: To run a full set of federation system tests, use \"make check-long\". To test with persistence, run the store version of this script." + fi + ${QPID_PYTHON_TEST} -m ${MODULENAME} ${SKIPTESTS} -b localhost:${REMOTE_PORT} -Dlocal-port=${LOCAL_PORT} -Dremote-port=${REMOTE_PORT} $@ + RETCODE=$? + stop_brokers + if test x${RETCODE} != x0; then + echo "FAIL federation tests"; exit 1; + fi +fi diff --git a/qpid/cpp/src/tests/run_federation_tests b/qpid/cpp/src/tests/run_federation_tests new file mode 100755 index 0000000000..8cadd3702f --- /dev/null +++ b/qpid/cpp/src/tests/run_federation_tests @@ -0,0 +1,61 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Run the federation tests. + +source ./test_env.sh +#set -x +trap stop_brokers INT TERM QUIT + +if [ -f ../xml.so ] ; then + MODULES="--load-module xml" # Load the XML exchange and run XML exchange federation tests + SKIPTESTS= +else + MODULES="--no-module-dir" + SKIPTESTS='-i *_xml' # note: single quotes prevent expansion of * +fi + +QPIDD_CMD="../qpidd --daemon --port 0 --interface 127.0.0.1 --no-data-dir $MODULES --auth no --log-enable=info+ --log-enable=debug+:Bridge --log-to-file" +start_brokers() { + rm -f fed_local.log fed_remote.log fed_b1.log fed_b2.log + LOCAL_PORT=$($QPIDD_CMD fed_local.log --federation-tag LOCAL) + REMOTE_PORT=$($QPIDD_CMD fed_remote.log --federation-tag REMOTE) + REMOTE_B1=$($QPIDD_CMD fed_b1.log --federation-tag B1) + REMOTE_B2=$($QPIDD_CMD fed_b2.log --federation-tag B2) +} + +stop_brokers() { + $QPIDD_EXEC $MODULES -q --port $LOCAL_PORT + $QPIDD_EXEC $MODULES -q --port $REMOTE_PORT + $QPIDD_EXEC $MODULES -q --port $REMOTE_B1 + $QPIDD_EXEC $MODULES -q --port $REMOTE_B2 +} + +if test -d ${PYTHON_DIR} ; then + start_brokers + echo "Running federation tests using brokers on ports $LOCAL_PORT $REMOTE_PORT $REMOTE_B1 $REMOTE_B2" + $QPID_PYTHON_TEST -m federation ${SKIPTESTS} -b localhost:$LOCAL_PORT -Dremote-port=$REMOTE_PORT -Dextra-brokers="$REMOTE_B1 $REMOTE_B2" $@ + RETCODE=$? + stop_brokers + if test x$RETCODE != x0; then + echo "FAIL federation tests"; exit 1; + fi +fi diff --git a/qpid/cpp/src/tests/run_federation_tests.ps1 b/qpid/cpp/src/tests/run_federation_tests.ps1 new file mode 100644 index 0000000000..803b3eef6f --- /dev/null +++ b/qpid/cpp/src/tests/run_federation_tests.ps1 @@ -0,0 +1,83 @@ +# +# 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. +# + +# Run the federation tests. + +$srcdir = Split-Path $myInvocation.InvocationName +$PYTHON_DIR = "$srcdir\..\..\..\python" +if (!(Test-Path $PYTHON_DIR -pathType Container)) { + "Skipping federation tests as python libs not found" + exit 1 +} + +. .\test_env.ps1 + +# Test runs from the tests directory but the broker executable is one level +# up, and most likely in a subdirectory from there based on what build type. +# Look around for it before trying to start it. +$subs = "Debug","Release","MinSizeRel","RelWithDebInfo" +foreach ($sub in $subs) { + $prog = "..\$sub\qpidd.exe" + if (Test-Path $prog) { + break + } +} +if (!(Test-Path $prog)) { + "Cannot locate qpidd.exe" + exit 1 +} +$cmdline = "$prog --auth=no --no-module-dir --no-data-dir --port=0 --ssl-port=0 --log-to-file qpidd.log $args | foreach { set-content qpidd.port `$_ }" +$cmdblock = $executioncontext.invokecommand.NewScriptBlock($cmdline) + +function start_brokers { + # Start 2 brokers, saving the port numbers in LOCAL_PORT, REMOTE_PORT. + . $srcdir\background.ps1 $cmdblock + while (!(Test-Path qpidd.port)) { + Start-Sleep 2 + } + set-item -path env:LOCAL_PORT -value (get-content -path qpidd.port -totalcount 1) + Remove-Item qpidd.port + . $srcdir\background.ps1 $cmdblock + while (!(Test-Path qpidd.port)) { + Start-Sleep 2 + } + set-item -path env:REMOTE_PORT -value (get-content -path qpidd.port -totalcount 1) +} + +function stop_brokers { + Invoke-Expression "$prog -q --port $env:LOCAL_PORT" | Out-Default + Invoke-Expression "$prog -q --port $env:REMOTE_PORT" | Out-Default +} + +trap { + &stop_brokers + break +} + +&start_brokers +"Running federation tests using brokers on ports $env:LOCAL_PORT $env:REMOTE_PORT" +$env:PYTHONPATH="$srcdir;$PYTHON_DIR;$PYTHON_TEST_DIR;$env:PYTHONPATH;$QMF_LIB" +$tests = "*" +Invoke-Expression "python $PYTHON_DIR/qpid-python-test -m federation -b localhost:$env:LOCAL_PORT -Dremote-port=$env:REMOTE_PORT $tests" | Out-Default +$RETCODE=$LASTEXITCODE +&stop_brokers +if ($RETCODE -ne 0) { + "FAIL federation tests" + exit 1 +} diff --git a/qpid/cpp/src/tests/run_ha_tests b/qpid/cpp/src/tests/run_ha_tests new file mode 100755 index 0000000000..bb60bea076 --- /dev/null +++ b/qpid/cpp/src/tests/run_ha_tests @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + + +# Make sure the python tools are available. They will be if we are building in +# a checkoug, they may not be in a distribution. +test -d $PYTHON_COMMANDS -a -x $PYTHON_COMMANDS/qpid-ha -a -x $PYTHON_COMMANDS/qpid-config || { echo "Skipping HA tests, qpid-ha or qpid-config not available."; exit 0; } + +srcdir=`dirname $0` +$srcdir/ha_tests.py + diff --git a/qpid/cpp/src/tests/run_header_test b/qpid/cpp/src/tests/run_header_test new file mode 100755 index 0000000000..d1edcf6831 --- /dev/null +++ b/qpid/cpp/src/tests/run_header_test @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Simple test of encode/decode of a double in application headers +# TODO: this should be expanded to cover a wider set of types and go +# in both directions + +source $QPID_TEST_COMMON + +ensure_python_tests + +./header_test -p $QPID_PORT +$srcdir/header_test.py "localhost" $QPID_PORT diff --git a/qpid/cpp/src/tests/run_header_test.ps1 b/qpid/cpp/src/tests/run_header_test.ps1 new file mode 100644 index 0000000000..344fac9cf9 --- /dev/null +++ b/qpid/cpp/src/tests/run_header_test.ps1 @@ -0,0 +1,48 @@ +# +# 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. +# + +# Simple test of encode/decode of a double in application headers +# TODO: this should be expanded to cover a wider set of types and go +# in both directions + +$srcdir = Split-Path $myInvocation.InvocationName +$PYTHON_DIR = "$srcdir\..\..\..\python" +if (!(Test-Path $PYTHON_DIR -pathType Container)) { + "Skipping header test as python libs not found" + exit 0 +} + +. .\test_env.ps1 + +if (Test-Path qpidd.port) { + set-item -path env:QPID_PORT -value (get-content -path qpidd.port -totalcount 1) +} + +# Test runs from the tests directory but the test executables are in a +# subdirectory based on the build type. Look around for it before trying +# to start it. +. $srcdir\find_prog.ps1 .\header_test.exe +if (!(Test-Path $prog)) { + "Cannot locate header_test.exe" + exit 1 +} + +Invoke-Expression "$prog -p $env:QPID_PORT" | Write-Output +Invoke-Expression "python $srcdir/header_test.py localhost $env:QPID_PORT" | Write-Output +exit $LASTEXITCODE diff --git a/qpid/cpp/src/tests/run_headers_federation_tests b/qpid/cpp/src/tests/run_headers_federation_tests new file mode 100644 index 0000000000..afbbf144ee --- /dev/null +++ b/qpid/cpp/src/tests/run_headers_federation_tests @@ -0,0 +1,49 @@ +#!/bin/sh + +# +# 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. +# + +# Run the federation tests for the Headers Exchange. + +source ./test_env.sh + +trap stop_brokers INT TERM QUIT + +start_brokers() { + ../qpidd --daemon --port 0 --interface 127.0.0.1 --no-data-dir --no-module-dir --auth no > qpidd.port + LOCAL_PORT=`cat qpidd.port` + ../qpidd --daemon --port 0 --interface 127.0.0.1 --no-data-dir --no-module-dir --auth no > qpidd.port + REMOTE_PORT=`cat qpidd.port` +} + +stop_brokers() { + $QPIDD_EXEC --no-module-dir -q --port $LOCAL_PORT + $QPIDD_EXEC --no-module-dir -q --port $REMOTE_PORT +} + +if test -d ${PYTHON_DIR} ; then + start_brokers + echo "Running HeadersExchange federation tests using brokers on ports $LOCAL_PORT $REMOTE_PORT" + $QPID_PYTHON_TEST -m headers_federation -b localhost:$LOCAL_PORT -Dremote-port=$REMOTE_PORT $@ + RETCODE=$? + stop_brokers + if test x$RETCODE != x0; then + echo "FAIL federation tests"; exit 1; + fi +fi diff --git a/qpid/cpp/src/tests/run_interlink_tests b/qpid/cpp/src/tests/run_interlink_tests new file mode 100755 index 0000000000..71482fa7fd --- /dev/null +++ b/qpid/cpp/src/tests/run_interlink_tests @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +test -e "$AMQP_LIB" || { echo "Skipping AMQP 1.0 based tests; AMQP 1.0 support not available."; exit 0; } + +srcdir=`dirname $0` +$srcdir/interlink_tests.py + diff --git a/qpid/cpp/src/tests/run_long_federation_sys_tests b/qpid/cpp/src/tests/run_long_federation_sys_tests new file mode 100644 index 0000000000..c2b4e02d81 --- /dev/null +++ b/qpid/cpp/src/tests/run_long_federation_sys_tests @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Run the federation system tests (long version). + +./run_federation_sys_tests LONG_TEST $@ diff --git a/qpid/cpp/src/tests/run_msg_group_tests b/qpid/cpp/src/tests/run_msg_group_tests new file mode 100755 index 0000000000..ee479c23c7 --- /dev/null +++ b/qpid/cpp/src/tests/run_msg_group_tests @@ -0,0 +1,62 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +#script to run a sequence of message group queue tests via make + +source $QPID_TEST_COMMON + +ensure_python_tests + +QUEUE_NAME="group-queue" +GROUP_KEY="My-Group-Id" + +BROKER_URL="${QPID_BROKER:-localhost}:${QPID_PORT:-5672}" + +run_test() { + "$@" +} + +##set -x + +declare -i i=0 +declare -a tests +tests=("qpid-config -b $BROKER_URL add queue $QUEUE_NAME --group-header=${GROUP_KEY} --shared-groups" + "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 103 --group-size 13 --receivers 2 --senders 3 --capacity 3 --ack-frequency 7 --randomize-group-size --interleave 3" + "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 103 --group-size 13 --receivers 2 --senders 3 --capacity 7 --ack-frequency 7 --randomize-group-size" + "qpid-config -b $BROKER_URL add queue ${QUEUE_NAME}-two --group-header=${GROUP_KEY} --shared-groups" + "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 103 --group-size 13 --receivers 2 --senders 3 --capacity 7 --ack-frequency 3 --randomize-group-size" + "msg_group_test -b $BROKER_URL -a ${QUEUE_NAME}-two --group-key $GROUP_KEY --messages 103 --group-size 13 --receivers 2 --senders 3 --capacity 3 --ack-frequency 7 --randomize-group-size --interleave 5" + "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 59 --group-size 5 --receivers 2 --senders 3 --capacity 1 --ack-frequency 3 --randomize-group-size" + "qpid-config -b $BROKER_URL del queue ${QUEUE_NAME}-two --force" + "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 59 --group-size 3 --receivers 2 --senders 3 --capacity 1 --ack-frequency 1 --randomize-group-size" + "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 211 --group-size 13 --receivers 2 --senders 3 --capacity 47 --ack-frequency 79 --interleave 53" + "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 10000 --group-size 1 --receivers 0 --senders 1" + "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 10000 --receivers 5 --senders 0" + "qpid-config -b $BROKER_URL del queue $QUEUE_NAME --force") + +while [ -n "${tests[i]}" ]; do + run_test ${tests[i]} + RETCODE=$? + if test x$RETCODE != x0; then + echo "FAILED message group test. Failed command: \"${tests[i]}\""; + exit 1; + fi + i+=1 +done diff --git a/qpid/cpp/src/tests/run_msg_group_tests.ps1 b/qpid/cpp/src/tests/run_msg_group_tests.ps1 new file mode 100644 index 0000000000..e9cee0a5a0 --- /dev/null +++ b/qpid/cpp/src/tests/run_msg_group_tests.ps1 @@ -0,0 +1,71 @@ +# +# 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. +# + +# Simple test of encode/decode of a double in application headers +# TODO: this should be expanded to cover a wider set of types and go +# in both directions + +$srcdir = Split-Path $myInvocation.InvocationName +$PYTHON_DIR = "$srcdir\..\..\..\python" +if (!(Test-Path $PYTHON_DIR -pathType Container)) { + "Skipping msg_group test as python libs not found" + exit 0 +} + +. .\test_env.ps1 + +if (Test-Path qpidd.port) { + set-item -path env:QPID_PORT -value (get-content -path qpidd.port -totalcount 1) +} + +# Test runs from the tests directory but the test executables are in a +# subdirectory based on the build type. Look around for it before trying +# to start it. +. $srcdir\find_prog.ps1 .\msg_group_test.exe +if (!(Test-Path $prog)) { + "Cannot locate msg_group_test.exe" + exit 1 +} + +$QUEUE_NAME="group-queue" +$GROUP_KEY="My-Group-Id" +$BROKER_URL="localhost:$env:QPID_PORT" + +$tests=@("python $QPID_CONFIG_EXEC -b $BROKER_URL add queue $QUEUE_NAME --group-header=${GROUP_KEY} --shared-groups", + "$prog -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 103 --group-size 13 --receivers 2 --senders 3 --capacity 3 --ack-frequency 7 --randomize-group-size --interleave 3", + "$prog -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 103 --group-size 13 --receivers 2 --senders 3 --capacity 7 --ack-frequency 7 --randomize-group-size", + "python $QPID_CONFIG_EXEC -b $BROKER_URL add queue ${QUEUE_NAME}-two --group-header=${GROUP_KEY} --shared-groups", + "$prog -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 103 --group-size 13 --receivers 2 --senders 3 --capacity 7 --ack-frequency 3 --randomize-group-size", + "$prog -b $BROKER_URL -a ${QUEUE_NAME}-two --group-key $GROUP_KEY --messages 103 --group-size 13 --receivers 2 --senders 3 --capacity 3 --ack-frequency 7 --randomize-group-size --interleave 5", + "$prog -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 59 --group-size 5 --receivers 2 --senders 3 --capacity 1 --ack-frequency 3 --randomize-group-size", + "python $QPID_CONFIG_EXEC -b $BROKER_URL del queue ${QUEUE_NAME}-two --force", + "$prog -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 59 --group-size 3 --receivers 2 --senders 3 --capacity 1 --ack-frequency 1 --randomize-group-size", + "$prog -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 211 --group-size 13 --receivers 2 --senders 3 --capacity 47 --ack-frequency 79 --interleave 53", + "$prog -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 10000 --group-size 1 --receivers 0 --senders 1", + "$prog -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 10000 --receivers 5 --senders 0", + "python $QPID_CONFIG_EXEC -b $BROKER_URL del queue $QUEUE_NAME --force") + +foreach ($cmd in $tests) +{ + Invoke-Expression "$cmd" | Write-Output + $ret = $LASTEXITCODE + if ($ret -ne 0) {Write-Host "FAILED message group test. Failed command: $cmd" + break} +} +exit $ret diff --git a/qpid/cpp/src/tests/run_msg_group_tests_soak b/qpid/cpp/src/tests/run_msg_group_tests_soak new file mode 100755 index 0000000000..d87ca16c88 --- /dev/null +++ b/qpid/cpp/src/tests/run_msg_group_tests_soak @@ -0,0 +1,63 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +#script to run a sequence of long-running message group tests via make + +#setup path to find qpid-config and msg_group_test test progs +source ./test_env.sh +test -d $PYTHON_DIR || { echo "Skipping message group tests, no python dir."; exit 0; } + +export PATH=$PWD:$srcdir:$PYTHON_COMMANDS:$PATH + +#set port to connect to via env var +test -s qpidd.port && QPID_PORT=`cat qpidd.port` + +#trap cleanup INT TERM QUIT + +QUEUE_NAME="group-queue" +GROUP_KEY="My-Group-Id" + +BROKER_URL="${QPID_BROKER:-localhost}:${QPID_PORT:-5672}" + +run_test() { + $@ +} + +##set -x + +declare -i i=0 +declare -a tests +tests=("qpid-config -b $BROKER_URL add queue $QUEUE_NAME --group-header=${GROUP_KEY} --shared-groups" + "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 10007 --receivers 3 --senders 5 --group-size 211 --randomize-group-size --capacity 47 --ack-frequency 97" + "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 10007 --receivers 3 --senders 5 --group-size 211 --randomize-group-size --capacity 79 --ack-frequency 79" + "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 10007 --receivers 3 --senders 5 --group-size 211 --randomize-group-size --capacity 97 --ack-frequency 47" + "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 40000 --receivers 0 --senders 5 --group-size 13 --randomize-group-size" + "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 200000 --receivers 3 --senders 0 --capacity 23 --ack-frequency 7" + "qpid-config -b $BROKER_URL del queue $QUEUE_NAME --force") + +while [ -n "${tests[i]}" ]; do + run_test ${tests[i]} + RETCODE=$? + if test x$RETCODE != x0; then + echo "FAILED message group test. Failed command: \"${tests[i]}\""; + exit 1; + fi + i+=1 +done diff --git a/qpid/cpp/src/tests/run_paged_queue_tests b/qpid/cpp/src/tests/run_paged_queue_tests new file mode 100755 index 0000000000..2c1e3ae614 --- /dev/null +++ b/qpid/cpp/src/tests/run_paged_queue_tests @@ -0,0 +1,50 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +#setup path to find qpid-config and sender/receiver test progs +source ./test_env.sh +trap stop_broker INT TERM QUIT + +export PATH=$PWD:$srcdir:$PYTHON_COMMANDS:$PATH + +start_broker() { + QPID_PORT=$($QPIDD_EXEC --daemon --port 0 --interface 127.0.0.1 --no-data-dir --paging-dir=$PWD/pqtest_data $MODULES --auth no) || { echo "Could not start broker"; exit 1; } +} + +stop_broker() { + $QPIDD_EXEC -q --port $QPID_PORT +} + +test_single_page() { + msgcount=1000 + qpid-send --messages $msgcount --content-size 1024 --broker "localhost:$QPID_PORT" --address "onepage; {create: always, node:{x-declare:{arguments:{'qpid.paging':True,'qpid.max_pages_loaded':1}}}}" + received=$(qpid-receive --address onepage --broker "localhost:$QPID_PORT" --messages $msgcount | wc -l) + if [[ $received -ne $msgcount ]]; then + echo "single page test failed: received $received messages, expected $msgcount" + exit 1 + fi +} + +start_broker +test_single_page +qpid-cpp-benchmark --broker "localhost:$QPID_PORT" --create-option "node:{x-declare:{arguments:{'qpid.paging':True,'qpid.max_size':0,'qpid.max_count':0,'qpid.flow_stop_size':0,'qpid.flow_resume_size':0,'qpid.flow_stop_count':0,'qpid.flow_resume_count':0}}}" +qpid-cpp-benchmark --broker "localhost:$QPID_PORT" --create-option "node:{x-declare:{arguments:{'qpid.paging':True,'qpid.max_size':0,'qpid.max_count':0,'qpid.flow_stop_size':0,'qpid.flow_resume_size':0,'qpid.flow_stop_count':0,'qpid.flow_resume_count':0}}}" --fill-drain +stop_broker diff --git a/qpid/cpp/src/tests/run_perftest b/qpid/cpp/src/tests/run_perftest new file mode 100755 index 0000000000..2fadc6cc62 --- /dev/null +++ b/qpid/cpp/src/tests/run_perftest @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Args: count [qpid-perftest options...] +# Run a qpid-perftest with count multiplied. +# +MULTIPLIER=3 +COUNT=`expr $1 \* $MULTIPLIER` +shift +exec `dirname $0`/run_test ./qpid-perftest --summary --count $COUNT "$@" diff --git a/qpid/cpp/src/tests/run_queue_flow_limit_tests b/qpid/cpp/src/tests/run_queue_flow_limit_tests new file mode 100755 index 0000000000..55b3e5d4c5 --- /dev/null +++ b/qpid/cpp/src/tests/run_queue_flow_limit_tests @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +source $QPID_TEST_COMMON + +ensure_python_tests + +# Run tests against Queue producer flow control. +$QPID_PYTHON_TEST -m queue_flow_limit_tests $SKIPTESTS -b localhost:$QPID_PORT diff --git a/qpid/cpp/src/tests/run_queue_redirect b/qpid/cpp/src/tests/run_queue_redirect new file mode 100755 index 0000000000..3a0ae5118a --- /dev/null +++ b/qpid/cpp/src/tests/run_queue_redirect @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Run the queue redirect. $srcdir is set by the Makefile. +source ./test_env.sh +DATA_DIR=`pwd`/data_dir + +trap stop_brokers INT TERM QUIT + +start_brokers() { + $QPIDD_EXEC --daemon \ + --port 0 --interface 127.0.0.1 \ + --no-module-dir \ + --data-dir $DATA_DIR \ + --acl-file policy.acl \ + --auth no \ + --log-to-file queue_redirect.log \ + --log-enable info+ \ + --log-enable trace+:Model \ + --log-enable trace+ > qpidd.port + LOCAL_PORT=`cat qpidd.port` +} + +stop_brokers() { + $QPIDD_EXEC --no-module-dir -q --port $LOCAL_PORT +} + +if test -d ${PYTHON_DIR} ; then + rm -f queue_redirect.log + rm -rf $DATA_DIR + mkdir -p $DATA_DIR + cp $srcdir/policy.acl $DATA_DIR + start_brokers + echo "Running queue redirect tests using broker on port $LOCAL_PORT" + $QPID_PYTHON_TEST -b localhost:$LOCAL_PORT -m queue_redirect + stop_brokers || EXITCODE=1 + exit $EXITCODE +fi diff --git a/qpid/cpp/src/tests/run_ring_queue_test b/qpid/cpp/src/tests/run_ring_queue_test new file mode 100755 index 0000000000..69497f9872 --- /dev/null +++ b/qpid/cpp/src/tests/run_ring_queue_test @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +#script to run a sequence of ring queue tests via make + +#setup path to find qpid-config and sender/receiver test progs +source ./test_env.sh + +export PATH=$PWD:$srcdir:$PYTHON_COMMANDS:$PATH + +#set port to connect to via env var +test -s qpidd.port && QPID_PORT=`cat qpidd.port` +export QPID_PORT + +ring_queue_test -c -s 4 -r 4 +ring_queue_test -s 4 -r 0 +ring_queue_test -s 1 -r 1 + + diff --git a/qpid/cpp/src/tests/run_store_tests.ps1 b/qpid/cpp/src/tests/run_store_tests.ps1 new file mode 100644 index 0000000000..0683892393 --- /dev/null +++ b/qpid/cpp/src/tests/run_store_tests.ps1 @@ -0,0 +1,132 @@ +# +# 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. +# + +# Run the store tests. +# There are two sets of tests: +# 1. A subset of the normal broker python tests, dtx and persistence, but +# run again with the desired store loaded. +# 2. store.py, which tests recovering things across broker restarts. + +$test_store = $args[0] +if ($test_store -ne "MSSQL" -and $test_store -ne "MSSQL-CLFS") { + "Invalid store test type $test_store - must be MSSQL or MSSQL-CLFS" + exit 1 +} + +$srcdir = Split-Path $myInvocation.InvocationName + +. .\test_env.ps1 + +if (!(Test-Path $PYTHON_DIR -pathType Container)) { + "Skipping store tests as python libs not found" + exit 1 +} + +# Test runs from the tests directory but the broker executable is one level +# up, and most likely in a subdirectory from there based on what build type. +# Look around for it before trying to start it. +$subs = "Debug","Release","MinSizeRel","RelWithDebInfo" +foreach ($sub in $subs) { + $prog = "..\$sub\qpidd.exe" + if (Test-Path $prog) { + break + } +} +if (!(Test-Path $prog)) { + "Cannot locate qpidd.exe" + exit 1 +} + +# The store to test is the same build type as the broker. +$store_dir = "..\qpid\store\$sub" +if (!([string]::Compare($sub, "Debug", $True))) { + $suffix = "d" +} + +$stamp = Get-Date -format %dMMMyyyy_HHmmss +$env:STORE_LIB="$store_dir\store$suffix.dll" +if ($test_store -eq "MSSQL") { + $test_store_module="$store_dir\mssql_store$suffix.dll" + $env:STORE_SQL_LIB=$test_store_module + $env:STORE_CATALOG="store_recovery_sql_test_$stamp" + $cat1="store_sql_test_$stamp" + $out = "sql_store_test_$stamp" +} +else { + $test_store_module="$store_dir\msclfs_store$suffix.dll" + $env:STORE_SQL_CLFS_LIB=$test_store_module + $env:STORE_CATALOG="store_recovery_clfs_test_$stamp" + $cat1="store_clfs_test_$stamp" + $out = "clfs_store_test_$stamp" +} + +$FAILCODE = 0 + +# Test 1... re-run some of the regular python broker tests against a broker +# with the store module loaded. +$cmdline = "$prog --auth=no --port=0 --log-to-file qpidd-store.log --no-module-dir --load-module $env:STORE_LIB --load-module $test_store_module --catalog $cat1 | foreach { set-content qpidd-store.port `$_ }" +$cmdblock = $executioncontext.invokecommand.NewScriptBlock($cmdline) +. $srcdir\background.ps1 $cmdblock + +$wait_time = 0 +while (!(Test-Path qpidd-store.port) -and ($wait_time -lt 90)) { + Start-Sleep 2 + $wait_time += 2 +} +if (!(Test-Path qpidd-store.port)) { + "Time out waiting for broker to start" + exit 1 +} +set-item -path env:QPID_PORT -value (get-content -path qpidd-store.port -totalcount 1) +Remove-Item qpidd-store.port + +$PYTHON_TEST_DIR = "$srcdir\..\..\..\tests\src\py\qpid_tests\broker_0_10" +$env:PYTHONPATH="$PYTHON_TEST_DIR;$srcdir;$env:PYTHONPATH" +python $PYTHON_DIR/qpid-python-test -m dtx -m persistence -b localhost:$env:QPID_PORT $fails $tests +$RETCODE=$LASTEXITCODE +if ($RETCODE -ne 0) { + $FAILCODE = 1 +} + +# Piping the output makes the script wait for qpidd to finish. +Invoke-Expression "$prog --quit --port $env:QPID_PORT" | Write-Output + + +# Test 2... store.py starts/stops/restarts its own brokers + +$tests = "*" +$env:QPIDD_EXEC="$prog" +$env:STORE_LIB="$store_dir\store$suffix.dll" +if ($test_store -eq "MSSQL") { + $env:STORE_SQL_LIB="$store_dir\mssql_store$suffix.dll" + $env:STORE_CATALOG="store_recovery_sql_test_$stamp" + $out = "sql_store_test_$stamp" +} +else { + $env:STORE_SQL_CLFS_LIB="$store_dir\msclfs_store$suffix.dll" + $env:STORE_CATALOG="store_recovery_clfs_test_$stamp" + $out = "clfs_store_test_$stamp" +} +Invoke-Expression "python $PYTHON_DIR/qpid-python-test -m store -D OUTDIR=$out $tests" | Out-Default +$RETCODE=$LASTEXITCODE +if ($RETCODE -ne 0) { + "FAIL $test_store store tests" + $FAILCODE = 1 +} +exit $FAILCODE diff --git a/qpid/cpp/src/tests/run_test b/qpid/cpp/src/tests/run_test new file mode 100755 index 0000000000..8e397b3458 --- /dev/null +++ b/qpid/cpp/src/tests/run_test @@ -0,0 +1,191 @@ +#!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# +# Set up environment and run a test executable or script. +# +# Output nothing if test passes, show the output if it fails and +# leave output in <test>.log for examination. +# +# If qpidd.port exists and is not empty run test with QPID_PORT=`cat qpidd.port` +# +# If $VALGRIND if is set run under valgrind. If there are valgrind +# erros show valgrind output, also leave it in <test>.valgrind for +# examination. +# + +wrapper="Qpid Test Wrapper" +function usage { + echo "Usage:" + echo " -workingDir DIR" + echo " -buildDir DIR" + echo " -sourceDir DIR" + echo " -python - run python script" + echo " -boostTest - run boost unit test" + echo " -xml - XML output from tests" + echo " -startBroker - start/stop broker before/after test" + echo " -brokerOptions - use these extra options when starting broker" + echo " -help - print this message" + echo " -- - This is required to separate the wrapped command" + echo " from the test parameters" +} + +function illegal_option { + echo "$wrapper: $1 is not an accepted option" + usage >&2 +} + +function no_command { + echo "$wrapper: No wrapped command specified" + usage >&2 +} + +function ignored_argument { + echo "Ignored argument: $1" >&2 +} + +working_dir='.' + +while true; do +case "$1" in + --) shift; break ;; + # Split up any parameters expressed as -blah=foo + # and process them next time round the loop + -*=*) option=${1%%=*}; param=${1#*=} + shift; + set -- "$option" "$param" "$@" ;; + -workingDir) working_dir=$2; shift 2 ;; + -buildDir) build_dir=$2; shift 2 ;; + -sourceDir) source_dir=$2; shift 2 ;; + -python) run_python=yes; shift ;; + -boostTest) boost_test=yes; shift ;; + -xml) xml_output=yes; shift ;; + -startBroker) start_broker=yes; shift ;; + -brokerOptions) qpidd_extra_options=$2; shift 2 ;; + -help) usage; exit 0; ;; + -*) illegal_option "$1"; exit 1; ;; + '') no_command; exit 1; ;; + *) ignored_argument "$1"; shift; ;; +esac +done + +program=$1 +shift + +logfilebase=$(pwd -P)/$(basename $program) +source $build_dir/src/tests/test_env.sh || (echo "Error: Couldn't read test_env.sh (build settings)" ; exit 1) +source $srcdir/vg_check + +# Allow environment to dictate if we output xml test results +if [ -n "$QPID_XML_TEST_OUTPUT" ] ; then + xml_output=yes +fi + +# Use VALGRIND_OPTS="--gen-suppressions=all" to generated suppressions +VALGRIND_OPTS="$VALGRIND_OPTS +--leak-check=full +--demangle=yes +--suppressions=$srcdir/.valgrind.supp +--num-callers=25 +" + +# Set up environment for running a Qpid test +if [ -n "$start_broker" ] ; then + qpidd_command="$QPIDD_EXEC --auth=no --no-module-dir --daemon --port=0 --interface 127.0.0.1 --log-to-file $logfilebase-qpidd.log $qpidd_extra_options" + if [ -n "$VALGRIND" ] ; then + if [ -n "$xml_output" ] ; then + QPID_PORT=$($VALGRIND $VALGRIND_OPTS --xml=yes --xml-file=$logfilebase-qpidd-vg.xml -- $qpidd_command) + else + QPID_PORT=$($VALGRIND $VALGRIND_OPTS --log-file=$logfilebase-qpidd.vglog -- $qpidd_command) + fi + else + QPID_PORT=$($qpidd_command) + fi +elif [ -r qpidd.port ]; then + QPID_PORT=$(cat qpidd.port) +fi +export QPID_PORT +QPID_LOG_TO_FILE="$logfilebase.log" +export QPID_LOG_TO_FILE + +# Export variables from makefile. +export srcdir + +if [ -n "$VALGRIND" ] ; then + if [ -n "$xml_output" ] ; then + valgrind_command="$VALGRIND $VALGRIND_OPTS --xml=yes --xml-file=$logfilebase-vg.xml --" + else + VG_LOG="$logfilebase.vglog" + rm -f $VG_LOG* + valgrind_command="$VALGRIND $VALGRIND_OPTS --log-file=$VG_LOG --" + fi +fi + +ERROR=0 +if [ -n "$run_python" -a -n "$PYTHON" ] ; then + (cd $working_dir; $PYTHON $program "$@") || ERROR=1 +elif [ ! -x $program ] ; then + echo "Cannot execute $program" + ERROR=1 +elif file $program | grep -q ELF; then + if [ -n "$boost_test" ] ; then + # Set boost unit test environment + if [ -n "$xml_output" ] ; then + export BOOST_TEST_SHOW_PROGRESS=no + export BOOST_TEST_OUTPUT_FORMAT=XML + export BOOST_TEST_LOG_LEVEL=test_suite + export BOOST_TEST_REPORT_LEVEL=no + (cd $working_dir; $valgrind_command $program "$@") > $logfilebase-unittest.xml || ERROR=1 + else + (cd $working_dir; $valgrind_command $program "$@") || ERROR=1 + fi + else + # This is a real executable, valgrind it if required + # Hide output unless there's an error. + (cd $working_dir; $valgrind_command $program "$@" 2>&1) || ERROR=1 + fi + if [ -n "$VG_LOG" ] ; then + vg_check $VG_LOG* || ERROR=1 + fi +else + (cd $working_dir; $program "$@") || ERROR=1 +fi + +# Check log +if [ -r $QPID_LOG_TO_FILE ]; then +egrep 'warning\|error\|critical' $QPID_LOG_TO_FILE && { + echo "WARNING: Suspicious log entries in $QPID_LOG_TO_FILE, above." +} +fi + +if [ -n "$start_broker" ] ; then + $QPIDD_EXEC --no-module-dir --quit || ERROR=1 + + # Check qpidd.log. + egrep 'warning\|error\|critical' $logfilebase-qpidd.log && { + echo "WARNING: Suspicious broker log entries in qpidd.log, above." + } + + # Check valgrind log. + if [ -n "$VALGRIND" -a -z "$xml_output" ] ; then + vg_check $logfilebase-qpidd.vglog || ERROR=1 + fi +fi +exit $ERROR diff --git a/qpid/cpp/src/tests/run_test.ps1 b/qpid/cpp/src/tests/run_test.ps1 new file mode 100644 index 0000000000..ff103e4556 --- /dev/null +++ b/qpid/cpp/src/tests/run_test.ps1 @@ -0,0 +1,162 @@ +# +# 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. +# + +param( + [string]$workingDir = $pwd, + [string]$buildDir = $(throw "-buildDir is required"), + [string]$sourceDir, + [switch]$python = $false, + [switch]$boostTest = $false, + [switch]$xml, + [switch]$startBroker = $false, + [string]$brokerOptions, + [switch]$help, + [Parameter(Mandatory=$true, ValueFromRemainingArguments=$true, Position=0)] + [String[]]$rest + ) + +if ([string]::IsNullOrEmpty($sourceDir)) { + $sourceDir = Split-Path $myInvocation.InvocationName +} + +if ([string]::IsNullOrEmpty($xml)) { + $xml = Test-Path variable:global:QPID_XML_TEST_OUTPUT +} + +# Set up environment and run a test executable or script. +. .\test_env.ps1 + +if ($rest[0] -eq $null) { + "No wrapped command specified" + exit 1 +} +# The test exe is probably not in the current binary dir - it's usually +# placed in a subdirectory based on the configuration built in Visual Studio. +# So check around to see where it is - when located, set the QPID_LIB_DIR +# and PATH to look in the corresponding configuration off the src directory, +# one level up. +$prog = $rest[0] +$logfilebase = [System.IO.Path]::GetFileNameWithoutExtension($prog) +$logfilebase = "$pwd\\$logfilebase" +# Qpid client lib sees QPID_LOG_TO_FILE; acts like using --log-to-file on +# command line. +$env:QPID_LOG_TO_FILE = "$logfilebase.log" +$is_script = $prog -match ".ps1$" +if (($is_script -or $python) -and !(Test-Path "$prog")) { + "$prog does not exist" + exit 1 +} +if (!$is_script -and !(Test-Path "$prog")) { + . $sourceDir\find_prog.ps1 $prog + $rest[0] = $prog + $env:QPID_LIB_DIR = "..\$sub" +} + +# Set up environment for running a Qpid test. If a broker should be started, +# do that, else check for a saved port number to use. +if ($startBroker) { + $broker = new-object System.Diagnostics.ProcessStartInfo + $broker.WorkingDirectory = $pwd + $broker.UseShellExecute = $false + $broker.CreateNoWindow = $true + $broker.RedirectStandardOutput = $true + $broker.FileName = $env:QPIDD_EXEC + $broker.Arguments = "--auth=no --no-module-dir --port=0 --interface 127.0.0.1 --log-to-file $logfilebase-qpidd.log $brokerOptions" + $broker_process = [System.Diagnostics.Process]::Start($broker) + $env:QPID_PORT = $broker_process.StandardOutput.ReadLine() +} +else { + # If qpidd.port exists and is not empty run test with QPID_PORT set. + if (Test-Path qpidd.port) { + set-item -path env:QPID_PORT -value (get-content -path qpidd.port -totalcount 1) + } +} + +# Now start the real test. +if ($python) { + $to_run = $PYTHON_EXE + $skip_args0 = $false + $outputfile = "" +} +elseif ($boostTest) { + if ($xml) { + $env:BOOST_TEST_SHOW_PROGRESS=no + $env:BOOST_TEST_OUTPUT_FORMAT=XML + $env:BOOST_TEST_LOG_LEVEL=test_suite + $env:BOOST_TEST_REPORT_LEVEL=no + $to_run = $rest[0] + $skip_args0 = $true + $outputfile = "$logfilebase-unittest.xml" + } + else { + $to_run = $rest[0] + $skip_args0 = $true + $outputfile = "" + } +} +else { + # Non-boost executable or powershell script + $outputfile = "" + if ($is_script) { + $to_run = (get-command powershell.exe).Definition + $skip_args0 = $false + } + else { + $to_run = $rest[0] + $skip_args0 = $true + } +} + +if ($skip_args0) { + $arglist = $rest[1..($rest.length-1)] +} +else { + $arglist = $rest +} + +if ($outputfile -eq "") { + $p = Start-Process -FilePath $to_run -ArgumentList $arglist -NoNewWindow -PassThru + $line = "" +} +else { + $p = Start-Process -FilePath $to_run -ArgumentList $arglist -NoNewWindow -RedirectStandardOutput $outputfile -PassThru +} +Wait-Process -InputObject $p +$status = $p.ExitCode + +if (Test-Path $env:QPID_LOG_TO_FILE) { + $problems = Select-String -Path $env:QPID_LOG_TO_FILE -pattern " error ", " warning ", " critical " + if ($problems -ne $null) { + "WARNING: suspicious log entries in $env:QPID_LOG_TO_FILE:\n$problems" + $status = 1 + } +} + +# If a broker was started, stop it. +if ($startBroker) { + & $env:QPIDD_EXEC --no-module-dir --quit + # Check qpid log for problems + $problems = Select-String -Path $logfilebase-qpidd.log -pattern " error ", " warning ", " critical " + if ($problems -ne $null) { + "WARNING: suspicious log entries in $logfilebase-qpidd.log:\n$problems" + $status = 1 + } +} + +exit $status diff --git a/qpid/cpp/src/tests/sasl_fed b/qpid/cpp/src/tests/sasl_fed new file mode 100755 index 0000000000..38ef43f56f --- /dev/null +++ b/qpid/cpp/src/tests/sasl_fed @@ -0,0 +1,169 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# This minimum value corresponds to sasl version 2.1.22 +minimum_sasl_version=131350 + +sasl_version=`$QPID_TEST_EXEC_DIR/sasl_version` + +# This test is necessary becasue this sasl version is the first one that permits +# redirection of the sasl config file path. +if [ "$sasl_version" -lt "$minimum_sasl_version" ]; then + echo "sasl_fed: must have sasl version 2.1.22 or greater. ( Integer value: $minimum_sasl_version ) Version is: $sasl_version" + exit 0 +fi + +# In a distribution, the python tools will be absent. +if [ ! -f $QPID_CONFIG_EXEC ] || [ ! -f $QPID_ROUTE_EXEC ] ; then + echo "python tools absent - skipping sasl_fed." + exit 0 +fi + + +sasl_config_file=$QPID_TEST_EXEC_DIR/sasl_config + +my_random_number=$RANDOM +tmp_root=/tmp/sasl_fed_$my_random_number +mkdir -p $tmp_root + +# create ACL file to allow links +echo acl allow all all > $tmp_root/sasl_fed.acl + + +#-------------------------------------------------- +#echo " Starting broker 1" +#-------------------------------------------------- +$QPIDD_EXEC \ + -p 0 --interface 127.0.0.1 \ + --data-dir $tmp_root/data_1 \ + --auth=yes \ + --mgmt-enable=yes \ + --log-enable info+ \ + --log-source yes \ + --log-to-file $tmp_root/qpidd_1.log \ + --sasl-config=$sasl_config_file \ + --acl-file $tmp_root/sasl_fed.acl \ + -d > $tmp_root/broker_1_port + +broker_1_port=`cat $tmp_root/broker_1_port` + + +#-------------------------------------------------- +#echo " Starting broker 2" +#-------------------------------------------------- +$QPIDD_EXEC \ + -p 0 --interface 127.0.0.1 \ + --data-dir $tmp_root/data_2 \ + --auth=yes \ + --mgmt-enable=yes \ + --log-enable info+ \ + --log-source yes \ + --log-to-file $tmp_root/qpidd_2.log \ + --sasl-config=$sasl_config_file \ + --acl-file $tmp_root/sasl_fed.acl \ + -d > $tmp_root/broker_2_port + +broker_2_port=`cat $tmp_root/broker_2_port` + +sleep 2 + +# I am not randomizing these names, because the test creates its own brokers. +QUEUE_NAME=sasl_fed_queue +ROUTING_KEY=sasl_fed_queue +EXCHANGE_NAME=sasl_fedex + +#-------------------------------------------------- +#echo " add exchanges" +#-------------------------------------------------- +$QPID_CONFIG_EXEC -b localhost:$broker_1_port add exchange direct $EXCHANGE_NAME +$QPID_CONFIG_EXEC -b localhost:$broker_2_port add exchange direct $EXCHANGE_NAME + + +#-------------------------------------------------- +#echo " add queues" +#-------------------------------------------------- +$QPID_CONFIG_EXEC -b localhost:$broker_1_port add queue $QUEUE_NAME +$QPID_CONFIG_EXEC -b localhost:$broker_2_port add queue $QUEUE_NAME + +sleep 5 + +#-------------------------------------------------- +#echo " create bindings" +#-------------------------------------------------- +$QPID_CONFIG_EXEC -b localhost:$broker_1_port bind $EXCHANGE_NAME $QUEUE_NAME $ROUTING_KEY +$QPID_CONFIG_EXEC -b localhost:$broker_2_port bind $EXCHANGE_NAME $QUEUE_NAME $ROUTING_KEY + +sleep 5 + + +#-------------------------------------------------- +#echo " qpid-route route add" +#-------------------------------------------------- +$QPID_ROUTE_EXEC route add zag/zag@localhost:$broker_2_port zag/zag@localhost:$broker_1_port $EXCHANGE_NAME $ROUTING_KEY "" "" DIGEST-MD5 + +sleep 5 + + +n_messages=100 +#-------------------------------------------------- +#echo " Sending 100 messages to $broker_1_port " +#-------------------------------------------------- +$QPID_TEST_EXEC_DIR/datagen --count $n_messages | $SENDER_EXEC --mechanism DIGEST-MD5 --username zag --password zag --exchange $EXCHANGE_NAME --routing-key $ROUTING_KEY --port $broker_1_port + +sleep 5 + +#-------------------------------------------------- +#echo " Examine Broker $broker_1_port" +#-------------------------------------------------- +broker_1_message_count=`$PYTHON_COMMANDS/qpid-stat -q -b localhost:$broker_1_port | grep sasl_fed_queue | awk '{print $2}'` +#echo " " + +#-------------------------------------------------- +#echo " Examine Broker $broker_2_port" +#-------------------------------------------------- +broker_2_message_count=`$PYTHON_COMMANDS/qpid-stat -q -b localhost:$broker_2_port | grep sasl_fed_queue | awk '{print $2}'` +#echo " " + +#-------------------------------------------------- +#echo " Asking brokers to quit." +#-------------------------------------------------- +$QPIDD_EXEC --port $broker_1_port --quit +$QPIDD_EXEC --port $broker_2_port --quit + + +#-------------------------------------------------- +#echo "Removing temporary directory $tmp_root" +#-------------------------------------------------- +rm -rf $tmp_root + +if [ "$broker_2_message_count" -eq "$n_messages" ]; then + # echo "good: |$broker_2_message_count| == |$n_messages|" + exit 0 +else + # echo "not ideal: |$broker_1_message_count| != |$n_messages|" + exit 1 +fi + + + + + + diff --git a/qpid/cpp/src/tests/sasl_fed_ex b/qpid/cpp/src/tests/sasl_fed_ex new file mode 100755 index 0000000000..e2ee37ba39 --- /dev/null +++ b/qpid/cpp/src/tests/sasl_fed_ex @@ -0,0 +1,283 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +#=============================================================================== +# These tests create federated links between two brokers using SASL security. +# The SASL mechanism used is EXTERNAL, which is satisfied by SSL +# transport-layer security. +#=============================================================================== + +source $QPID_TEST_COMMON + +ensure_python_tests + +script_name=`basename $0` + +if [ $# -lt 1 ] || [ $# -gt 2 ] +then + echo + # These are the four different ways of creating links ( or routes+links ) + # that the qpid-route command provides. + echo "Usage: ${script_name} dynamic|link|queue|route" + echo + exit 1 +fi + +qpid_route_method=$1 + +# Debugging print. -------------------------- +debug= +function print { + if [ "$debug" ]; then + echo "${script_name}: $1" + fi +} + +print "=========== start sasl_fed_ex $* ============" + + + +# This minimum value corresponds to sasl version 2.1.22 +minimum_sasl_version=131350 + +sasl_version=`$QPID_TEST_EXEC_DIR/sasl_version` + +# This test is necessary because this sasl version is the first one that permits +# redirection of the sasl config file path. +if [ "$sasl_version" -lt "$minimum_sasl_version" ]; then + echo "sasl_fed: must have sasl version 2.1.22 or greater. ( Integer value: $minimum_sasl_version ) Version is: $sasl_version" + exit 0 +fi + +CERT_DIR=`pwd`/test_cert_db +CERT_PW_FILE=`pwd`/cert.password +TEST_HOSTNAME=127.0.0.1 + +create_certs() { + #create certificate and key databases with single, simple, self-signed certificate in it + mkdir ${CERT_DIR} + certutil -N -d ${CERT_DIR} -f ${CERT_PW_FILE} + certutil -S -d ${CERT_DIR} -n ${TEST_HOSTNAME} -s "CN=${TEST_HOSTNAME}" -t "CT,," -x -f ${CERT_PW_FILE} -z /bin/sh 2> /dev/null +} + +delete_certs() { + if [[ -e ${CERT_DIR} ]] ; then + print "removing cert dir ${CERT_DIR}" + rm -rf ${CERT_DIR} + fi +} + + +CERTUTIL=$(type -p certutil) +if [[ !(-x $CERTUTIL) ]] ; then + echo "No certutil, skipping ssl test"; + exit 0; +fi + +delete_certs +create_certs 2> /dev/null +if [ ! $? ]; then + error "Could not create test certificate" + exit 1 +fi + +sasl_config_dir=$QPID_TEST_EXEC_DIR/sasl_config + +tmp_root=$QPID_TEST_EXEC_DIR/sasl_fed_ex_temp +print "results dir is ${tmp_root}" +rm -rf ${tmp_root} +mkdir -p $tmp_root + +SRC_SSL_PORT=6667 +DST_SSL_PORT=6666 + +SRC_SSL_PORT_2=6668 +DST_SSL_PORT_2=6669 + +SRC_TCP_PORT=5801 +DST_TCP_PORT=5807 + +SRC_TCP_PORT_2=5802 +DST_TCP_PORT_2=5803 + +export QPID_SSL_CERT_NAME=${TEST_HOSTNAME} + +export QPID_NO_MODULE_DIR=1 +export QPID_SSL_CERT_DB=${CERT_DIR} +export QPID_SSL_CERT_PASSWORD_FILE=${CERT_PW_FILE} +export QPID_SSL_CERT_NAME=${TEST_HOSTNAME} + + + +####################################### +# Understanding this Plumbing +####################################### +# 1. when you establish the route with qpid-route, +# here is the best termiology to use: +# +# qpid-route route add DST SRC +# +# 2. DST will connect to SRC through the ssl port of SRC. +# +# 3. sender client connects to the tcp port of SRC. +# +# 4. sender specifies mechanism ANONYMOUS. +# +# 5. DST pulls messages off the temp queue on SRC to itself. +# + +COMMON_BROKER_OPTIONS=" \ + --ssl-sasl-no-dict \ + --sasl-config=$sasl_config_dir \ + --ssl-require-client-authentication \ + --auth yes \ + --ssl-cert-db $CERT_DIR \ + --ssl-cert-password-file $CERT_PW_FILE \ + --ssl-cert-name $TEST_HOSTNAME \ + --no-data-dir \ + --no-module-dir \ + --mgmt-enable=yes \ + --log-enable info+ \ + --log-source yes \ + --daemon " + + +function start_brokers { + # vanilla brokers -------------------------------- + print "Starting SRC broker" + $QPIDD_EXEC \ + --port=${SRC_TCP_PORT} \ + --ssl-port ${SRC_SSL_PORT} \ + ${COMMON_BROKER_OPTIONS} \ + --log-to-file $tmp_root/qpidd_src.log 2> /dev/null + + broker_ports[0]=${SRC_TCP_PORT} + + print "Starting DST broker" + $QPIDD_EXEC \ + --port=${DST_TCP_PORT} \ + --ssl-port ${DST_SSL_PORT} \ + ${COMMON_BROKER_OPTIONS} \ + --log-to-file $tmp_root/qpidd_dst.log 2> /dev/null + + broker_ports[1]=${DST_TCP_PORT} +} + +function halt_brokers { + n_brokers=${#broker_ports[@]} + print "Halting ${n_brokers} brokers." + for i in $(seq 0 $((${n_brokers} - 1))) + do + halt_port=${broker_ports[$i]} + print "Halting broker $i on port ${halt_port}" + $QPIDD_EXEC --port ${halt_port} --quit + done + +} + + +start_brokers + + +# I am not randomizing these names, because this test creates its own brokers. +QUEUE_NAME=sasl_fed_queue +ROUTING_KEY=sasl_fed_queue +EXCHANGE_NAME=sasl_fedex + + +print "add exchanges" +$QPID_CONFIG_EXEC -b localhost:${SRC_TCP_PORT} add exchange direct $EXCHANGE_NAME +$QPID_CONFIG_EXEC -b localhost:${DST_TCP_PORT} add exchange direct $EXCHANGE_NAME + + +print "add queues" +$QPID_CONFIG_EXEC -b localhost:${SRC_TCP_PORT} add queue $QUEUE_NAME +$QPID_CONFIG_EXEC -b localhost:${DST_TCP_PORT} add queue $QUEUE_NAME + + +print "create bindings" +$QPID_CONFIG_EXEC -b localhost:${SRC_TCP_PORT} bind $EXCHANGE_NAME $QUEUE_NAME $ROUTING_KEY +$QPID_CONFIG_EXEC -b localhost:${DST_TCP_PORT} bind $EXCHANGE_NAME $QUEUE_NAME $ROUTING_KEY + + +# +# NOTE: The SRC broker *must* be referred to as $TEST_HOSTNAME, and not as "localhost". +# It must be referred to by the exact string given as the Common Name (CN) in the cert, +# which was created in the function create_certs, above. + + + +#---------------------------------------------------------------- +# Use qpid-route to create the link, or the link+route, depending +# on which of its several methods was requested. +#---------------------------------------------------------------- +if [ ${qpid_route_method} == "dynamic" ]; then + print "dynamic add" + $QPID_ROUTE_EXEC -t ssl dynamic add localhost:${DST_TCP_PORT} $TEST_HOSTNAME:${SRC_SSL_PORT} $EXCHANGE_NAME "" "" EXTERNAL +elif [ ${qpid_route_method} == "link" ]; then + print "link add" + $QPID_ROUTE_EXEC -t ssl link add localhost:${DST_TCP_PORT} $TEST_HOSTNAME:${SRC_SSL_PORT} EXTERNAL +elif [ ${qpid_route_method} == "queue" ]; then + print "queue add" + $QPID_ROUTE_EXEC -t ssl queue add localhost:${DST_TCP_PORT} $TEST_HOSTNAME:${SRC_SSL_PORT} $EXCHANGE_NAME $ROUTING_KEY EXTERNAL +elif [ ${qpid_route_method} == "route" ]; then + print "route add" + $QPID_ROUTE_EXEC -t ssl route add localhost:${DST_TCP_PORT} $TEST_HOSTNAME:${SRC_SSL_PORT} $EXCHANGE_NAME $ROUTING_KEY "" "" EXTERNAL +else + echo "unknown method: |${qpid_route_method}|" + echo " choices are: dynamic|link|queue|route " + halt_brokers + exit 1 +fi + + +# I don't know how to avoid this sleep yet. It has to come after route-creation +# to avoid false negatives. +sleep 5 + +# Look only at the transport field, which should be "ssl". +print "check the link" +link_status=$($QPID_ROUTE_EXEC link list localhost:${DST_TCP_PORT} | tail -1 | awk '{print $3}') + +halt_brokers + +sleep 1 + +if [ ! ${link_status} ]; then + print "link_status is empty" + print "result: fail" + exit 2 +fi + +if [ ${link_status} == "ssl" ]; then + print "result: good" + # Only remove the tmp_root on success, to permit debugging. + print "Removing temporary directory $tmp_root" + rm -rf $tmp_root + exit 0 +fi + +print "link_status has a bad value: ${link_status}" +print "result: fail" +exit 3 + + + diff --git a/qpid/cpp/src/tests/sasl_no_dir b/qpid/cpp/src/tests/sasl_no_dir new file mode 100755 index 0000000000..b2f5d1668e --- /dev/null +++ b/qpid/cpp/src/tests/sasl_no_dir @@ -0,0 +1,106 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +script_name=`basename $0` + +# This minimum value corresponds to sasl version 2.1.22 +minimum_sasl_version=131350 + +sasl_version=$($QPID_TEST_EXEC_DIR/sasl_version) + +# This test is necessary because this sasl version is the first one that permits +# redirection of the sasl config file path. +if [ "$sasl_version" -lt "$minimum_sasl_version" ]; then + echo "sasl_fed: must have sasl version 2.1.22 or greater. ( Integer value: $minimum_sasl_version ) Version is: $sasl_version" + exit 0 +fi + + +sasl_config_dir=$QPID_TEST_EXEC_DIR/sasl_config + + +# Debugging print. -------------------------- +debug= +function print { + if [ "$debug" ]; then + echo "${script_name}: $1" + fi +} + + +my_random_number=$RANDOM +tmp_root=/tmp/sasl_fed_$my_random_number +mkdir -p $tmp_root + + +LOG_FILE=$tmp_root/qpidd.log + +# If you want to see this test fail, just comment out this 'mv' command. +print "Moving sasl configuration dir." +mv ${sasl_config_dir} ${sasl_config_dir}- + + +#-------------------------------------------------- +print " Starting broker" +#-------------------------------------------------- +$QPIDD_EXEC \ + -p 0 --interface 127.0.0.1 \ + --no-data-dir \ + --auth=yes \ + --mgmt-enable=yes \ + --log-enable info+ \ + --log-source yes \ + --log-to-file ${LOG_FILE} \ + --sasl-config=$sasl_config_dir \ + -d 2> /dev/null 1> $tmp_root/broker_port + + + +# If it works right, the output will look something like this: ( two lines long ) +# Daemon startup failed: SASL: sasl_set_path failed: no such directory: /home/mick/trunk/qpid/cpp/src/tests/sasl_config (qpid/broker/SaslAuthenticator.cpp:112) +# 2011-10-13 14:07:00 critical qpidd.cpp:83: Unexpected error: Daemon startup failed: SASL: sasl_set_path failed: no such directory: /home/mick/trunk/qpid/cpp/src/tests/sasl_config (qpid/broker/SaslAuthenticator.cpp:112) + +result=`cat ${LOG_FILE} | grep "sasl_set_path failed: no such directory" | wc -l ` + +#-------------------------------------------------- +print "Restore the Sasl config dir to its original place." +#-------------------------------------------------- +mv ${sasl_config_dir}- ${sasl_config_dir} + +if [ "2" -eq ${result} ]; then + print "result: success" + rm -rf $tmp_root + exit 0 +fi + + +# If this test fails, the broker is still alive. +# Kill it. +broker_port=`cat $tmp_root/broker_port` +#-------------------------------------------------- +print "Asking broker to quit." +#-------------------------------------------------- +$QPIDD_EXEC --port $broker_port --quit + +rm -rf $tmp_root + +print "result: fail" +exit 1 diff --git a/qpid/cpp/src/tests/sasl_test_setup.sh b/qpid/cpp/src/tests/sasl_test_setup.sh new file mode 100755 index 0000000000..d41efbe6e5 --- /dev/null +++ b/qpid/cpp/src/tests/sasl_test_setup.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +source ./test_env.sh + +test -x $SASL_PW || { echo Skipping SASL test, saslpasswd2 not found; exit 0; } + +mkdir -p sasl_config + +# Create configuration file. +cat > sasl_config/qpidd.conf <<EOF +pwcheck_method: auxprop +auxprop_plugin: sasldb +sasldb_path: $PWD/sasl_config/qpidd.sasldb +sql_select: dummy select +mech_list: ANONYMOUS PLAIN DIGEST-MD5 EXTERNAL CRAM-MD5 +EOF + +# Populate temporary sasl db. +SASLTEST_DB=./sasl_config/qpidd.sasldb +rm -f $SASLTEST_DB +echo guest | $SASL_PW -c -p -f $SASLTEST_DB -u QPID guest +echo zig | $SASL_PW -c -p -f $SASLTEST_DB -u QPID zig +echo zag | $SASL_PW -c -p -f $SASLTEST_DB -u QPID zag + diff --git a/qpid/cpp/src/tests/sasl_version.cpp b/qpid/cpp/src/tests/sasl_version.cpp new file mode 100644 index 0000000000..db3efe4181 --- /dev/null +++ b/qpid/cpp/src/tests/sasl_version.cpp @@ -0,0 +1,48 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include <iostream> + +#include "sasl/sasl.h" + + +/* + Some tests need to distinguish between different versions of + SASL. This encodes and outputs the version number as an integer + for easy use in testing scripts. +*/ + +int +main ( ) +{ + // I assume that these are 8-bit quantities.... + int sasl_version = (SASL_VERSION_MAJOR << 16) + + (SASL_VERSION_MINOR << 8) + + SASL_VERSION_STEP; + + std::cout << sasl_version << std::endl; + + return 0; +} + + + + diff --git a/qpid/cpp/src/tests/sender.cpp b/qpid/cpp/src/tests/sender.cpp new file mode 100644 index 0000000000..063b5e87dc --- /dev/null +++ b/qpid/cpp/src/tests/sender.cpp @@ -0,0 +1,157 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include <qpid/client/FailoverManager.h> +#include <qpid/client/Session.h> +#include <qpid/client/AsyncSession.h> +#include <qpid/client/Message.h> +#include <qpid/client/MessageReplayTracker.h> +#include <qpid/client/QueueOptions.h> +#include <qpid/Exception.h> +#include "TestOptions.h" + +#include "qpid/messaging/Message.h" // Only for Statistics +#include "Statistics.h" + +#include <fstream> +#include <iostream> + +using namespace qpid; +using namespace qpid::client; +using namespace qpid::framing; + +using namespace std; + +namespace qpid { +namespace tests { + +struct Args : public qpid::TestOptions +{ + string destination; + string key; + uint sendEos; + bool durable; + uint ttl; + string lvqMatchValue; + string lvqMatchFile; + bool reportTotal; + int reportEvery; + bool reportHeader; + + Args() : + key("test-queue"), sendEos(0), durable(false), ttl(0), + reportTotal(false), + reportEvery(0), + reportHeader(true) + { + addOptions() + ("exchange", qpid::optValue(destination, "EXCHANGE"), "Exchange to send messages to") + ("routing-key", qpid::optValue(key, "KEY"), "Routing key to add to messages") + ("send-eos", qpid::optValue(sendEos, "N"), "Send N EOS messages to mark end of input") + ("durable", qpid::optValue(durable, "true|false"), "Mark messages as durable.") + ("ttl", qpid::optValue(ttl, "msecs"), "Time-to-live for messages, in milliseconds") + ("lvq-match-value", qpid::optValue(lvqMatchValue, "KEY"), "The value to set for the LVQ match key property") + ("lvq-match-file", qpid::optValue(lvqMatchFile, "FILE"), "A file containing values to set for the LVQ match key property") + ("report-total", qpid::optValue(reportTotal), "Report total throughput statistics") + ("report-every", qpid::optValue(reportEvery,"N"), "Report throughput statistics every N messages") + ("report-header", qpid::optValue(reportHeader, "yes|no"), "Headers on report.") + ; + } +}; + +const string EOS("eos"); + +class Sender : public FailoverManager::Command +{ + public: + Sender(Reporter<Throughput>& reporter, const std::string& destination, const std::string& key, uint sendEos, bool durable, uint ttl, + const std::string& lvqMatchValue, const std::string& lvqMatchFile); + void execute(AsyncSession& session, bool isRetry); + + private: + Reporter<Throughput>& reporter; + messaging::Message dummyMessage; + const std::string destination; + MessageReplayTracker sender; + Message message; + const uint sendEos; + uint sent; + std::ifstream lvqMatchValues; +}; + +Sender::Sender(Reporter<Throughput>& rep, const std::string& dest, const std::string& key, uint eos, bool durable, uint ttl, const std::string& lvqMatchValue, const std::string& lvqMatchFile) : + reporter(rep), destination(dest), sender(10), message("", key), sendEos(eos), sent(0) , lvqMatchValues(lvqMatchFile.c_str()) +{ + if (durable){ + message.getDeliveryProperties().setDeliveryMode(framing::PERSISTENT); + } + + if (ttl) { + message.getDeliveryProperties().setTtl(ttl); + } + + if (!lvqMatchValue.empty()) { + message.getHeaders().setString(QueueOptions::strLVQMatchProperty, lvqMatchValue); + } +} + +void Sender::execute(AsyncSession& session, bool isRetry) +{ + if (isRetry) sender.replay(session); + else sender.init(session); + string data; + while (getline(std::cin, data)) { + message.setData(data); + //message.getHeaders().setInt("SN", ++sent); + string matchKey; + if (lvqMatchValues && getline(lvqMatchValues, matchKey)) { + message.getHeaders().setString(QueueOptions::strLVQMatchProperty, matchKey); + } + reporter.message(dummyMessage); // For statistics + sender.send(message, destination); + } + for (uint i = sendEos; i > 0; --i) { + message.setData(EOS); + sender.send(message, destination); + } +} + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char ** argv) +{ + Args opts; + try { + opts.parse(argc, argv); + Reporter<Throughput> reporter(std::cout, opts.reportEvery, opts.reportHeader); + FailoverManager connection(opts.con); + Sender sender(reporter, opts.destination, opts.key, opts.sendEos, opts.durable, opts.ttl, opts.lvqMatchValue, opts.lvqMatchFile); + connection.execute(sender); + connection.close(); + if (opts.reportTotal) reporter.report(); + return 0; + } catch(const std::exception& error) { + std::cout << "Failed: " << error.what() << std::endl; + } + return 1; +} diff --git a/qpid/cpp/src/tests/shared_perftest b/qpid/cpp/src/tests/shared_perftest new file mode 100755 index 0000000000..709ffd56b5 --- /dev/null +++ b/qpid/cpp/src/tests/shared_perftest @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +exec `dirname $0`/run_perftest 100000 --mode shared --npubs 16 --nsubs 16 diff --git a/qpid/cpp/src/tests/shlibtest.cpp b/qpid/cpp/src/tests/shlibtest.cpp new file mode 100644 index 0000000000..5655eb7e64 --- /dev/null +++ b/qpid/cpp/src/tests/shlibtest.cpp @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +namespace qpid { +namespace tests { + +int* loaderData = 0; +extern "C" +#ifdef WIN32 +__declspec(dllexport) +#endif +void callMe(int *i) { loaderData=i; } + +struct OnUnload { ~OnUnload() { *loaderData=42; } }; +OnUnload unloader; // For destructor. + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/ssl_test b/qpid/cpp/src/tests/ssl_test new file mode 100755 index 0000000000..d681059495 --- /dev/null +++ b/qpid/cpp/src/tests/ssl_test @@ -0,0 +1,331 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Run a simple test over SSL + +#set -x + +CONFIG=$(dirname $0)/config.null +TEST_CERT_DIR=`pwd`/test_cert_dir +CERT_DB=${TEST_CERT_DIR}/test_cert_db +CERT_PW_FILE=`pwd`/cert.password +TEST_HOSTNAME=127.0.0.1 +TEST_CLIENT_CERT=rumplestiltskin +CA_PEM_FILE=${TEST_CERT_DIR}/ca_cert.pem +OTHER_CA_CERT_DB=${TEST_CERT_DIR}/x_ca_cert_db +OTHER_CA_PEM_FILE=${TEST_CERT_DIR}/other_ca_cert.pem +PY_PING_BROKER=${QPID_TEST_SRC_DIR}/ping_broker +COUNT=10 + +if [[ -a $AMQP_LIB ]] ; then + MODULES="--load-module $AMQP_LIB" +fi + +trap cleanup EXIT + +error() { echo $*; exit 1; } + +# create the test certificate database +# $1 = string used as Subject in server's certificate +# $2 = string used as SubjectAlternateName (SAN) in server's certificate +create_certs() { + + local CERT_SUBJECT=${1:-"CN=${TEST_HOSTNAME},O=MyCo,ST=Massachusetts,C=US"} + local CERT_SAN=${2:-"*.server.com"} + + mkdir -p ${TEST_CERT_DIR} + rm -rf ${TEST_CERT_DIR}/* + + # Set Up a CA with a self-signed Certificate + # + mkdir -p ${CERT_DB} + certutil -N -d ${CERT_DB} -f ${CERT_PW_FILE} + certutil -S -d ${CERT_DB} -n "Test-CA" -s "CN=Test-CA,O=MyCo,ST=Massachusetts,C=US" -t "CT,," -x -f ${CERT_PW_FILE} -z /bin/sh >/dev/null 2>&1 + certutil -L -d ${CERT_DB} -n "Test-CA" -a -o ${CERT_DB}/rootca.crt -f ${CERT_PW_FILE} + #certutil -L -d ${CERT_DB} -f ${CERT_PW_FILE} + + # create server certificate signed by Test-CA + # + certutil -R -d ${CERT_DB} -s "${CERT_SUBJECT}" -o ${TEST_CERT_DIR}/server.req -f ${CERT_PW_FILE} -z /bin/sh > /dev/null 2>&1 + certutil -C -d ${CERT_DB} -c "Test-CA" -8 "${CERT_SAN}" -i ${TEST_CERT_DIR}/server.req -o ${TEST_CERT_DIR}/server.crt -f ${CERT_PW_FILE} -m ${RANDOM} + certutil -A -d ${CERT_DB} -n ${TEST_HOSTNAME} -i ${TEST_CERT_DIR}/server.crt -t "Pu,," + + # create a certificate to identify the client + # + certutil -R -d ${CERT_DB} -s "CN=${TEST_CLIENT_CERT}" -o ${TEST_CERT_DIR}/client.req -f ${CERT_PW_FILE} -z /bin/sh > /dev/null 2>&1 + certutil -C -d ${CERT_DB} -c "Test-CA" -8 "*.client.com" -i ${TEST_CERT_DIR}/client.req -o ${TEST_CERT_DIR}/client.crt -f ${CERT_PW_FILE} -m ${RANDOM} + certutil -A -d ${CERT_DB} -n ${TEST_CLIENT_CERT} -i ${TEST_CERT_DIR}/client.crt -t "Pu,," + ### + #certutil -N -d ${SERVER_CERT_DIR} -f ${CERT_PW_FILE} + #certutil -S -d ${SERVER_CERT_DIR} -n ${TEST_HOSTNAME} -s "CN=${TEST_HOSTNAME}" -t "CT,," -x -f ${CERT_PW_FILE} -z /usr/bin/certutil + #certutil -S -d ${SERVER_CERT_DIR} -n ${TEST_CLIENT_CERT} -s "CN=${TEST_CLIENT_CERT}" -t "CT,," -x -f ${CERT_PW_FILE} -z /usr/bin/certutil + + # Set up a separate DB with its own CA for testing failure to validate scenario + # + mkdir -p ${OTHER_CA_CERT_DB} + certutil -N -d ${OTHER_CA_CERT_DB} -f ${CERT_PW_FILE} + certutil -S -d ${OTHER_CA_CERT_DB} -n "Other-Test-CA" -s "CN=Another Test CA,O=MyCo,ST=Massachusetts,C=US" -t "CT,," -x -f ${CERT_PW_FILE} -z /bin/sh >/dev/null 2>&1 + certutil -L -d ${OTHER_CA_CERT_DB} -n "Other-Test-CA" -a -o ${OTHER_CA_CERT_DB}/rootca.crt -f ${CERT_PW_FILE} + #certutil -L -d ${OTHER_CA_CERT_DB} -f ${CERT_PW_FILE} +} + +delete_certs() { + if [[ -e ${TEST_CERT_DIR} ]] ; then + rm -rf ${TEST_CERT_DIR} + fi +} + +# Don't need --no-module-dir or --no-data-dir as they are set as env vars in test_env.sh +COMMON_OPTS="--daemon --config $CONFIG --ssl-cert-db $CERT_DB --ssl-cert-password-file $CERT_PW_FILE --ssl-cert-name $TEST_HOSTNAME" + +# Start new brokers: +# $1 must be integer +# $2 = extra opts +# Append used ports to PORTS variable +start_brokers() { + local -a ports + for (( i=0; $i<$1; i++)) do + ports[$i]=$($QPIDD_EXEC --port 0 --interface 127.0.0.1 $COMMON_OPTS $2) || error "Could not start broker $i" + done + PORTS=( ${PORTS[@]} ${ports[@]} ) +} + +# Stop single broker: +# $1 is number of broker to stop (0 based) +stop_broker() { + $QPIDD_EXEC -qp ${PORTS[$1]} + + # Remove from ports array + unset PORTS[$1] +} + +stop_brokers() { + for port in "${PORTS[@]}"; + do + $QPIDD_EXEC -qp $port + done + PORTS=() +} + +pick_port() { + # We need a fixed port to set --cluster-url. Use qpidd to pick a free port. + PICK=`../qpidd --no-module-dir --listen-disable ssl -dp0` + ../qpidd --no-module-dir -qp $PICK + echo $PICK +} + +cleanup() { + stop_brokers + delete_certs + rm -f ${CERT_PW_FILE} +} + +start_ssl_broker() { + start_brokers 1 "--transport ssl --ssl-port 0 --require-encryption --auth no $MODULES" +} + +start_ssl_mux_broker() { + ../qpidd $COMMON_OPTS --port $1 --ssl-port $1 --auth no + PORTS=( ${PORTS[@]} $1 ) +} + +sasl_config_dir=$QPID_TEST_EXEC_DIR/sasl_config + +start_authenticating_broker() { + start_brokers 1 "--transport ssl --ssl-port 0 --require-encryption --ssl-sasl-no-dict --ssl-require-client-authentication --auth yes --sasl-config=${sasl_config_dir} $MODULES" +} + +ssl_cluster_broker() { # $1 = port + start_brokers 1 "--ssl-port $1 --auth no --load-module $CLUSTER_LIB --cluster-name ssl_test.$HOSTNAME.$$ --cluster-url amqp:ssl:$TEST_HOSTNAME:$1" + + # Wait for broker to be ready + qpid-ping -Pssl -b $TEST_HOSTNAME:$1 -q || { echo "Cannot connect to broker on $1"; exit 1; } +} + +CERTUTIL=$(type -p certutil) +if [[ !(-x $CERTUTIL) ]] ; then + echo "No certutil, skipping ssl test"; + exit 0; +fi + +if [[ !(-e ${CERT_PW_FILE}) ]] ; then + echo password > ${CERT_PW_FILE} +fi +delete_certs +create_certs || error "Could not create test certificate database" + +start_ssl_broker +PORT=${PORTS[0]} +echo "Running SSL test on port $PORT" +export QPID_NO_MODULE_DIR=1 +export QPID_SSL_CERT_DB=${CERT_DB} +export QPID_SSL_CERT_PASSWORD_FILE=${CERT_PW_FILE} + +## Test connection via connection settings +./qpid-perftest --count ${COUNT} --port ${PORT} -P ssl -b $TEST_HOSTNAME --summary + +## Test connection with a URL +URL=amqp:ssl:$TEST_HOSTNAME:$PORT +./qpid-send -b $URL --content-string=hello -a "foo;{create:always}" +MSG=`./qpid-receive -b $URL -a "foo;{create:always}" --messages 1` +test "$MSG" = "hello" || { echo "receive failed '$MSG' != 'hello'"; exit 1; } + +if [[ -a $AMQP_LIB ]] ; then + echo "Testing ssl over AMQP 1.0" + ./qpid-send --connection-options '{protocol:amqp1.0}' -b $URL --content-string=hello -a "foo;{create:always}" + MSG=`./qpid-receive --connection-options '{protocol:amqp1.0}' -b $URL -a "foo;{create:always}" --messages 1` + test "$MSG" = "hello" || { echo "receive failed for AMQP 1.0 '$MSG' != 'hello'"; exit 1; } +fi + +## Test connection with a combination of URL and connection options (in messaging API) +URL=$TEST_HOSTNAME:$PORT +./qpid-send -b $URL --connection-options '{transport:ssl,heartbeat:2}' --content-string='hello again' -a "foo;{create:always}" +MSG=`./qpid-receive -b $URL --connection-options '{transport:ssl,heartbeat:2}' -a "foo;{create:always}" --messages 1` +test "$MSG" = "hello again" || { echo "receive failed '$MSG' != 'hello again'"; exit 1; } + +## Test using the Python client +if test -d $PYTHON_DIR; then + echo "Testing Non-Authenticating with Python Client..." + URL=amqps://$TEST_HOSTNAME:$PORT + if `$PY_PING_BROKER -b $URL`; then echo " Passed"; else { echo " Failed"; exit 1; }; fi +else + echo "Skipping python part of ssl_test, no python dir." +fi + +#### Client Authentication tests + +start_authenticating_broker +PORT2=${PORTS[1]} +echo "Running SSL client authentication test on port $PORT2" +URL=amqp:ssl:$TEST_HOSTNAME:$PORT2 + +## See if you can set the SSL cert-name for the connection +./qpid-send -b $URL --connection-options "{ssl-cert-name: $TEST_CLIENT_CERT }" --content-string=hello -a "bar;{create:always}" +MSG2=`./qpid-receive -b $URL --connection-options "{ssl-cert-name: $TEST_CLIENT_CERT }" -a "bar;{create:always}" --messages 1` +test "$MSG2" = "hello" || { echo "receive failed '$MSG2' != 'hello'"; exit 1; } + +## Make sure that connect fails with an invalid SSL cert-name +./qpid-send -b $URL --connection-options "{ssl-cert-name: pignose }" --content-string=hello -a "baz;{create:always}" 2>/dev/null 1>/dev/null +MSG3=`./qpid-receive -b $URL --connection-options "{ssl-cert-name: pignose }" -a "baz;{create:always}" --messages 1 2>/dev/null` +test "$MSG3" = "" || { echo "receive succeeded without valid ssl cert '$MSG3' != ''"; exit 1; } + +stop_brokers + +# Test ssl muxed with plain TCP on the same connection + +# Test a specified port number - since tcp/ssl are the same port don't need to specify --transport ssl +PORT=`pick_port` +start_ssl_mux_broker $PORT || error "Could not start broker" +echo "Running SSL/TCP mux test on fixed port $PORT" + +## Test connection via connection settings +./qpid-perftest --count ${COUNT} --port ${PORT} -P ssl -b $TEST_HOSTNAME --summary || error "SSL connection failed!" +./qpid-perftest --count ${COUNT} --port ${PORT} -P tcp -b $TEST_HOSTNAME --summary || error "TCP connection failed!" + +# Test a broker chosen port - since ssl chooses port need to use --transport ssl here +start_ssl_broker +PORT=${PORTS[0]} +echo "Running SSL/TCP mux test on random port $PORT" + +## Test connection via connection settings +./qpid-perftest --count ${COUNT} --port ${PORT} -P ssl -b $TEST_HOSTNAME --summary || error "SSL connection failed!" +./qpid-perftest --count ${COUNT} --port ${PORT} -P tcp -b $TEST_HOSTNAME --summary || error "TCP connection failed!" + +stop_brokers + +### Additional tests that require 'openssl' and 'pk12util' to be installed (optional) + +PK12UTIL=$(type -p pk12util) +if [[ !(-x $PK12UTIL) ]] ; then + echo >&2 "'pk12util' command not available, skipping remaining tests" + exit 0 +fi + +OPENSSL=$(type -p openssl) +if [[ !(-x $OPENSSL) ]] ; then + echo >&2 "'openssl' command not available, skipping remaining tests" + exit 0 +fi + +if test -d $PYTHON_DIR; then +## verify python version > 2.5 (only 2.6+ does certificate checking) + PY_VERSION=$(python -c "import sys; print hex(sys.hexversion)") + if (( PY_VERSION < 0x02060000 )); then + echo >&2 "Detected python version < 2.6 - skipping certificate verification tests" + exit 0 + fi + + echo "Testing Certificate validation and Authentication with the Python Client..." + +# extract the CA's certificate as a PEM file + get_ca_certs() { + $PK12UTIL -o ${TEST_CERT_DIR}/CA_pk12.out -d ${CERT_DB} -n "Test-CA" -w ${CERT_PW_FILE} -k ${CERT_PW_FILE} > /dev/null + $OPENSSL pkcs12 -in ${TEST_CERT_DIR}/CA_pk12.out -out ${CA_PEM_FILE} -nokeys -passin file:${CERT_PW_FILE} >/dev/null + $PK12UTIL -o ${TEST_CERT_DIR}/other_CA_pk12.out -d ${OTHER_CA_CERT_DB} -n "Other-Test-CA" -w ${CERT_PW_FILE} -k ${CERT_PW_FILE} > /dev/null + $OPENSSL pkcs12 -in ${TEST_CERT_DIR}/other_CA_pk12.out -out ${OTHER_CA_PEM_FILE} -nokeys -passin file:${CERT_PW_FILE} >/dev/null + } + + get_ca_certs || error "Could not extract CA certificates as PEM files" + start_ssl_broker + PORT=${PORTS[0]} + URL=amqps://$TEST_HOSTNAME:$PORT +# verify the python client can authenticate the broker using the CA + if `${PY_PING_BROKER} -b $URL --ssl-trustfile=${CA_PEM_FILE}`; then echo " Passed"; else { echo " Failed"; exit 1; }; fi +# verify the python client fails to authenticate the broker when using the other CA + if `${PY_PING_BROKER} -b $URL --ssl-trustfile=${OTHER_CA_PEM_FILE} > /dev/null 2>&1`; then { echo " Failed"; exit 1; }; else echo " Passed"; fi + stop_brokers + +# create a certificate without matching TEST_HOSTNAME, should fail to verify + + create_certs "O=MyCo" "*.${TEST_HOSTNAME}.com" || error "Could not create server test certificate" + get_ca_certs || error "Could not extract CA certificates as PEM files" + start_ssl_broker + PORT=${PORTS[0]} + URL=amqps://$TEST_HOSTNAME:$PORT + if `${PY_PING_BROKER} -b $URL --ssl-trustfile=${CA_PEM_FILE} > /dev/null 2>&1`; then { echo " Failed"; exit 1; }; else echo " Passed"; fi +# but disabling the check for the hostname should pass + if `${PY_PING_BROKER} -b $URL --ssl-trustfile=${CA_PEM_FILE} --ssl-skip-hostname-check`; then echo " Passed"; else { echo " Failed"; exit 1; }; fi + stop_brokers + +# test SubjectAltName parsing + + if (( PY_VERSION >= 0x02070300 )); then + # python 2.7.3+ supports SubjectAltName extraction + # create a certificate with TEST_HOSTNAME only in SAN, should verify OK + create_certs "O=MyCo" "*.foo.com,${TEST_HOSTNAME},*xyz.com" || error "Could not create server test certificate" + get_ca_certs || error "Could not extract CA certificates as PEM files" + start_ssl_broker + PORT=${PORTS[0]} + URL=amqps://$TEST_HOSTNAME:$PORT + if `${PY_PING_BROKER} -b $URL --ssl-trustfile=${CA_PEM_FILE}`; then echo " Passed"; else { echo " Failed"; exit 1; }; fi + stop_brokers + + create_certs "O=MyCo" "*${TEST_HOSTNAME}" || error "Could not create server test certificate" + get_ca_certs || error "Could not extract CA certificates as PEM files" + start_ssl_broker + PORT=${PORTS[0]} + URL=amqps://$TEST_HOSTNAME:$PORT + if `${PY_PING_BROKER} -b $URL --ssl-trustfile=${CA_PEM_FILE}`; then echo " Passed"; else { echo " Failed"; exit 1; }; fi + stop_brokers + fi + +fi + diff --git a/qpid/cpp/src/tests/store.py b/qpid/cpp/src/tests/store.py new file mode 100755 index 0000000000..5c1934dded --- /dev/null +++ b/qpid/cpp/src/tests/store.py @@ -0,0 +1,214 @@ +#!/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. +# + +import errno, os, time +from brokertest import * +from qpid import compat, session +from qpid.util import connect +from qpid.connection import Connection +from qpid.datatypes import Message, uuid4 +from qpid.queue import Empty + +class StoreTests(BrokerTest): + + XA_RBROLLBACK = 1 + XA_RBTIMEOUT = 2 + XA_OK = 0 + tx_counter = 0 + + def configure(self, config): + self.config = config + self.defines = self.config.defines + BrokerTest.configure(self, config) + + def setup_connection(self): + socket = connect(self._broker.host(), self._broker.port()) + return Connection(sock=socket) + + def setup_session(self): + self.conn.start() + return self.conn.session(str(uuid4())) + + def start_session(self): + self.conn = self.setup_connection() + self.ssn = self.setup_session() + + def setUp(self): + BrokerTest.setUp(self) + self._broker = self.broker() + self.start_session() + + def cycle_broker(self): + # tearDown resets working dir; change it back after. + d = os.getcwd() + BrokerTest.tearDown(self) + os.chdir(d) + self._broker = None + self._broker = self.broker() + self.conn = self.setup_connection() + self.ssn = self.setup_session() + + def xid(self, txid): + StoreTests.tx_counter += 1 + branchqual = "v%s" % StoreTests.tx_counter + return self.ssn.xid(format=0, global_id=txid, branch_id=branchqual) + + def testDurableExchange(self): + try: + self.ssn.exchange_delete(exchange="DE1") + except: + # restart the session busted from the exception + self.start_session() + + self.ssn.exchange_declare(exchange="DE1", type="direct", durable=True) + response = self.ssn.exchange_query(name="DE1") + self.assert_(response.durable) + self.assert_(not response.not_found) + + # Cycle the broker and make sure the exchange recovers + self.cycle_broker() + response = self.ssn.exchange_query(name="DE1") + self.assert_(response.durable) + self.assert_(not response.not_found) + + self.ssn.exchange_delete(exchange="DE1") + + def testDurableQueues(self): + try: + self.ssn.queue_delete(queue="DQ1") + except: + self.start_session() + + self.ssn.queue_declare(queue="DQ1", durable=True) + response = self.ssn.queue_query(queue="DQ1") + self.assertEqual("DQ1", response.queue) + self.assert_(response.durable) + + # Cycle the broker and make sure the queue recovers + self.cycle_broker() + response = self.ssn.queue_query(queue="DQ1") + self.assertEqual("DQ1", response.queue) + self.assert_(response.durable) + + self.ssn.queue_delete(queue="DQ1") + + def testDurableBindings(self): + try: + self.ssn.exchange_unbind(queue="DB_Q1", exchange="DB_E1", binding_key="K1") + except: + self.start_session() + try: + self.ssn.exchange_delete(exchange="DB_E1") + except: + self.start_session() + try: + self.ssn.queue_delete(queue="DB_Q1") + except: + self.start_session() + + self.ssn.queue_declare(queue="DB_Q1", durable=True) + self.ssn.exchange_declare(exchange="DB_E1", type="direct", durable=True) + self.ssn.exchange_bind(exchange="DB_E1", queue="DB_Q1", binding_key="K1") + + # Queue up 2 messages, one with non-zero body, one with zero-length. + # 2 = delivery_mode.persistent + dp = self.ssn.delivery_properties(routing_key="DB_Q1", delivery_mode=2) + self.ssn.message_transfer(message=Message(dp, "normal message")) + self.ssn.message_transfer(message=Message(dp, "")) + + # Cycle the broker and make sure the binding recovers + self.cycle_broker() + response = self.ssn.exchange_bound(exchange="DB_E1", queue="DB_Q1", binding_key="K1") + self.assert_(not response.exchange_not_found) + self.assert_(not response.queue_not_found) + self.assert_(not response.queue_not_matched) + self.assert_(not response.key_not_matched) + + # Are the messages still there? + self.ssn.message_subscribe(destination="msgs", queue="DB_Q1", accept_mode=1, acquire_mode=0) + self.ssn.message_flow(unit = 1, value = 0xFFFFFFFFL, destination = "msgs") + self.ssn.message_flow(unit = 0, value = 10, destination = "msgs") + message_arrivals = self.ssn.incoming("msgs") + try: + message_arrivals.get(timeout=1) + message_arrivals.get(timeout=1) + except Empty: + assert False, 'Durable message(s) not recovered' + + self.ssn.exchange_unbind(queue="DB_Q1", exchange="DB_E1", binding_key="K1") + self.ssn.exchange_delete(exchange="DB_E1") + self.ssn.queue_delete(queue="DB_Q1") + + def testDtxRecoverPrepared(self): + try: + self.ssn.exchange_unbind(queue="Dtx_Q", exchange="Dtx_E", binding_key="Dtx") + except: + self.start_session() + try: + self.ssn.exchange_delete(exchange="Dtx_E") + except: + self.start_session() + try: + self.ssn.queue_delete(queue="Dtx_Q") + except: + self.start_session() + + self.ssn.queue_declare(queue="Dtx_Q", auto_delete=False, durable=True) + self.ssn.exchange_declare(exchange="Dtx_E", type="direct", durable=True) + self.ssn.exchange_bind(exchange="Dtx_E", queue="Dtx_Q", binding_key="Dtx") + txid = self.xid("DtxRecoverPrepared") + self.ssn.dtx_select() + self.ssn.dtx_start(xid=txid) + # 2 = delivery_mode.persistent + dp = self.ssn.delivery_properties(routing_key="Dtx_Q", delivery_mode=2) + self.ssn.message_transfer(message=Message(dp, "transactional message")) + self.ssn.dtx_end(xid=txid) + self.assertEqual(self.XA_OK, self.ssn.dtx_prepare(xid=txid).status) + # Cycle the broker and make sure the xid is there, the message is not + # queued. + self.cycle_broker() + # The txid should be recovered and in doubt + xids = self.ssn.dtx_recover().in_doubt + xid_matched = False + for x in xids: + self.assertEqual(txid.format, x.format) + self.assertEqual(txid.global_id, x.global_id) + self.assertEqual(txid.branch_id, x.branch_id) + xid_matched = True + self.assert_(xid_matched) + self.ssn.message_subscribe(destination="dtx_msgs", queue="Dtx_Q", accept_mode=1, acquire_mode=0) + self.ssn.message_flow(unit = 1, value = 0xFFFFFFFFL, destination = "dtx_msgs") + self.ssn.message_flow(unit = 0, value = 10, destination = "dtx_msgs") + message_arrivals = self.ssn.incoming("dtx_msgs") + try: + message_arrivals.get(timeout=1) + assert False, 'Message present in queue before commit' + except Empty: pass + self.ssn.dtx_select() + self.assertEqual(self.XA_OK, self.ssn.dtx_commit(xid=txid, one_phase=False).status) + try: + msg = message_arrivals.get(timeout=1) + self.assertEqual("transactional message", msg.body) + except Empty: + assert False, 'Message should be present after dtx commit but is not' + + self.ssn.exchange_unbind(queue="Dtx_Q", exchange="Dtx_E", binding_key="Dtx") + self.ssn.exchange_delete(exchange="Dtx_E") + self.ssn.queue_delete(queue="Dtx_Q") diff --git a/qpid/cpp/src/tests/swig_python_tests b/qpid/cpp/src/tests/swig_python_tests new file mode 100755 index 0000000000..40c35ac0fa --- /dev/null +++ b/qpid/cpp/src/tests/swig_python_tests @@ -0,0 +1,66 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Run the python tests. +source $QPID_TEST_COMMON + +ensure_python_tests + +trap stop_broker INT TERM QUIT + +if [[ -a $AMQP_LIB ]] ; then + echo "Found AMQP support: $AMQP_LIB" + MODULES="--load-module $AMQP_LIB" +fi + +fail() { + echo "FAIL swigged python tests: $1"; exit 1; +} +skip() { + echo "SKIPPED swigged python tests: $1"; exit 0; +} + +start_broker() { + rm -f swig_python_tests.log + QPID_PORT=$($QPIDD_EXEC --daemon --port 0 --interface 127.0.0.1 --no-data-dir $MODULES --auth no --log-to-file swig_python_tests.log) || fail "Could not start broker" +} + +stop_broker() { + $QPIDD_EXEC -q --port $QPID_PORT +} + +test -f $PYTHONSWIGMODULE || skip "no swigged python client" +test -d $QPID_TESTS || skip "test code not found" + +start_broker +echo "Running swigged python tests using broker on port $QPID_PORT" + +export PYTHONPATH=$PYTHONPATH:$PYTHONPATH_SWIG +export QPID_USE_SWIG_CLIENT=1 +$QPID_PYTHON_TEST -m qpid.tests.messaging.message -m qpid_tests.broker_0_10.priority -m qpid_tests.broker_0_10.lvq -m qpid_tests.broker_0_10.new_api -b localhost:$QPID_PORT -I $srcdir/failing-amqp0-10-python-tests $* || FAILED=1 +if [[ -a $AMQP_LIB ]] ; then + $QPID_PYTHON_TEST --define="protocol_version=amqp1.0" -m qpid_tests.broker_1_0 -m qpid_tests.broker_0_10.new_api -m assertions -m reject_release -m misc -m policies -b localhost:$QPID_PORT -I $srcdir/failing-amqp1.0-python-tests $* || FAILED=1 +fi +stop_broker +if [[ $FAILED -eq 1 ]]; then + fail "" +fi + diff --git a/qpid/cpp/src/tests/test.xquery b/qpid/cpp/src/tests/test.xquery new file mode 100644 index 0000000000..4cfe3af02d --- /dev/null +++ b/qpid/cpp/src/tests/test.xquery @@ -0,0 +1,6 @@ + let $w := ./weather + return $w/station = 'Raleigh-Durham International Airport (KRDU)' + and $w/temperature_f > 50 + and $w/temperature_f - $w/dewpoint > 5 + and $w/wind_speed_mph > 7 + and $w/wind_speed_mph < 20 diff --git a/qpid/cpp/src/tests/test_env.ps1.in b/qpid/cpp/src/tests/test_env.ps1.in new file mode 100644 index 0000000000..94834a4b5e --- /dev/null +++ b/qpid/cpp/src/tests/test_env.ps1.in @@ -0,0 +1,77 @@ +# +# 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. +# + +# Environment variables substituted by configure/cmake. +$abs_srcdir="@abs_srcdir@" +$abs_builddir="@abs_builddir@" +$top_srcdir="@abs_top_srcdir@" +$top_builddir="@abs_top_builddir@" +$moduledir="$top_builddir\src@builddir_lib_suffix@" +$testmoduledir="$builddir@builddir_lib_suffix@" +$BOOST_LIBRARYDIR="@BOOST_LIBRARYDIR@" + +# Python paths and directories +$PYTHON_EXE="@PYTHON_EXECUTABLE@" +$PYTHON_DIR="$builddir\python" +$QPID_PYTHON_TEST="$PYTHON_DIR\commands\qpid-python-test" +if ( !(Test-Path "$PYTHON_DIR") -and (Test-Path "$top_srcdir\..\python")) { + $PYTHON_DIR="$top_srcdir\..\python" + $QPID_PYTHON_TEST="$PYTHON_DIR\qpid-python-test" +} +$QPID_TESTS="$top_srcdir\..\tests" +$QPID_TESTS_PY="$QPID_TESTS\src\py" +$QPID_TOOLS="$top_srcdir\..\tools" +$QPID_TOOLS_LIBS="$QPID_TOOLS\src\py" +$QMF_LIB="$top_srcdir\..\extras\qmf\src\py" +$PYTHON_COMMANDS="$QPID_TOOLS\src\py" +$env:PYTHONPATH="$srcdir;$PYTHON_DIR;$PYTHON_COMMANDS;$QPID_TESTS_PY;$QPID_TOOLS_LIBS;$QMF_LIB;$env:PYTHONPATH" +$QPID_CONFIG_EXEC="$PYTHON_COMMANDS\qpid-config" +$QPID_ROUTE_EXEC="$PYTHON_COMMANDS\qpid-route" +$QPID_HA_TOOL_EXEC="$PYTHON_COMMANDS\qpid-ha-tool" + +# Executables +$env:QPIDD_EXEC="$top_builddir\src\@CMAKE_BUILD_TYPE@\qpidd.exe" +$env:QPID_WATCHDOG_EXEC="$top_builddir\src\qpidd_watchdog" + +# Test executables +$QPID_TEST_EXEC_DIR="$builddir\@CMAKE_BUILD_TYPE@" +$RECEIVER_EXEC="$QPID_TEST_EXEC_DIR\receiver" +$SENDER_EXEC="$QPID_TEST_EXEC_DIR\sender" + +# Path +$env:PATH="$top_builddir\src\@CMAKE_BUILD_TYPE@;$builddir\@CMAKE_BUILD_TYPE@;$srcdir;$PYTHON_COMMANDS;$QPID_TEST_EXEC_DIR;@BOOST_LIBRARYDIR@;$env:PATH" + +# Modules +$env:TEST_STORE_LIB="$testmoduledir\test_store.so" + +#exportmodule() { test -f $moduledir/$2 && eval "export $1=$moduledir/$2"; } +#exportmodule ACL_LIB acl.so +#exportmodule CLUSTER_LIB cluster.so +#exportmodule SSLCONNECTOR_LIB sslconnector.so +#exportmodule SSL_LIB ssl.so +#exportmodule WATCHDOG_LIB watchdog.so +#exportmodule XML_LIB xml.so + +# Qpid options +$env:QPID_NO_MODULE_DIR="1" # Don't accidentally load installed modules +$env:QPID_DATA_DIR= # Default to no data dir, not ~/.qpidd + +# Options for boost test framework +$env:BOOST_TEST_SHOW_PROGRESS="yes" +$env:BOOST_TEST_CATCH_SYSTEM_ERRORS="no" diff --git a/qpid/cpp/src/tests/test_env.sh.in b/qpid/cpp/src/tests/test_env.sh.in new file mode 100644 index 0000000000..1c4c117e4b --- /dev/null +++ b/qpid/cpp/src/tests/test_env.sh.in @@ -0,0 +1,100 @@ +# +# 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. +# + +absdir() { echo `cd $1 && pwd`; } + +# Environment variables substituted by cmake. +export srcdir=`absdir @abs_srcdir@` +export builddir=`absdir @abs_builddir@` +export top_srcdir=`absdir @abs_top_srcdir@` +export top_builddir=`absdir @abs_top_builddir@` +export moduledir=$top_builddir/src@builddir_lib_suffix@ +export pythonswigdir=$top_builddir/bindings/qpid/python/ +export pythonswiglibdir=$top_builddir/bindings/qpid/python@builddir_lib_suffix@ +export testmoduledir=$builddir@builddir_lib_suffix@ +export QPID_INSTALL_PREFIX=@prefix@ + +# Tools substituted by cmake +enable_valgrind=${enable_valgrind-@ENABLE_VALGRIND@} +if [ "$enable_valgrind" = "ON" ] ; then + export VALGRIND=@VALGRIND_EXECUTABLE@ +fi +export SASL_PW=@SASLPASSWD2_EXECUTABLE@ +export PYTHON=@PYTHON_EXECUTABLE@ + +# Python paths and directories +export PYTHON_DIR=$builddir/python +export QPID_PYTHON_TEST=$PYTHON_DIR/commands/qpid-python-test +if [ ! -d $PYTHON_DIR -a -d $top_srcdir/../python ]; then + export PYTHON_DIR=$top_srcdir/../python + export QPID_PYTHON_TEST=$PYTHON_DIR/qpid-python-test +fi +export QPID_TESTS=$top_srcdir/../tests +export QPID_TESTS_PY=$QPID_TESTS/src/py +export QPID_TOOLS=$top_srcdir/../tools +export QMF_LIB=$top_srcdir/../extras/qmf/src/py +export PYTHON_COMMANDS=$QPID_TOOLS/src/py +export PYTHONPATH_SWIG=$pythonswigdir:$pythonswiglibdir +export PYTHONPATH=$srcdir:$PYTHON_DIR:$PYTHON_COMMANDS:$QPID_TESTS_PY:$QMF_LIB:$PYTHONPATH_SWIG:$PYTHONPATH +export QPID_CONFIG_EXEC=$PYTHON_COMMANDS/qpid-config +export QPID_ROUTE_EXEC=$PYTHON_COMMANDS/qpid-route +export QPID_HA_EXEC=$PYTHON_COMMANDS/qpid-ha +export PYTHONSWIGMODULE=$pythonswigdir/qpid_messaging.py +# Executables +export QPIDD_EXEC=$top_builddir/src/qpidd + +# Test executables +export QPID_TEST_EXEC_DIR=$builddir +export QPID_TEST_SRC_DIR=$srcdir +export RECEIVER_EXEC=$QPID_TEST_EXEC_DIR/receiver +export SENDER_EXEC=$QPID_TEST_EXEC_DIR/sender + +# Path +export PATH=$top_builddir/src:$builddir:$srcdir:$PYTHON_COMMANDS:$QPID_TEST_EXEC_DIR:$PYTHON_DIR/commands:$PATH + +# Modules +export TEST_STORE_LIB=$testmoduledir/test_store.so + +exportmodule() { test -f $moduledir/$2 && eval "export $1=$moduledir/$2"; } +exportmodule HA_LIB ha.so +exportmodule XML_LIB xml.so +test "$STORE_LIB" || exportmodule STORE_LIB linearstore.so +test "$STORE_LIB" || exportmodule STORE_LIB legacystore.so +exportmodule AMQP_LIB amqp.so + +# Qpid options +export QPID_NO_MODULE_DIR=1 # Don't accidentally load installed modules +export QPID_DATA_DIR= +export QPID_CONFIG=$srcdir/qpidd-empty.conf + +# Use temporary directory if $HOME does not exist +if [ ! -e "$HOME" ]; then + export QPID_DATA_DIR=/tmp/qpid + export QPID_PID_DIR=/tmp/qpid +fi + +# Options for boost test framework +test -z "$BOOST_TEST_SHOW_PROGRESS" && export BOOST_TEST_SHOW_PROGRESS=yes +test -z "$BOOST_TEST_CATCH_SYSTEM_ERRORS" && export BOOST_TEST_CATCH_SYSTEM_ERRORS=no + +# Source this for useful common testing functions +export QPID_TEST_COMMON=$srcdir/test_env_common.sh + +# Proton configuration +export QPID_PROTON_VERSION=@Proton_VERSION@ diff --git a/qpid/cpp/src/tests/test_env_common.sh b/qpid/cpp/src/tests/test_env_common.sh new file mode 100644 index 0000000000..348664ca76 --- /dev/null +++ b/qpid/cpp/src/tests/test_env_common.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Ensure that we have python testing tools available +function ensure_python_tests { + if [ ! -d ${PYTHON_DIR} ] ; then + echo "Python test code not found: skipping python based test" + exit 0; + fi +} + diff --git a/qpid/cpp/src/tests/test_store.cpp b/qpid/cpp/src/tests/test_store.cpp new file mode 100644 index 0000000000..14aee7b648 --- /dev/null +++ b/qpid/cpp/src/tests/test_store.cpp @@ -0,0 +1,339 @@ +/* + * + * 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. + * + */ + + +/**@file + * + * Message store for tests, with two roles: + * + * 1. Dump store events to a text file that can be compared to expected event + * sequence + * + * 2. Emulate hard-to-recreate conditions such as asynchronous completion delays + * or store errors. + * + * Messages with specially formatted contents trigger various actions. + * See class Action below for available actions and message format.. + * + */ + +#include "qpid/broker/NullMessageStore.h" +#include "qpid/broker/Broker.h" +#include "qpid/broker/amqp_0_10/MessageTransfer.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/Thread.h" +#include "qpid/Plugin.h" +#include "qpid/Options.h" +#include "qpid/RefCounted.h" +#include "qpid/Msg.h" +#include <boost/cast.hpp> +#include <boost/lexical_cast.hpp> +#include <memory> +#include <ostream> +#include <fstream> +#include <sstream> + +using namespace std; +using namespace boost; +using namespace qpid; +using namespace qpid::broker; +using namespace qpid::sys; + +namespace qpid { +namespace tests { + +namespace { + +bool startswith(const string& s, const string& prefix) { + return s.compare(0, prefix.size(), prefix) == 0; +} + +void split(const string& s, vector<string>& result, const char* sep=" \t\n") { + size_t i = s.find_first_not_of(sep); + while (i != string::npos) { + size_t j = s.find_first_of(sep, i); + if (j == string::npos) { + result.push_back(s.substr(i)); + break; + } + result.push_back(s.substr(i, j-i)); + i = s.find_first_not_of(sep, j); + } +} + +} + +/** + * Action message format is TEST_STORE_DO [<name>...]:<action> [<args>...] + * + * A list of store <name> can be included so the action only executes on one of + * the named stores. This is useful in a cluster setting where the same message + * is replicated to all broker's stores but should only trigger an action on + * specific ones. If no <name> is given, execute on any store. + * + */ +class Action { + public: + /** Available actions */ + enum ActionEnum { + NONE, + THROW, ///< Throw an exception from enqueue + DELAY, ///< Delay completion, takes an ID string to complete. + COMPLETE, ///< Complete a previously delayed message, takes ID + + N_ACTIONS // Count of actions, must be last + }; + + string name; + ActionEnum index; + vector<string> storeNames, args; + + Action(const string& s) { + index = NONE; + if (!startswith(s, PREFIX)) return; + size_t colon = s.find_first_of(":"); + if (colon == string::npos) return; + assert(colon >= PREFIX.size()); + split(s.substr(PREFIX.size(), colon-PREFIX.size()), storeNames); + split(s.substr(colon+1), args); + if (args.empty()) return; + for (size_t i = 0; i < N_ACTIONS; ++i) { + if (args[0] == ACTION_NAMES[i]) { + name = args[0]; + index = ActionEnum(i); + args.erase(args.begin()); + break; + } + } + } + + bool executeIn(const string& storeName) { + return storeNames.empty() || + find(storeNames.begin(), storeNames.end(), storeName) !=storeNames.end(); + } + + private: + static string PREFIX; + static const char* ACTION_NAMES[N_ACTIONS]; +}; + +string Action::PREFIX("TEST_STORE_DO"); + +const char* Action::ACTION_NAMES[] = { "none", "throw", "delay", "complete" }; + + +struct TestStoreOptions : public Options { + + string name; + string dump; + string events; + + TestStoreOptions() : Options("Test Store Options") { + addOptions() + ("test-store-name", optValue(name, "NAME"), + "Name of test store instance.") + ("test-store-dump", optValue(dump, "FILE"), + "File to dump enqueued messages.") + ("test-store-events", optValue(events, "FILE"), + "File to log events, 1 line per event.") + ; + } +}; + + +class TestStore : public NullMessageStore { + public: + TestStore(const TestStoreOptions& opts, Broker& broker_) + : options(opts), name(opts.name), broker(broker_) + { + QPID_LOG(info, "TestStore name=" << name + << " dump=" << options.dump + << " events=" << options.events) + + if (!options.dump.empty()) + dump.reset(new ofstream(options.dump.c_str())); + if (!options.events.empty()) + events.reset(new ofstream(options.events.c_str())); + } + + ~TestStore() { + for_each(threads.begin(), threads.end(), boost::bind(&Thread::join, _1)); + } + + // Dummy transaction context. + struct TxContext : public TPCTransactionContext { + static int nextId; + string id; + TxContext() : id(lexical_cast<string>(nextId++)) {} + TxContext(string xid) : id(xid) {} + }; + + static string getId(const TransactionContext& tx) { + const TxContext* tc = dynamic_cast<const TxContext*>(&tx); + assert(tc); + return tc->id; + } + + + bool isNull() const { return false; } + + void log(const string& msg) { + QPID_LOG(info, "test_store: " << msg); + if (events.get()) *events << msg << endl << std::flush; + } + + auto_ptr<TransactionContext> begin() { + auto_ptr<TxContext> tx(new TxContext()); + log(Msg() << "<begin tx " << tx->id << ">"); + return auto_ptr<TransactionContext>(tx); + } + + auto_ptr<TPCTransactionContext> begin(const std::string& xid) { + auto_ptr<TxContext> tx(new TxContext(xid)); + log(Msg() << "<begin tx " << tx->id << ">"); + return auto_ptr<TPCTransactionContext>(tx); + } + + string getContent(const intrusive_ptr<PersistableMessage>& msg) { + intrusive_ptr<broker::Message::Encoding> enc( + dynamic_pointer_cast<broker::Message::Encoding>(msg)); + return enc->getContent(); + } + + void enqueue(TransactionContext* tx, + const boost::intrusive_ptr<PersistableMessage>& pmsg, + const PersistableQueue& queue) + { + ostringstream o; + string data = getContent(pmsg); + o << "<enqueue " << queue.getName() << " " << data; + if (tx) o << " tx=" << getId(*tx); + o << ">"; + log(o.str()); + + // Dump the message if there is a dump file. + if (dump.get()) { + *dump << "Message(" << data.size() << "): " << data << endl; + } + string logPrefix = "TestStore "+name+": "; + Action action(data); + bool doComplete = true; + if (action.index && action.executeIn(name)) { + switch (action.index) { + + case Action::THROW: + throw Exception(logPrefix + data); + break; + + case Action::DELAY: { + if (action.args.empty()) { + QPID_LOG(error, logPrefix << "async-id needs argument: " << data); + break; + } + asyncIds[action.args[0]] = pmsg; + QPID_LOG(debug, logPrefix << "delayed completion " << action.args[0]); + doComplete = false; + break; + } + + case Action::COMPLETE: { + if (action.args.empty()) { + QPID_LOG(error, logPrefix << "complete-id needs argument: " << data); + break; + } + AsyncIds::iterator i = asyncIds.find(action.args[0]); + if (i != asyncIds.end()) { + i->second->enqueueComplete(); + QPID_LOG(debug, logPrefix << "completed " << action.args[0]); + asyncIds.erase(i); + } else { + QPID_LOG(info, logPrefix << "not found for completion " << action.args[0]); + } + break; + } + + default: + QPID_LOG(error, logPrefix << "unknown action: " << data); + } + } + if (doComplete) pmsg->enqueueComplete(); + } + + void dequeue(TransactionContext* tx, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& queue) + { + QPID_LOG(debug, "TestStore dequeue " << queue.getName()); + ostringstream o; + o<< "<dequeue " << queue.getName() << " " << getContent(msg); + if (tx) o << " tx=" << getId(*tx); + o << ">"; + log(o.str()); + } + + void prepare(TPCTransactionContext& txn) { + log(Msg() << "<prepare tx=" << getId(txn) << ">"); + } + + void commit(TransactionContext& txn) { + log(Msg() << "<commit tx=" << getId(txn) << ">"); + } + + void abort(TransactionContext& txn) { + log(Msg() << "<abort tx=" << getId(txn) << ">"); + } + + + private: + typedef map<string, boost::intrusive_ptr<PersistableMessage> > AsyncIds; + + TestStoreOptions options; + string name; + Broker& broker; + vector<Thread> threads; + std::auto_ptr<ofstream> dump; + std::auto_ptr<ofstream> events; + AsyncIds asyncIds; +}; + +int TestStore::TxContext::nextId(1); + +struct TestStorePlugin : public Plugin { + + TestStoreOptions options; + + Options* getOptions() { return &options; } + + void earlyInitialize (Plugin::Target& target) + { + Broker* broker = dynamic_cast<Broker*>(&target); + if (!broker) return; + boost::shared_ptr<MessageStore> p(new TestStore(options, *broker)); + broker->setStore (p); + } + + void initialize(qpid::Plugin::Target&) {} +}; + +static TestStorePlugin pluginInstance; + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/test_tools.h b/qpid/cpp/src/tests/test_tools.h new file mode 100644 index 0000000000..d006246299 --- /dev/null +++ b/qpid/cpp/src/tests/test_tools.h @@ -0,0 +1,106 @@ +#ifndef TEST_TOOLS_H +#define TEST_TOOLS_H + +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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. + * + */ +#include "qpid/log/Logger.h" + +#include <limits.h> // Include before boost/test headers. +#include <boost/test/test_tools.hpp> +#include <boost/assign/list_of.hpp> +#include <vector> +#include <set> +#include <ostream> +#include <sstream> +#include <exception> +#include <stdexcept> + +// Print a sequence +template <class T> std::ostream& seqPrint(std::ostream& o, const T& seq) { + std::copy(seq.begin(), seq.end(), std::ostream_iterator<typename T::value_type>(o, " ")); + return o; +} + +// Compare sequences +template <class T, class U> +bool seqEqual(const T& a, const U& b) { + typename T::const_iterator i = a.begin(); + typename U::const_iterator j = b.begin(); + while (i != a.end() && j != b.end() && *i == *j) { ++i; ++j; } + return (i == a.end()) && (j == b.end()); +} + +// ostream and == operators so we can compare vectors and sets with +// boost::assign::list_of with BOOST_CHECK_EQUALS +namespace std { // In namespace std so boost can find them. + +template <class T> +ostream& operator<<(ostream& o, const vector<T>& v) { return seqPrint(o, v); } + +template <class T> +ostream& operator<<(ostream& o, const set<T>& v) { return seqPrint(o, v); } + +template <class T> +ostream& operator<<(ostream& o, const boost::assign_detail::generic_list<T>& l) { return seqPrint(o, l); } + +template <class T> +bool operator == (const vector<T>& a, const boost::assign_detail::generic_list<T>& b) { return seqEqual(a, b); } + +template <class T> +bool operator == (const boost::assign_detail::generic_list<T>& b, const vector<T>& a) { return seqEqual(a, b); } + +template <class T> +bool operator == (const set<T>& a, const boost::assign_detail::generic_list<T>& b) { return seqEqual(a, b); } + +template <class T> +bool operator == (const boost::assign_detail::generic_list<T>& b, const set<T>& a) { return seqEqual(a, b); } +} + +namespace qpid { +namespace tests { + +/** Check if types of two objects (as given by typeinfo::name()) match. */ +#define BOOST_CHECK_TYPEID_EQUAL(a,b) BOOST_CHECK_EQUAL(typeid(a).name(),typeid(b).name()) + +/** + * Supress all logging in a scope, restore to previous configuration in destructor. + */ +struct ScopedSuppressLogging { + typedef qpid::log::Logger Logger; + ScopedSuppressLogging(Logger& l=Logger::instance()) : logger(l), opts(l.getOptions()) { l.clear(); } + ~ScopedSuppressLogging() { logger.configure(opts); } + Logger& logger; + qpid::log::Options opts; +}; + +inline std::string getLibPath(const char* envName, const char* defaultPath = 0) { + const char* p = std::getenv(envName); + if (p != 0) + return p; + if (defaultPath == 0) { + std::ostringstream msg; + msg << "Environment variable " << envName << " not set."; + throw std::runtime_error(msg.str()); + } + return defaultPath; +} + +}} // namespace qpid::tests + +#endif /*!TEST_TOOLS_H*/ + diff --git a/qpid/cpp/src/tests/topic_perftest b/qpid/cpp/src/tests/topic_perftest new file mode 100755 index 0000000000..04e1cdcffb --- /dev/null +++ b/qpid/cpp/src/tests/topic_perftest @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +exec `dirname $0`/run_perftest 10000 --mode topic --qt 16 diff --git a/qpid/cpp/src/tests/topictest b/qpid/cpp/src/tests/topictest new file mode 100755 index 0000000000..f4c6e7187d --- /dev/null +++ b/qpid/cpp/src/tests/topictest @@ -0,0 +1,61 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Run the C++ topic test + +# Clean up old log files +rm -f subscriber_*.log + +# Defaults values +SUBSCRIBERS=10 +MESSAGES=2000 +BATCHES=10 + +while getopts "s:m:b:h:t" opt ; do + case $opt in + s) SUBSCRIBERS=$OPTARG ;; + m) MESSAGES=$OPTARG ;; + b) BATCHES=$OPTARG ;; + h) HOST=-h$OPTARG ;; + t) TRANSACTIONAL="--transactional --durable" ;; + ?) + echo "Usage: %0 [-s <subscribers>] [-m <messages.] [-b <batches>]" + exit 1 + ;; + esac +done + +subscribe() { + echo Start subscriber $1 + LOG="subscriber_$1.log" + ./qpid-topic-listener $TRANSACTIONAL > $LOG 2>&1 && rm -f $LOG +} + +publish() { + ./qpid-topic-publisher --messages $MESSAGES --batches $BATCHES --subscribers $SUBSCRIBERS $HOST $TRANSACTIONAL +} + +for ((i=$SUBSCRIBERS ; i--; )); do + subscribe $i & +done +# FIXME aconway 2007-03-27: Hack around startup race. Fix topic test. +sleep 2 +publish 2>&1 || exit 1 diff --git a/qpid/cpp/src/tests/topictest.ps1 b/qpid/cpp/src/tests/topictest.ps1 new file mode 100644 index 0000000000..f15b2d452c --- /dev/null +++ b/qpid/cpp/src/tests/topictest.ps1 @@ -0,0 +1,73 @@ +# +# 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. +# + +# Parameters with default values: s (subscribers) m (messages) b (batches) +# h (host) t (false; use transactions) +param ( + [int]$subscribers = 10, + [int]$message_count = 2000, + [int]$batches = 10, + [string]$broker, + [switch] $t # transactional +) + +# Run the C++ topic test +[string]$me = $myInvocation.InvocationName +$srcdir = Split-Path $me +#$srcdir = Split-Path $myInvocation.InvocationName + +# Clean up old log files +Get-Item subscriber_*.log | Remove-Item + +if ($t) { + $transactional = "--transactional --durable" +} + +# Find which subdir the exes are in +. $srcdir\find_prog.ps1 .\qpid-topic-listener.exe + +function subscribe { + param ([int]$num, [string]$sub) + "Start subscriber $num" + $LOG = "subscriber_$num.log" + $cmdline = ".\$sub\qpid-topic-listener $transactional > $LOG 2>&1 + if (`$LastExitCode -ne 0) { Remove-Item $LOG }" + $cmdblock = $executioncontext.invokecommand.NewScriptBlock($cmdline) + . $srcdir\background.ps1 $cmdblock +} + +function publish { + param ([string]$sub) + Invoke-Expression ".\$sub\qpid-topic-publisher --messages $message_count --batches $batches --subscribers $subscribers $host $transactional" 2>&1 +} + +if ($broker.length) { + $broker = "-h$broker" +} + +$i = $subscribers +while ($i -gt 0) { + subscribe $i $sub + $i-- +} + +# FIXME aconway 2007-03-27: Hack around startup race. Fix topic test. +Start-Sleep 2 +publish $sub +exit $LastExitCode diff --git a/qpid/cpp/src/tests/txjob.cpp b/qpid/cpp/src/tests/txjob.cpp new file mode 100644 index 0000000000..29394c3415 --- /dev/null +++ b/qpid/cpp/src/tests/txjob.cpp @@ -0,0 +1,102 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include <iostream> +#include <boost/bind.hpp> +#include <boost/ptr_container/ptr_vector.hpp> + +#include "TestOptions.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/client/FailoverManager.h" +#include "qpid/client/Message.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/sys/Thread.h" + +using namespace qpid::client; +using namespace qpid::sys; + +namespace qpid { +namespace tests { + +struct Args : public qpid::TestOptions +{ + std::string workQueue; + std::string source; + std::string dest; + uint messages; + uint jobs; + bool quit; + bool declareQueues; + + Args() : workQueue("txshift-control"), source("txshift-1"), dest("txshift-2"), messages(0), jobs(0), + quit(false), declareQueues(false) + { + addOptions() + ("messages", qpid::optValue(messages, "N"), "Number of messages to shift") + ("jobs", qpid::optValue(jobs, "N"), "Number of shift jobs to request") + ("source", qpid::optValue(source, "QUEUE NAME"), "source queue from which messages will be shifted") + ("dest", qpid::optValue(dest, "QUEUE NAME"), "dest queue to which messages will be shifted") + ("work-queue", qpid::optValue(workQueue, "QUEUE NAME"), "work queue from which to take instructions") + ("add-quit", qpid::optValue(quit), "add a 'quit' instruction to the queue (after any other jobs)") + ("declare-queues", qpid::optValue(declareQueues), "issue a declare for all queues"); + } +}; + +}} // namespace qpid::tests + +using namespace qpid::tests; + +//TODO: might be nice to make this capable of failover as well at some +//point; for now its just for the setup phase. +int main(int argc, char** argv) +{ + Args opts; + try { + opts.parse(argc, argv); + Connection connection; + connection.open(opts.con); + Session session = connection.newSession(); + if (opts.declareQueues) { + session.queueDeclare(arg::queue=opts.workQueue); + session.queueDeclare(arg::queue=opts.source); + session.queueDeclare(arg::queue=opts.dest); + } + for (uint i = 0; i < opts.jobs; ++i) { + Message job("transfer", opts.workQueue); + job.getHeaders().setString("src", opts.source); + job.getHeaders().setString("dest", opts.dest); + job.getHeaders().setInt("count", opts.messages); + async(session).messageTransfer(arg::content=job); + } + + if (opts.quit) { + async(session).messageTransfer(arg::content=Message("quit", opts.workQueue)); + } + + session.sync(); + session.close(); + + return 0; + } catch(const std::exception& e) { + std::cout << e.what() << std::endl; + return 1; + } +} diff --git a/qpid/cpp/src/tests/txshift.cpp b/qpid/cpp/src/tests/txshift.cpp new file mode 100644 index 0000000000..6ec28c7233 --- /dev/null +++ b/qpid/cpp/src/tests/txshift.cpp @@ -0,0 +1,193 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +#include <iostream> +#include <boost/bind.hpp> +#include <boost/ptr_container/ptr_vector.hpp> + +#include "TestOptions.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/client/FailoverManager.h" +#include "qpid/client/Message.h" +#include "qpid/client/SubscriptionManager.h" +#include "qpid/log/Statement.h" +#include "qpid/sys/Thread.h" + +using namespace qpid::client; +using namespace qpid::sys; + +namespace qpid { +namespace tests { + +struct Args : public qpid::TestOptions +{ + std::string workQueue; + uint workers; + + Args() : workQueue("txshift-control"), workers(1) + { + addOptions() + ("workers", qpid::optValue(workers, "N"), "Number of separate worker sessions to start") + ("work-queue", qpid::optValue(workQueue, "NAME"), "work queue from which to take instructions"); + } +}; + +struct Transfer : MessageListener +{ + std::string control; + std::string source; + std::string destination; + uint expected; + uint transfered; + SubscriptionSettings controlSettings; + Subscription controlSubscription; + SubscriptionSettings sourceSettings; + Subscription sourceSubscription; + + Transfer(const std::string control_) : control(control_), expected(0), transfered(0) {} + + void subscribeToSource(SubscriptionManager manager) + { + sourceSettings.autoAck = 0;//will accept once at the end of the batch + sourceSettings.flowControl = FlowControl::messageCredit(expected); + sourceSubscription = manager.subscribe(*this, source, sourceSettings); + QPID_LOG(info, "Subscribed to source: " << source << " expecting: " << expected); + } + + void subscribeToControl(SubscriptionManager manager) + { + controlSettings.flowControl = FlowControl::messageCredit(1); + controlSubscription = manager.subscribe(*this, control, controlSettings); + QPID_LOG(info, "Subscribed to job queue"); + } + + void received(Message& message) + { + QPID_LOG(debug, "received: " << message.getData() << " for " << message.getDestination()); + if (message.getDestination() == source) { + receivedFromSource(message); + } else if (message.getDestination() == control) { + receivedFromControl(message); + } else { + QPID_LOG(error, "Unexpected message: " << message.getData() << " to " << message.getDestination()); + } + } + + void receivedFromSource(Message& message) + { + QPID_LOG(debug, "transfering " << (transfered+1) << " of " << expected); + message.getDeliveryProperties().setRoutingKey(destination); + async(sourceSubscription.getSession()).messageTransfer(arg::content=message); + if (++transfered == expected) { + QPID_LOG(info, "completed job: " << transfered << " messages shifted from " << + source << " to " << destination); + sourceSubscription.accept(sourceSubscription.getUnaccepted()); + sourceSubscription.getSession().txCommit(); + sourceSubscription.cancel(); + //grant credit to allow broker to send us another control message + controlSubscription.grantMessageCredit(1); + } + } + + void receivedFromControl(Message& message) + { + if (message.getData() == "transfer") { + source = message.getHeaders().getAsString("src"); + destination = message.getHeaders().getAsString("dest"); + expected = message.getHeaders().getAsInt("count"); + transfered = 0; + QPID_LOG(info, "received transfer request: " << expected << " messages to be shifted from " << + source << " to " << destination); + subscribeToSource(controlSubscription.getSubscriptionManager()); + } else if (message.getData() == "quit") { + QPID_LOG(info, "received quit request"); + controlSubscription.cancel(); + } else { + std::cerr << "Rejecting invalid message: " << message.getData() << std::endl; + controlSubscription.getSession().messageReject(SequenceSet(message.getId())); + } + } + +}; + +struct Worker : FailoverManager::Command, Runnable +{ + FailoverManager& connection; + Transfer transfer; + Thread runner; + + Worker(FailoverManager& c, const std::string& controlQueue) : connection(c), transfer(controlQueue) {} + + void run() + { + connection.execute(*this); + } + + void start() + { + runner = Thread(this); + } + + void join() + { + runner.join(); + } + + void execute(AsyncSession& session, bool isRetry) + { + if (isRetry) QPID_LOG(info, "Retrying..."); + session.txSelect(); + SubscriptionManager subs(session); + transfer.subscribeToControl(subs); + subs.run(); + session.txCommit();//commit accept of control messages + } +}; + +}} // namespace qpid::tests + +using namespace qpid::tests; + +int main(int argc, char** argv) +{ + Args opts; + try { + opts.parse(argc, argv); + FailoverManager connection(opts.con); + connection.connect(); + if (opts.workers == 1) { + Worker worker(connection, opts.workQueue); + worker.run(); + } else { + boost::ptr_vector<Worker> workers; + for (uint i = 0; i < opts.workers; i++) { + workers.push_back(new Worker(connection, opts.workQueue)); + } + std::for_each(workers.begin(), workers.end(), boost::bind(&Worker::start, _1)); + std::for_each(workers.begin(), workers.end(), boost::bind(&Worker::join, _1)); + } + + return 0; + } catch(const std::exception& e) { + std::cout << e.what() << std::endl; + return 1; + } +} diff --git a/qpid/cpp/src/tests/unit_test.cpp b/qpid/cpp/src/tests/unit_test.cpp new file mode 100644 index 0000000000..00c61242e4 --- /dev/null +++ b/qpid/cpp/src/tests/unit_test.cpp @@ -0,0 +1,23 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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. + * + */ + +// Defines test_main function to link with actual unit test code. +#define BOOST_AUTO_TEST_MAIN // Boost 1.33 +#define BOOST_TEST_MAIN +#include "unit_test.h" + diff --git a/qpid/cpp/src/tests/unit_test.h b/qpid/cpp/src/tests/unit_test.h new file mode 100644 index 0000000000..a11df2ff04 --- /dev/null +++ b/qpid/cpp/src/tests/unit_test.h @@ -0,0 +1,74 @@ +#ifndef QPIPD_TEST_UNIT_TEST_H_ +#define QPIPD_TEST_UNIT_TEST_H_ + +/* + * + * 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. + * + */ + +// Workaround so we can build against boost 1.33 and boost 1.34. +// Remove when we no longer need to support 1.33. +// +#include <boost/version.hpp> +#include <limits.h> // Must be inclued beofre boost/test headers. + +// #include the correct header file. +// +#if (BOOST_VERSION < 103400) +# include <boost/test/auto_unit_test.hpp> +#else +# include <boost/test/unit_test.hpp> +#endif // BOOST_VERSION + +// Workarounds for BOOST_AUTO_TEST_CASE|SUITE|SUITE_END +// +#if (BOOST_VERSION < 103300) + +# define QPID_AUTO_TEST_SUITE(name) +# define QPID_AUTO_TEST_CASE(name) BOOST_AUTO_UNIT_TEST(name) +# define QPID_AUTO_TEST_SUITE_END() + +#elif (BOOST_VERSION < 103400) +// Note the trailing ';' +# define QPID_AUTO_TEST_SUITE(name) BOOST_AUTO_TEST_SUITE(name); +# define QPID_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END(); + +#endif // Workarounds for BOOST_AUTO_TEST_CASE|SUITE|SUITE_END + +// +// Default definitions for latest version of boost. +// + +#ifndef QPID_AUTO_TEST_SUITE +# define QPID_AUTO_TEST_SUITE(name) BOOST_AUTO_TEST_SUITE(name) +#endif + +#ifndef QPID_AUTO_TEST_CASE +# define QPID_AUTO_TEST_CASE(name) BOOST_AUTO_TEST_CASE(name) +#endif + +#ifndef QPID_FIXTURE_TEST_CASE +# define QPID_FIXTURE_TEST_CASE(name, f) BOOST_FIXTURE_TEST_CASE(name, f) +#endif + +#ifndef QPID_AUTO_TEST_SUITE_END +# define QPID_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END() +#endif + +#endif // !QPIPD_TEST_UNIT_TEST_H_ diff --git a/qpid/cpp/src/tests/vg_check b/qpid/cpp/src/tests/vg_check new file mode 100644 index 0000000000..462f4cb5e4 --- /dev/null +++ b/qpid/cpp/src/tests/vg_check @@ -0,0 +1,43 @@ +# +# 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. +# + +# Check for valgrind errors. Sourced by test scripts. + +vg_failed() { + echo "Valgrind error log in $VG_LOG." 1>&2 + cat $VG_LOG 1>&2 + echo $1 1>&2 + exit 1 +} + +vg_check() +{ + test -z "$1" || VG_LOG=$1 + test -f $VG_LOG || vg_failed Valgrind log file $VG_LOG missing. + # Ensure there is an ERROR SUMMARY line. + grep -E '^==[0-9]+== ERROR SUMMARY:' $VG_LOG > /dev/null || \ + vg_failed "No valgrind ERROR SUMMARY line in $VG_LOG." + # Ensure that the number of errors is 0. + grep -E '^==[0-9]+== ERROR SUMMARY: [^0]' $VG_LOG > /dev/null && \ + vg_failed "Valgrind reported errors in $VG_LOG; see above." + # Check for leaks. + grep -E '^==[0-9]+== +.* lost: [^0]' $VG_LOG && \ + vg_failed "Found memory leaks (see log file, $VG_LOG); see above." + true +} diff --git a/qpid/cpp/src/tests/windows/DisableWin32ErrorWindows.cpp b/qpid/cpp/src/tests/windows/DisableWin32ErrorWindows.cpp new file mode 100644 index 0000000000..14f1e46606 --- /dev/null +++ b/qpid/cpp/src/tests/windows/DisableWin32ErrorWindows.cpp @@ -0,0 +1,78 @@ +/* + * + * 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. + * + */ + +// This file intends to prevent Windows from throwing up error boxes and +// offering to debug when serious errors happen. The errors are displayed +// on stderr instead. The purpose of this is to allow the tests to proceed +// scripted and catch the text for logging. If this behavior is desired, +// include this file with the executable being built. If the default +// behaviors are desired, don't include this file in the build. + +#if defined(_MSC_VER) +#include <crtdbg.h> +#endif +#include <windows.h> +#include <iostream> + +namespace qpid { +namespace tests { +namespace windows { + +// Instead of popping up a window for exceptions, just print something out +LONG _stdcall UnhandledExceptionFilter (PEXCEPTION_POINTERS pExceptionInfo) +{ + DWORD dwExceptionCode = pExceptionInfo->ExceptionRecord->ExceptionCode; + + if (dwExceptionCode == EXCEPTION_ACCESS_VIOLATION) + std::cerr << "\nERROR: ACCESS VIOLATION\n" << std::endl; + else + std::cerr << "\nERROR: UNHANDLED EXCEPTION\n" << std::endl; + + return EXCEPTION_EXECUTE_HANDLER; +} + +struct redirect_errors_to_stderr { + redirect_errors_to_stderr (); +}; + +static redirect_errors_to_stderr block; + +redirect_errors_to_stderr::redirect_errors_to_stderr() +{ +#if defined(_MSC_VER) + _CrtSetReportMode (_CRT_WARN, _CRTDBG_MODE_FILE); + _CrtSetReportFile (_CRT_WARN, _CRTDBG_FILE_STDERR); + _CrtSetReportMode (_CRT_ERROR, _CRTDBG_MODE_FILE); + _CrtSetReportFile (_CRT_ERROR, _CRTDBG_FILE_STDERR); + _CrtSetReportMode (_CRT_ASSERT, _CRTDBG_MODE_FILE); + _CrtSetReportFile (_CRT_ASSERT, _CRTDBG_FILE_STDERR); +#endif + + // Prevent the system from displaying the critical-error-handler + // and can't-open-file message boxes. + SetErrorMode(SEM_FAILCRITICALERRORS); + SetErrorMode(SEM_NOOPENFILEERRORBOX); + + // And this will catch all unhandled exceptions. + SetUnhandledExceptionFilter (&UnhandledExceptionFilter); +} + +}}} // namespace |