diff options
Diffstat (limited to 'qpid/cpp/src/tests')
236 files changed, 38396 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..2c6a1509ff --- /dev/null +++ b/qpid/cpp/src/tests/.valgrind.supp @@ -0,0 +1,202 @@ +{ + 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 +} +{ + Benign leak in CPG - patched version. + Memcheck:Leak + fun:* + fun:openais_service_connect + fun:cpg_initialize +} + +{ + Benign error in libcpg. + Memcheck:Param + socketcall.sendmsg(msg.msg_iov[i]) + obj:*/libpthread-2.5.so + obj:*/libcpg.so.2.0.0 +} + +{ + 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 +} + +{ + CPG error - seems benign. + Memcheck:Param + socketcall.sendmsg(msg.msg_iov[i]) + obj:* + obj:*/libcpg.so.2.0.0 +} + +{ + 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/Address.cpp b/qpid/cpp/src/tests/Address.cpp new file mode 100644 index 0000000000..f41f27b6df --- /dev/null +++ b/qpid/cpp/src/tests/Address.cpp @@ -0,0 +1,124 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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_SUITE_END() + +}} diff --git a/qpid/cpp/src/tests/Array.cpp b/qpid/cpp/src/tests/Array.cpp new file mode 100644 index 0000000000..7622b89d15 --- /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; + b.collect(data2); + //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; + b.collect(data2); + //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..e32097106f --- /dev/null +++ b/qpid/cpp/src/tests/AsyncCompletion.cpp @@ -0,0 +1,120 @@ +/* + * + * 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) + +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. +} + +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..672d954572 --- /dev/null +++ b/qpid/cpp/src/tests/BrokerFixture.h @@ -0,0 +1,154 @@ +#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 "SocketProxy.h" + +#include "qpid/broker/Broker.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; + + BrokerPtr broker; + qpid::sys::Thread brokerThread; + + BrokerFixture(Broker::Options opts=Broker::Options(), bool enableMgmt=false) { + // 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.selectors.push_back("error+"); + qpid::log::Logger::instance().configure(logOpts); + } + opts.port=0; + // Management doesn't play well with multiple in-process brokers. + opts.enableMgmt=enableMgmt; + opts.workerThreads=1; + opts.dataDir=""; + opts.auth=false; + 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(); + broker->getPort(qpid::broker::Broker::TCP_TRANSPORT); + brokerThread = qpid::sys::Thread(*broker); + }; + + void shutdownBroker() + { + broker->shutdown(); + broker = BrokerPtr(); + } + + ~BrokerFixture() { + if (broker) broker->shutdown(); + brokerThread.join(); + } + + /** Open a connection to the broker. */ + void open(qpid::client::Connection& c) { + c.open("localhost", broker->getPort(qpid::broker::Broker::TCP_TRANSPORT)); + } + + uint16_t getPort() { return broker->getPort(qpid::broker::Broker::TCP_TRANSPORT); } +}; + +/** 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(); } +}; + +/** A local client connection via a socket proxy. */ +struct ProxyConnection : public qpid::client::Connection { + SocketProxy proxy; + ProxyConnection(int brokerPort) : proxy(brokerPort) { + open("localhost", proxy.getPort()); + } + ProxyConnection(const qpid::client::ConnectionSettings& s) : proxy(s.port) { + qpid::client::ConnectionSettings proxySettings(s); + proxySettings.port = proxy.getPort(); + open(proxySettings); + } + ~ProxyConnection() { 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(Broker::Options opts=Broker::Options()) : + BrokerFixture(opts), + ClientT<ConnectionType,SessionType>(broker->getPort(qpid::broker::Broker::TCP_TRANSPORT)) + {} + +}; + +typedef SessionFixtureT<LocalConnection> SessionFixture; +typedef SessionFixtureT<ProxyConnection> ProxySessionFixture; + +}} // 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..d0c6668b72 --- /dev/null +++ b/qpid/cpp/src/tests/BrokerMgmtAgent.cpp @@ -0,0 +1,792 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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/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.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* mgmtObj; + const std::string key; + public: + TestManageable(management::ManagementAgent *agent, std::string _key) + : key(_key) + { + _qmf::TestObject *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 = 0; /* deleted by agent on shutdown */ }; + management::ManagementObject* 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; + } + + + // verify that a deleted object is exported correctly using the + // exportDeletedObjects() method. V1 testcase. + // + QPID_AUTO_TEST_CASE(v1ExportDelObj) + { + AgentFixture* fix = new AgentFixture(3); + management::ManagementAgent* agent; + agent = fix->getBrokerAgent(); + + // create a manageable test object + TestManageable *tm = new TestManageable(agent, std::string("myObj")); + 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); + + // destroy the object, then immediately export (before the next poll cycle) + + ::qpid::management::ManagementAgent::DeletedObjectList delObjs; + tm->GetManagementObject()->resourceDestroy(); + agent->exportDeletedObjects( delObjs ); + BOOST_CHECK(delObjs.size() == 1); + + // 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); + + // verify there are no deleted objects to export now. + + agent->exportDeletedObjects( delObjs ); + BOOST_CHECK(delObjs.size() == 0); + + r1.close(); + delete fix; + delete tm; + } + + + // verify that a deleted object is imported correctly using the + // importDeletedObjects() method. V1 testcase. + // + QPID_AUTO_TEST_CASE(v1ImportDelObj) + { + AgentFixture* fix = new AgentFixture(3); + management::ManagementAgent* agent; + agent = fix->getBrokerAgent(); + + // create a manageable test object + TestManageable *tm = new TestManageable(agent, std::string("anObj")); + 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); + + // destroy the object, then immediately export (before the next poll cycle) + + ::qpid::management::ManagementAgent::DeletedObjectList delObjs; + tm->GetManagementObject()->resourceDestroy(); + agent->exportDeletedObjects( delObjs ); + BOOST_CHECK(delObjs.size() == 1); + + // destroy the broker, and reinistantiate a new one without populating it + // with a TestObject. + + r1.close(); + delete fix; + delete tm; // should no longer be necessary + + fix = new AgentFixture(3); + r1 = fix->createV1DataIndRcvr("org.apache.qpid.broker.mgmt.test", "#"); + agent = fix->getBrokerAgent(); + agent->importDeletedObjects( delObjs ); + + // 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); + + // verify there are no deleted objects to export now. + + agent->exportDeletedObjects( delObjs ); + BOOST_CHECK(delObjs.size() == 0); + + r1.close(); + delete fix; + } + + + // verify that an object that is added and deleted prior to the + // first poll cycle is accounted for by the export + // + QPID_AUTO_TEST_CASE(v1ExportFastDelObj) + { + AgentFixture* fix = new AgentFixture(3); + management::ManagementAgent* agent; + agent = fix->getBrokerAgent(); + + // create a manageable test object + TestManageable *tm = new TestManageable(agent, std::string("objectifyMe")); + + // add, then immediately delete and export the object... + + ::qpid::management::ManagementAgent::DeletedObjectList delObjs; + agent->addObject(tm->GetManagementObject(), 999); + tm->GetManagementObject()->resourceDestroy(); + agent->exportDeletedObjects( delObjs ); + BOOST_CHECK(delObjs.size() == 1); + + delete fix; + delete tm; + } + + + // Verify that we can export and import multiple deleted objects correctly. + // + QPID_AUTO_TEST_CASE(v1ImportMultiDelObj) + { + AgentFixture* fix = new AgentFixture(3); + management::ManagementAgent* agent; + agent = fix->getBrokerAgent(); + + Receiver r1 = fix->createV1DataIndRcvr("org.apache.qpid.broker.mgmt.test", "#"); + + // populate the agent with multiple test objects + const size_t objCount = 50; + std::vector<TestManageable *> tmv; + uint32_t objLen; + + for (size_t i = 0; i < objCount; i++) { + std::stringstream key; + key << "testobj-" << std::setfill('x') << std::setw(4) << i; + // (no, seriously, I didn't just do that.) + // Note well: we have to keep the key string length EXACTLY THE SAME + // FOR ALL OBJECTS, so objLen will be the same. Otherwise the + // decodeV1ObjectUpdates() will fail (v1 lacks explict encoded length). + TestManageable *tm = new TestManageable(agent, key.str()); + objLen = tm->GetManagementObject()->writePropertiesSize(); + agent->addObject(tm->GetManagementObject(), i + 1); + tmv.push_back(tm); + } + + // wait for the objects to be published + Message m1; + uint32_t msgCount = 0; + while(r1.fetch(m1, Duration::SECOND * 6)) { + TestObjectVector objs; + decodeV1ObjectUpdates(m1, objs, objLen); + msgCount += objs.size(); + } + + BOOST_CHECK_EQUAL(msgCount, objCount); + + // destroy some of the objects, then immediately export (before the next poll cycle) + + uint32_t delCount = 0; + for (size_t i = 0; i < objCount; i += 2) { + tmv[i]->GetManagementObject()->resourceDestroy(); + delCount++; + } + + ::qpid::management::ManagementAgent::DeletedObjectList delObjs; + agent->exportDeletedObjects( delObjs ); + BOOST_CHECK_EQUAL(delObjs.size(), delCount); + + // destroy the broker, and reinistantiate a new one without populating it + // with TestObjects. + + r1.close(); + delete fix; + while (tmv.size()) { + delete tmv.back(); + tmv.pop_back(); + } + + fix = new AgentFixture(3); + r1 = fix->createV1DataIndRcvr("org.apache.qpid.broker.mgmt.test", "#"); + agent = fix->getBrokerAgent(); + agent->importDeletedObjects( delObjs ); + + // wait for the deleted object to be published, verify the count + + uint32_t countDels = 0; + while (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); + if (mappy["_delete_ts"].asUint64() != 0) + countDels++; + } + } + + // make sure we get the correct # of deleted objects + BOOST_CHECK_EQUAL(countDels, delCount); + + // verify there are no deleted objects to export now. + + agent->exportDeletedObjects( delObjs ); + BOOST_CHECK(delObjs.size() == 0); + + r1.close(); + delete fix; + } + + // Verify that we can export and import multiple deleted objects correctly. + // QMF V2 variant + QPID_AUTO_TEST_CASE(v2ImportMultiDelObj) + { + AgentFixture* fix = new AgentFixture(3, true); + management::ManagementAgent* agent; + agent = fix->getBrokerAgent(); + + Receiver r1 = fix->createV2DataIndRcvr("org.apache.qpid.broker.mgmt.test", "#"); + + // populate the agent with multiple test objects + const size_t objCount = 50; + std::vector<TestManageable *> tmv; + uint32_t objLen; + + for (size_t i = 0; i < objCount; i++) { + std::stringstream key; + key << "testobj-" << i; + TestManageable *tm = new TestManageable(agent, key.str()); + objLen = tm->GetManagementObject()->writePropertiesSize(); + agent->addObject(tm->GetManagementObject(), key.str()); + tmv.push_back(tm); + } + + // wait for the objects to be published + Message m1; + uint32_t msgCount = 0; + while(r1.fetch(m1, Duration::SECOND * 6)) { + TestObjectVector objs; + decodeV2ObjectUpdates(m1, objs); + msgCount += objs.size(); + } + + BOOST_CHECK_EQUAL(msgCount, objCount); + + // destroy some of the objects, then immediately export (before the next poll cycle) + + uint32_t delCount = 0; + for (size_t i = 0; i < objCount; i += 2) { + tmv[i]->GetManagementObject()->resourceDestroy(); + delCount++; + } + + ::qpid::management::ManagementAgent::DeletedObjectList delObjs; + agent->exportDeletedObjects( delObjs ); + BOOST_CHECK_EQUAL(delObjs.size(), delCount); + + // destroy the broker, and reinistantiate a new one without populating it + // with TestObjects. + + r1.close(); + delete fix; + while (tmv.size()) { + delete tmv.back(); + tmv.pop_back(); + } + + fix = new AgentFixture(3, true); + r1 = fix->createV2DataIndRcvr("org.apache.qpid.broker.mgmt.test", "#"); + agent = fix->getBrokerAgent(); + agent->importDeletedObjects( delObjs ); + + // wait for the deleted object to be published, verify the count + + uint32_t countDels = 0; + while (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); + if (mappy["_delete_ts"].asUint64() != 0) + countDels++; + } + } + + // make sure we get the correct # of deleted objects + BOOST_CHECK_EQUAL(countDels, delCount); + + // verify there are no deleted objects to export now. + + agent->exportDeletedObjects( delObjs ); + BOOST_CHECK(delObjs.size() == 0); + + r1.close(); + delete fix; + } + + // 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; + } + + // See QPID-2997 + QPID_AUTO_TEST_CASE(v2DuplicateErrorObj) + { + AgentFixture* fix = new AgentFixture(3, true); + management::ManagementAgent* agent; + agent = fix->getBrokerAgent(); + + // turn off the expected error log message + qpid::log::Options logOpts; + logOpts.selectors.clear(); + logOpts.selectors.push_back("critical+"); + qpid::log::Logger::instance().configure(logOpts); + + // two objects, same ObjID + TestManageable *tm1 = new TestManageable(agent, std::string("obj2")); + TestManageable *tm2 = new TestManageable(agent, std::string("obj2")); + // Keep a pointer to the ManagementObject. This test simulates a user-caused error + // case (duplicate objects) where the broker has no choice but to leak a management + // object (safest assumption). To prevent valgrind from flagging this leak, we + // manually clean up the object at the end of the test. + management::ManagementObject *save = tm2->GetManagementObject(); + + 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"); + agent->addObject(tm2->GetManagementObject(), "testobj-1"); + + TestObjectVector objs; + 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); + } + } + + BOOST_CHECK(count == 1); // only one should be accepted. + + r1.close(); + delete fix; + delete tm1; + delete tm2; + delete save; + } + + 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/CMakeLists.txt b/qpid/cpp/src/tests/CMakeLists.txt new file mode 100644 index 0000000000..405718f12b --- /dev/null +++ b/qpid/cpp/src/tests/CMakeLists.txt @@ -0,0 +1,356 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Enable dashboard reporting. +include (CTest) + +# Make sure that everything get built before the tests +# Need to create a var with all the necessary top level targets + +add_definitions(-DBOOST_TEST_DYN_LINK) +include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ) + +include (FindPythonInterp) + +# 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 "") +configure_file (${CMAKE_CURRENT_SOURCE_DIR}/test_env.sh.in + ${CMAKE_CURRENT_BINARY_DIR}/test_env.sh) + + +# If valgrind is selected in the configuration step, set up the path to it +# for CTest. +if (ENABLE_VALGRIND) + set (MEMORYCHECK_COMMAND ${VALGRIND}) + set (MEMORYCHECK_COMMAND_OPTIONS "--gen-suppressions=all +--leak-check=full +--demangle=yes +--suppressions=${CMAKE_CURRENT_SOURCE_DIR}/.valgrind.supp +--num-callers=25 +--log-file=ctest_valgrind.vglog") +endif (ENABLE_VALGRIND) + +# 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) + +# Like this to work with cmake 2.4 on Unix +set (qpid_test_boost_libs + ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} ${Boost_SYSTEM_LIBRARY}) + +# 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) + +# 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) + +# +# 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. + +set(unit_tests_to_build + exception_test + RefCounted + SessionState + logging + AsyncCompletion + Url + Uuid + Shlib + FieldValue + FieldTable + Array + QueueOptionsTest + InlineAllocator + InlineVector + ClientSessionTest + MessagingSessionTests + SequenceSet + StringUtils + RangeSet + AtomicValue + QueueTest + AccumulatedAckTest + DtxWorkRecordTest + DeliveryRecordTest + ExchangeTest + HeadersExchangeTest + MessageTest + QueueRegistryTest + QueuePolicyTest + QueueFlowLimitTest + FramingTest + HeaderTest + SequenceNumberTest + TimerTest + TopicExchangeTest + TxBufferTest + TxPublishTest + MessageBuilderTest + ManagementTest + MessageReplayTracker + ConsoleTest + QueueEvents + ProxyTest + RetryList + RateFlowcontrolTest + FrameDecoder + ReplicationTest + ClientMessageTest + PollableCondition + Variant + ClientMessage + ${xml_tests} + CACHE STRING "Which unit tests to build" + ) + +mark_as_advanced(unit_tests_to_build) + +# Disabled till we move to amqp_0_10 codec. +# amqp_0_10/serialize.cpp allSegmentTypes.h \ +# amqp_0_10/ProxyTemplate.cpp \ +# amqp_0_10/apply.cpp \ +# amqp_0_10/Map.cpp \ +# amqp_0_10/handlers.cpp + +add_executable (unit_test unit_test + ${unit_tests_to_build} ${platform_test_additions}) +target_link_libraries (unit_test + ${qpid_test_boost_libs} + qpidmessaging qpidbroker qmfconsole) +remember_location(unit_test) + +add_library (shlibtest MODULE shlibtest.cpp) + +if (BUILD_CLUSTER) + include (cluster.cmake) +endif (BUILD_CLUSTER) + +# FIXME aconway 2009-11-30: enable SSL +#if SSL +#include ssl.mk +#endif + +# +# Other test programs +# +add_executable (qpid-perftest qpid-perftest.cpp ${platform_test_additions}) +target_link_libraries (qpid-perftest qpidclient) +#qpid_perftest_SOURCES=qpid-perftest.cpp test_tools.h TestOptions.h ConnectionOptions.h +remember_location(qpid-perftest) + +add_executable (qpid-txtest qpid-txtest.cpp ${platform_test_additions}) +target_link_libraries (qpid-txtest qpidclient) +#qpid_txtest_SOURCES=qpid-txtest.cpp TestOptions.h ConnectionOptions.h +remember_location(qpid-txtest) + +add_executable (qpid-latency-test qpid-latency-test.cpp ${platform_test_additions}) +target_link_libraries (qpid-latency-test qpidclient) +#qpid_latencytest_SOURCES=qpid-latency-test.cpp TestOptions.h ConnectionOptions.h +remember_location(qpid-latency-test) + +add_executable (echotest echotest.cpp ${platform_test_additions}) +target_link_libraries (echotest qpidclient) +#echotest_SOURCES=echotest.cpp TestOptions.h ConnectionOptions.h +remember_location(echotest) + +add_executable (qpid-client-test qpid-client-test.cpp ${platform_test_additions}) +target_link_libraries (qpid-client-test qpidclient) +#qpid_client_test_SOURCES=qpid-client-test.cpp TestOptions.h ConnectionOptions.h +remember_location(qpid-client-test) + +add_executable (qpid-topic-listener qpid-topic-listener.cpp ${platform_test_additions}) +target_link_libraries (qpid-topic-listener qpidclient) +#qpid_topic_listener_SOURCES=qpid-topic-listener.cpp TestOptions.h ConnectionOptions.h +remember_location(qpid-topic-listener) + +add_executable (qpid-topic-publisher qpid-topic-publisher.cpp ${platform_test_additions}) +target_link_libraries (qpid-topic-publisher qpidclient) +#qpid_topic_publisher_SOURCES=qpid-topic-publisher.cpp TestOptions.h ConnectionOptions.h +remember_location(qpid-topic-publisher) + +add_executable (publish publish.cpp ${platform_test_additions}) +target_link_libraries (publish qpidclient) +#publish_SOURCES=publish.cpp TestOptions.h ConnectionOptions.h +remember_location(publish) + +add_executable (consume consume.cpp ${platform_test_additions}) +target_link_libraries (consume qpidclient) +#consume_SOURCES=consume.cpp TestOptions.h ConnectionOptions.h +remember_location(consume) + +add_executable (header_test header_test.cpp ${platform_test_additions}) +target_link_libraries (header_test qpidclient) +#header_test_SOURCES=header_test.cpp TestOptions.h ConnectionOptions.h +remember_location(header_test) + +add_executable (declare_queues declare_queues.cpp ${platform_test_additions}) +target_link_libraries (declare_queues qpidclient) +remember_location(declare_queues) + +add_executable (replaying_sender replaying_sender.cpp ${platform_test_additions}) +target_link_libraries (replaying_sender qpidclient) +remember_location(replaying_sender) + +add_executable (resuming_receiver resuming_receiver.cpp ${platform_test_additions}) +target_link_libraries (resuming_receiver qpidclient) +remember_location(resuming_receiver) + +add_executable (txshift txshift.cpp ${platform_test_additions}) +target_link_libraries (txshift qpidclient) +#txshift_SOURCES=txshift.cpp TestOptions.h ConnectionOptions.h +remember_location(txshift) + +add_executable (txjob txjob.cpp ${platform_test_additions}) +target_link_libraries (txjob qpidclient) +#txjob_SOURCES=txjob.cpp TestOptions.h ConnectionOptions.h +remember_location(txjob) + +add_executable (receiver receiver.cpp ${platform_test_additions}) +target_link_libraries (receiver qpidclient) +#receiver_SOURCES=receiver.cpp TestOptions.h ConnectionOptions.h +remember_location(receiver) + +add_executable (sender sender.cpp Statistics.cpp ${platform_test_additions}) +target_link_libraries (sender qpidmessaging) +#sender_SOURCES=sender.cpp TestOptions.h ConnectionOptions.h +remember_location(sender) + +add_executable (qpid-receive qpid-receive.cpp Statistics.cpp ${platform_test_additions}) +target_link_libraries (qpid-receive qpidmessaging) +remember_location(qpid-receive) + +add_executable (qpid-send qpid-send.cpp Statistics.cpp ${platform_test_additions}) +target_link_libraries (qpid-send qpidmessaging) +remember_location(qpid-send) + +# qpid-perftest and qpid-latency-test are generally useful so install them +install (TARGETS qpid-perftest qpid-latency-test RUNTIME + DESTINATION ${QPID_INSTALL_BINDIR}) + +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}) + +add_test (unit_test ${test_wrap} ${unit_test_LOCATION}) +add_test (start_broker ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/start_broker${test_script_suffix}) +add_test (qpid-client-test ${test_wrap} ${qpid-client_test_LOCATION}) +add_test (quick_perftest ${test_wrap} ${qpid-perftest_LOCATION} --summary --count 100) +add_test (quick_topictest ${test_wrap} ${CMAKE_CURRENT_SOURCE_DIR}/quick_topictest${test_script_suffix}) +add_test (quick_txtest ${test_wrap} ${qpid-txtest_LOCATION} --queues 4 --tx-count 10 --quiet) +if (PYTHON_EXECUTABLE) + add_test (run_header_test ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_header_test${test_script_suffix}) + add_test (python_tests ${test_wrap} ${CMAKE_CURRENT_SOURCE_DIR}/python_tests${test_script_suffix}) +endif (PYTHON_EXECUTABLE) +add_test (stop_broker ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/stop_broker${test_script_suffix}) +if (PYTHON_EXECUTABLE) + add_test (federation_tests ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_federation_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 (dynamic_log_level_test ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/dynamic_log_level_test${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 ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_store_tests${test_script_suffix} MSSQL-CLFS) +endif (BUILD_MSCLFS) +endif (PYTHON_EXECUTABLE) + +add_library(test_store MODULE test_store.cpp) +target_link_libraries (test_store qpidbroker qpidcommon) +set_target_properties (test_store PROPERTIES PREFIX "") + +#EXTRA_DIST += \ +# run_test vg_check \ +# run-unit-tests start_broker python_tests stop_broker \ +# quick_topictest \ +# quick_perftest \ +# quick_txtest \ +# topictest \ +# run_header_test \ +# header_test.py \ +# ssl_test \ +# config.null \ +# ais_check \ +# run_federation_tests \ +# run_acl_tests \ +# .valgrind.supp \ +# MessageUtils.h \ +# TestMessageStore.h \ +# TxMocks.h \ +# start_cluster stop_cluster restart_cluster + +add_library (dlclose_noop MODULE dlclose_noop.c) +#libdlclose_noop_la_LDFLAGS = -module -rpath $(abs_builddir) + +#CLEANFILES+=valgrind.out *.log *.vglog* dummy_test $(unit_wrappers) +# +## FIXME aconway 2008-05-23: Disabled interop_runner because it uses +## the obsolete Channel class. Convert to Session and re-enable. +## +## check_PROGRAMS += interop_runner +# +## interop_runner_SOURCES = \ +## interop_runner.cpp \ +## SimpleTestCaseBase.cpp \ +## BasicP2PTest.cpp \ +## BasicPubSubTest.cpp \ +## SimpleTestCaseBase.h \ +## BasicP2PTest.h \ +## BasicPubSubTest.h \ +## TestCase.h \ +## TestOptions.h ConnectionOptions.h +## interop_runner_LDADD = $(lib_client) $(lib_common) $(extra_libs) +# +# +## Longer running stability tests, not run by default check: target. +## Not run under valgrind, too slow +#LONG_TESTS=fanout_perftest shared_perftest multiq_perftest topic_perftest run_failover_soak +#EXTRA_DIST+=$(LONG_TESTS) run_perftest +#check-long: +# $(MAKE) check TESTS="start_broker $(LONG_TESTS) stop_broker" VALGRIND= 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..3c0cff7350 --- /dev/null +++ b/qpid/cpp/src/tests/ClientSessionTest.cpp @@ -0,0 +1,682 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 <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::Broker; +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 ProxySessionFixture +{ + ClientSessionFixture(Broker::Options opts = Broker::Options()) : ProxySessionFixture(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()); +} + +// FIXME aconway 2009-06-17: test for unimplemented feature, enable when implemented. +void testSuspend0Timeout() { + ClientSessionFixture fix; + fix.session.suspend(); // session has 0 timeout. + try { + fix.connection.resume(fix.session); + BOOST_FAIL("Expected InvalidArgumentException."); + } catch(const InternalErrorException&) {} +} + +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&) {} +} + +// FIXME aconway 2009-06-17: test for unimplemented feature, enable when implemented. +void testSuspendResume() { + ClientSessionFixture fix; + fix.session.timeout(60); + fix.session.suspend(); + // Make sure we are still subscribed after resume. + fix.connection.resume(fix.session); + fix.session.messageTransfer(arg::content=Message("my-message", "my-queue")); + BOOST_CHECK_EQUAL("my-message", fix.subs.get("my-queue", TIME_SEC).getData()); +} + + +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) { + Broker::Options opts; + opts.queueCleanInterval = 1; + 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); + 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/ClusterFailover.cpp b/qpid/cpp/src/tests/ClusterFailover.cpp new file mode 100644 index 0000000000..bf5c147f19 --- /dev/null +++ b/qpid/cpp/src/tests/ClusterFailover.cpp @@ -0,0 +1,115 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 Tests for partial failure in a cluster. + * Partial failure means some nodes experience a failure while others do not. + * In this case the failed nodes must shut down. + */ + +#include "test_tools.h" +#include "unit_test.h" +#include "ClusterFixture.h" +#include "qpid/client/FailoverManager.h" +#include <boost/assign.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/bind.hpp> + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(ClusterFailoverTestSuite) + +using namespace std; +using namespace qpid; +using namespace qpid::cluster; +using namespace qpid::framing; +using namespace qpid::client; +using namespace qpid::client::arg; +using namespace boost::assign; +using broker::Broker; +using boost::shared_ptr; + +// Timeout for tests that wait for messages +const sys::Duration TIMEOUT=sys::TIME_SEC/4; + +ClusterFixture::Args getArgs(bool durable=std::getenv("STORE_LIB")) +{ + ClusterFixture::Args args; + args += "--auth", "no", "--no-module-dir", + "--load-module", getLibPath("CLUSTER_LIB"); + if (durable) + args += "--load-module", getLibPath("STORE_LIB"), "TMP_DATA_DIR"; + else + args += "--no-data-dir"; + return args; +} + +// Test re-connecting with same session name after a failure. +QPID_AUTO_TEST_CASE(testReconnectSameSessionName) { + ClusterFixture cluster(2, getArgs(), -1); + // Specify a timeout to make sure it is ignored, session resume is + // not implemented so sessions belonging to dead brokers should + // not be kept. + Client c0(cluster[0], "foo", 5); + BOOST_CHECK_EQUAL(2u, knownBrokerPorts(c0.connection, 2).size()); // wait for both. + c0.session.queueDeclare("q"); + c0.session.messageTransfer(arg::content=Message("sendme", "q")); + BOOST_CHECK_EQUAL(c0.subs.get("q").getData(), "sendme"); + cluster.killWithSilencer(0, c0.connection, 9); + Client c1(cluster[1], "foo", 5); + c1.session.queueQuery(); // Try to use the session. +} + +QPID_AUTO_TEST_CASE(testReconnectExclusiveQueue) { + // Regresion test. Session timeouts should be ignored + // by the broker as session resume is not implemented. + ClusterFixture cluster(2, getArgs(), -1); + Client c0(cluster[0], "foo", 5); + c0.session.queueDeclare("exq", arg::exclusive=true); + SubscriptionSettings settings; + settings.exclusive = true; + settings.autoAck = 0; + Subscription s0 = c0.subs.subscribe(c0.lq, "exq", settings, "exsub"); + c0.session.messageTransfer(arg::content=Message("sendme", "exq")); + BOOST_CHECK_EQUAL(c0.lq.get().getData(), "sendme"); + + // Regression: core dump on exit if unacked messages were left in + // a session with a timeout. + cluster.killWithSilencer(0, c0.connection); + + // Regression: session timeouts prevented re-connecting to + // exclusive queue. + Client c1(cluster[1]); + c1.session.queueDeclare("exq", arg::exclusive=true); + Subscription s1 = c1.subs.subscribe(c1.lq, "exq", settings, "exsub"); + s1.cancel(); + + // Regression: session timeouts prevented new member joining + // cluster with exclusive queues. + cluster.add(); + Client c2(cluster[2]); + c2.session.queueQuery(); +} + + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/ClusterFixture.cpp b/qpid/cpp/src/tests/ClusterFixture.cpp new file mode 100644 index 0000000000..6b62cb6fc7 --- /dev/null +++ b/qpid/cpp/src/tests/ClusterFixture.cpp @@ -0,0 +1,160 @@ +/* + * + * 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 "unit_test.h" +#include "ForkedBroker.h" +#include "BrokerFixture.h" + +#include "qpid/client/Connection.h" +#include "qpid/client/ConnectionAccess.h" +#include "qpid/client/Session.h" +#include "qpid/client/FailoverListener.h" +#include "qpid/cluster/Cluster.h" +#include "qpid/cluster/Cpg.h" +#include "qpid/cluster/UpdateClient.h" +#include "qpid/framing/AMQBody.h" +#include "qpid/framing/Uuid.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/framing/enum.h" +#include "qpid/log/Logger.h" + +#include <boost/bind.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/assign.hpp> + +#include <string> +#include <iostream> +#include <iterator> +#include <vector> +#include <set> +#include <algorithm> +#include <iterator> + + +using namespace std; +using namespace qpid; +using namespace qpid::cluster; +using namespace qpid::framing; +using namespace qpid::client; +using qpid::sys::TIME_SEC; +using qpid::broker::Broker; +using boost::shared_ptr; +using qpid::cluster::Cluster; +using boost::assign::list_of; + + +#include "ClusterFixture.h" + +namespace qpid { +namespace tests { + +ClusterFixture::ClusterFixture(size_t n, const Args& args_, int localIndex_) + : name(Uuid(true).str()), localIndex(localIndex_), userArgs(args_) +{ + add(n); +} + +ClusterFixture::ClusterFixture(size_t n, boost::function<void (Args&, size_t)> updateArgs_, int localIndex_) + : name(Uuid(true).str()), localIndex(localIndex_), updateArgs(updateArgs_) +{ + add(n); +} + +ClusterFixture::Args ClusterFixture::makeArgs(const std::string& prefix, size_t index) { + Args args = list_of<string>("qpidd ") + ("--cluster-name")(name) + ("--log-prefix")(prefix); + args.insert(args.end(), userArgs.begin(), userArgs.end()); + if (updateArgs) updateArgs(args, index); + return args; +} + +void ClusterFixture::add() { + if (size() != size_t(localIndex)) { // fork a broker process. + std::ostringstream os; os << "fork" << size(); + std::string prefix = os.str(); + forkedBrokers.push_back(shared_ptr<ForkedBroker>(new ForkedBroker(makeArgs(prefix, size())))); + push_back(forkedBrokers.back()->getPort()); + } + else { // Run in this process + addLocal(); + } +} + +namespace { +/** Parse broker & cluster options */ +Broker::Options parseOpts(size_t argc, const char* argv[]) { + Broker::Options opts; + Plugin::addOptions(opts); // Pick up cluster options. + opts.parse(argc, argv, "", true); // Allow-unknown for --load-module + return opts; +} +} + +void ClusterFixture::addLocal() { + assert(int(size()) == localIndex); + ostringstream os; os << "local" << localIndex; + string prefix = os.str(); + Args args(makeArgs(prefix, localIndex)); + vector<const char*> argv(args.size()); + transform(args.begin(), args.end(), argv.begin(), boost::bind(&string::c_str, _1)); + qpid::log::Logger::instance().setPrefix(prefix); + localBroker.reset(new BrokerFixture(parseOpts(argv.size(), &argv[0]))); + push_back(localBroker->getPort()); + forkedBrokers.push_back(shared_ptr<ForkedBroker>()); +} + +bool ClusterFixture::hasLocal() const { return localIndex >= 0 && size_t(localIndex) < size(); } + +/** Kill a forked broker with sig, or shutdown localBroker if n==0. */ +void ClusterFixture::kill(size_t n, int sig) { + if (n == size_t(localIndex)) + localBroker->broker->shutdown(); + else + forkedBrokers[n]->kill(sig); +} + +/** Kill a broker and suppress errors from closing connection c. */ +void ClusterFixture::killWithSilencer(size_t n, client::Connection& c, int sig) { + ScopedSuppressLogging sl; + try { c.close(); } catch(...) {} + kill(n,sig); +} + +/** + * Get the known broker ports from a Connection. + *@param n if specified wait for the cluster size to be n, up to a timeout. + */ +std::set<int> knownBrokerPorts(qpid::client::Connection& c, int n) { + FailoverListener fl(c, false); + std::vector<qpid::Url> urls = fl.getKnownBrokers(); + if (n >= 0 && unsigned(n) != urls.size()) { + // Retry up to 10 secs in .1 second intervals. + for (size_t retry=100; urls.size() != unsigned(n) && retry != 0; --retry) { + qpid::sys::usleep(1000*100); // 0.1 secs + urls = fl.getKnownBrokers(); + } + } + std::set<int> s; + for (std::vector<qpid::Url>::const_iterator i = urls.begin(); i != urls.end(); ++i) + s.insert((*i)[0].port); + return s; +} + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/ClusterFixture.h b/qpid/cpp/src/tests/ClusterFixture.h new file mode 100644 index 0000000000..f548ff9376 --- /dev/null +++ b/qpid/cpp/src/tests/ClusterFixture.h @@ -0,0 +1,115 @@ +#ifndef CLUSTER_FIXTURE_H +#define CLUSTER_FIXTURE_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 "test_tools.h" +#include "unit_test.h" +#include "ForkedBroker.h" +#include "BrokerFixture.h" + +#include "qpid/client/Connection.h" +#include "qpid/client/ConnectionAccess.h" +#include "qpid/client/Session.h" +#include "qpid/client/FailoverListener.h" +#include "qpid/cluster/Cluster.h" +#include "qpid/cluster/Cpg.h" +#include "qpid/cluster/UpdateClient.h" +#include "qpid/framing/AMQBody.h" +#include "qpid/framing/Uuid.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/framing/enum.h" +#include "qpid/log/Logger.h" + +#include <boost/bind.hpp> +#include <boost/function.hpp> +#include <boost/shared_ptr.hpp> + +#include <string> +#include <iostream> +#include <iterator> +#include <vector> +#include <set> +#include <algorithm> +#include <iterator> + + +using namespace std; +using namespace qpid; +using namespace qpid::cluster; +using namespace qpid::framing; +using namespace qpid::client; +using qpid::sys::TIME_SEC; +using qpid::broker::Broker; +using boost::shared_ptr; +using qpid::cluster::Cluster; + +namespace qpid { +namespace tests { + +/** Cluster fixture is a vector of ports for the replicas. + * + * At most one replica (by default replica 0) is in the current + * process, all others are forked as children. + */ +class ClusterFixture : public vector<uint16_t> { + public: + typedef std::vector<std::string> Args; + + /** @param localIndex can be -1 meaning don't automatically start a local broker. + * A local broker can be started with addLocal(). + */ + ClusterFixture(size_t n, const Args& args, int localIndex=-1); + + /**@param updateArgs function is passed the index of the cluster member and can update the arguments. */ + ClusterFixture(size_t n, boost::function<void (Args&, size_t)> updateArgs, int localIndex=-1); + + void add(size_t n) { for (size_t i=0; i < n; ++i) add(); } + void add(); // Add a broker. + void setup(); + + bool hasLocal() const; + + /** Kill a forked broker with sig, or shutdown localBroker. */ + void kill(size_t n, int sig=SIGINT); + + /** Kill a broker and suppress errors from closing connection c. */ + void killWithSilencer(size_t n, client::Connection& c, int sig=SIGINT); + + private: + + void addLocal(); // Add a local broker. + Args makeArgs(const std::string& prefix, size_t index); + string name; + std::auto_ptr<BrokerFixture> localBroker; + int localIndex; + std::vector<shared_ptr<ForkedBroker> > forkedBrokers; + Args userArgs; + boost::function<void (Args&, size_t)> updateArgs; +}; + +/** + * Get the known broker ports from a Connection. + *@param n if specified wait for the cluster size to be n, up to a timeout. + */ +std::set<int> knownBrokerPorts(qpid::client::Connection& source, int n=-1); + +}} // namespace qpid::tests + +#endif /*!CLUSTER_FIXTURE_H*/ 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/ConsoleTest.cpp b/qpid/cpp/src/tests/ConsoleTest.cpp new file mode 100644 index 0000000000..107472ed9e --- /dev/null +++ b/qpid/cpp/src/tests/ConsoleTest.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 "qpid/console/Package.h" +#include "qpid/console/ClassKey.h" +#include "unit_test.h" + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(ConsoleTestSuite) + +using namespace qpid::framing; +using namespace qpid::console; + +QPID_AUTO_TEST_CASE(testClassKey) { + uint8_t hash[16] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; + ClassKey k("com.redhat.test", "class", hash); + + BOOST_CHECK_EQUAL(k.getPackageName(), "com.redhat.test"); + BOOST_CHECK_EQUAL(k.getClassName(), "class"); + BOOST_CHECK_EQUAL(k.getHashString(), "00010203-04050607-08090a0b-0c0d0e0f"); + BOOST_CHECK_EQUAL(k.str(), "com.redhat.test:class(00010203-04050607-08090a0b-0c0d0e0f)"); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/DeliveryRecordTest.cpp b/qpid/cpp/src/tests/DeliveryRecordTest.cpp new file mode 100644 index 0000000000..f7013014ff --- /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(QueuedMessage(0), Queue::shared_ptr(), "tag", 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..e1691db584 --- /dev/null +++ b/qpid/cpp/src/tests/DispatcherTest.cpp @@ -0,0 +1,241 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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/IOHandle.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; + + PosixIOHandle f0(sv[0]); + PosixIOHandle 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..9d7666dca4 --- /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(); + + DtxBuffer::shared_ptr bufferA(new DtxBuffer()); + bufferA->enlist(static_pointer_cast<TxOp>(opA)); + bufferA->markEnded(); + DtxBuffer::shared_ptr 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(); + + DtxBuffer::shared_ptr bufferA(new DtxBuffer()); + bufferA->enlist(static_pointer_cast<TxOp>(opA)); + bufferA->markEnded(); + DtxBuffer::shared_ptr bufferB(new DtxBuffer()); + bufferB->enlist(static_pointer_cast<TxOp>(opB)); + bufferB->markEnded(); + DtxBuffer::shared_ptr 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(); + + DtxBuffer::shared_ptr bufferA(new DtxBuffer()); + bufferA->enlist(static_pointer_cast<TxOp>(opA)); + bufferA->markEnded(); + DtxBuffer::shared_ptr 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(); + + DtxBuffer::shared_ptr bufferA(new DtxBuffer()); + bufferA->enlist(static_pointer_cast<TxOp>(opA)); + bufferA->markEnded(); + DtxBuffer::shared_ptr bufferB(new DtxBuffer()); + bufferB->enlist(static_pointer_cast<TxOp>(opB)); + bufferB->markEnded(); + DtxBuffer::shared_ptr 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(); + + DtxBuffer::shared_ptr bufferA(new DtxBuffer()); + bufferA->enlist(static_pointer_cast<TxOp>(opA)); + bufferA->markEnded(); + DtxBuffer::shared_ptr 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..88a1cd99c2 --- /dev/null +++ b/qpid/cpp/src/tests/ExchangeTest.cpp @@ -0,0 +1,289 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 boost::intrusive_ptr; +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(); + + intrusive_ptr<Message> msgPtr(MessageUtils::createMessage("exchange", "key", false, "id")); + DeliverableMessage msg(msgPtr); + topic.route(msg, "abc", 0); + direct.route(msg, "abc", 0); + +} + +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, "", &args3); + 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, 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, FieldTable()); + BOOST_CHECK_EQUAL(string("direct"), response.first->getType()); +} + +intrusive_ptr<Message> cmessage(std::string exchange, std::string routingKey) { + intrusive_ptr<Message> msg(new Message()); + AMQFrame method((MessageTransferBody(ProtocolVersion(), exchange, 0, 0))); + AMQFrame header((AMQHeaderBody())); + msg->getFrames().append(method); + msg->getFrames().append(header); + msg->getFrames().getHeaders()->get<DeliveryProperties>(true)->setRoutingKey(routingKey); + return msg; +} + +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, args); + + intrusive_ptr<Message> msg1 = cmessage("e", "A"); + intrusive_ptr<Message> msg2 = cmessage("e", "B"); + intrusive_ptr<Message> msg3 = cmessage("e", "C"); + + DeliverableMessage dmsg1(msg1); + DeliverableMessage dmsg2(msg2); + DeliverableMessage dmsg3(msg3); + + direct.route(dmsg1, "abc", 0); + direct.route(dmsg2, "abc", 0); + direct.route(dmsg3, "abc", 0); + + BOOST_CHECK_EQUAL(1, msg1->getApplicationHeaders()->getAsInt64("qpid.msg_sequence")); + BOOST_CHECK_EQUAL(2, msg2->getApplicationHeaders()->getAsInt64("qpid.msg_sequence")); + BOOST_CHECK_EQUAL(3, msg3->getApplicationHeaders()->getAsInt64("qpid.msg_sequence")); + + FanOutExchange fanout("fanout1", false, args); + HeadersExchange header("headers1", false, args); + TopicExchange topic ("topic1", false, args); + + // check other exchanges, that they preroute + intrusive_ptr<Message> msg4 = cmessage("e", "A"); + intrusive_ptr<Message> msg5 = cmessage("e", "B"); + intrusive_ptr<Message> msg6 = cmessage("e", "C"); + + DeliverableMessage dmsg4(msg4); + DeliverableMessage dmsg5(msg5); + DeliverableMessage dmsg6(msg6); + + fanout.route(dmsg4, "abc", 0); + BOOST_CHECK_EQUAL(1, msg4->getApplicationHeaders()->getAsInt64("qpid.msg_sequence")); + + FieldTable headers; + header.route(dmsg5, "abc", &headers); + BOOST_CHECK_EQUAL(1, msg5->getApplicationHeaders()->getAsInt64("qpid.msg_sequence")); + + topic.route(dmsg6, "abc", 0); + BOOST_CHECK_EQUAL(1, msg6->getApplicationHeaders()->getAsInt64("qpid.msg_sequence")); + direct.encode(buffer); + } + { + + ExchangeRegistry exchanges; + buffer.reset(); + DirectExchange::shared_ptr exch_dec = Exchange::decode(exchanges, buffer); + + intrusive_ptr<Message> msg1 = cmessage("e", "A"); + DeliverableMessage dmsg1(msg1); + exch_dec->route(dmsg1, "abc", 0); + + BOOST_CHECK_EQUAL(4, msg1->getApplicationHeaders()->getAsInt64("qpid.msg_sequence")); + + } + delete [] buff; +} + +QPID_AUTO_TEST_CASE(testIVEOption) +{ + FieldTable args; + args.setInt("qpid.ive",1); + DirectExchange direct("direct1", false, args); + FanOutExchange fanout("fanout1", false, args); + HeadersExchange header("headers1", false, args); + TopicExchange topic ("topic1", false, args); + + intrusive_ptr<Message> msg1 = cmessage("direct1", "abc"); + msg1->getProperties<MessageProperties>()->getApplicationHeaders().setString("a", "abc"); + DeliverableMessage dmsg1(msg1); + + FieldTable args2; + args2.setString("x-match", "any"); + args2.setString("a", "abc"); + + direct.route(dmsg1, "abc", 0); + fanout.route(dmsg1, "abc", 0); + header.route(dmsg1, "abc", &args2); + topic.route(dmsg1, "abc", 0); + 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(HeadersExchange::match(args2, msg1->getProperties<MessageProperties>()->getApplicationHeaders())); + + 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..fe2a14ec03 --- /dev/null +++ b/qpid/cpp/src/tests/FieldTable.cpp @@ -0,0 +1,213 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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/framing/FieldTable.h" +#include "qpid/framing/FieldValue.h" +#include "qpid/framing/List.h" +#include "qpid/sys/alloca.h" + +#include "unit_test.h" + +using namespace qpid::framing; + +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; + + char* buff = static_cast<char*>(::alloca(c.encodedSize())); + Buffer wbuffer(buff, c.encodedSize()); + wbuffer.put(c); + + Buffer rbuffer(buff, 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; + c.collect(items); + 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(string("abc") == a.getAsString("string")); + BOOST_CHECK(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(f2 == f); + + double d2; + BOOST_CHECK(!a.getDouble("string", d2)); + BOOST_CHECK(!a.getDouble("int", d2)); + BOOST_CHECK(a.getDouble("double", d2)); + BOOST_CHECK(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..0ebd0d7d44 --- /dev/null +++ b/qpid/cpp/src/tests/FieldValue.cpp @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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" + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(FieldValueTestSuite) + +using namespace qpid::framing; + +Str16Value s("abc"); +IntegerValue i(42); +//DecimalValue d(1234,2); +//FieldTableValue ft; +//EmptyValue e; + +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); +// BOOST_CHECK(s != ft); + +} + +QPID_AUTO_TEST_CASE(testIntegerValueEquals) +{ + 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<int>() == true); + BOOST_CHECK_THROW(i.get<std::string>(), InvalidConversionException); + BOOST_CHECK(i.get<int>() == 42); +// BOOST_CHECK(i != ft); +} + +#if 0 +QPID_AUTO_TEST_CASE(testDecimalValueEquals) +{ + BOOST_CHECK(DecimalValue(1234, 2) == d); + BOOST_CHECK(DecimalValue(12345, 2) != d); + BOOST_CHECK(DecimalValue(1234, 3) != d); + BOOST_CHECK(d != s); +} + +QPID_AUTO_TEST_CASE(testFieldTableValueEquals) +{ + ft.getValue().setString("foo", "FOO"); + ft.getValue().setInt("magic", 7); + + BOOST_CHECK_EQUAL(std::string("FOO"), + ft.getValue().getString("foo")); + BOOST_CHECK_EQUAL(7, ft.getValue().getInt("magic")); + + FieldTableValue f2; + BOOST_CHECK(ft != f2); + f2.getValue().setString("foo", "FOO"); + BOOST_CHECK(ft != f2); + f2.getValue().setInt("magic", 7); + BOOST_CHECK_EQUAL(ft,f2); + BOOST_CHECK(ft == f2); + f2.getValue().setString("foo", "BAR"); + BOOST_CHECK(ft != f2); + BOOST_CHECK(ft != i); +} +#endif + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/ForkedBroker.cpp b/qpid/cpp/src/tests/ForkedBroker.cpp new file mode 100644 index 0000000000..53eaa7e1ce --- /dev/null +++ b/qpid/cpp/src/tests/ForkedBroker.cpp @@ -0,0 +1,158 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 "ForkedBroker.h" +#include "qpid/log/Statement.h" +#include <boost/bind.hpp> +#include <algorithm> +#include <stdlib.h> +#include <sys/types.h> +#include <signal.h> + +using namespace std; +using qpid::ErrnoException; + +namespace std { +static ostream& operator<<(ostream& o, const qpid::tests::ForkedBroker::Args& a) { +copy(a.begin(), a.end(), ostream_iterator<string>(o, " ")); +return o; +} +} + +namespace qpid { +namespace tests { + +ForkedBroker::ForkedBroker(const Args& constArgs) : running(false), exitStatus(0) { + Args args(constArgs); + // Substitute the special value "TMP_DATA_DIR" with a temporary data dir. + Args::iterator i = find(args.begin(), args.end(), string("TMP_DATA_DIR")); + if (i != args.end()) { + args.erase(i); + char dd[] = "/tmp/ForkedBroker.XXXXXX"; + if (!mkdtemp(dd)) + throw qpid::ErrnoException("Can't create data dir"); + dataDir = dd; + args.push_back("--data-dir"); + args.push_back(dataDir); + } + // Never use the default data directory, set --no-data-dir if no other data-dir arg. + Args::iterator j = find(args.begin(), args.end(), string("--data-dir")); + Args::iterator k = find(args.begin(), args.end(), string("--no-data-dir")); + if (j == args.end() && k == args.end()) + args.push_back("--no-data-dir"); + init(args); +} + +ForkedBroker::~ForkedBroker() { + try { kill(); } + catch (const std::exception& e) { + QPID_LOG(error, QPID_MSG("Killing forked broker: " << e.what())); + } + if (!dataDir.empty()) + { + int unused_ret; // Suppress warnings about ignoring return value. + unused_ret = ::system(("rm -rf "+dataDir).c_str()); + } +} + +void ForkedBroker::kill(int sig) { + if (pid == 0) return; + int savePid = pid; + pid = 0; // Reset pid here in case of an exception. + using qpid::ErrnoException; + if (::kill(savePid, sig) < 0) + throw ErrnoException("kill failed"); + int status; + if (::waitpid(savePid, &status, 0) < 0 && sig != 9) + throw ErrnoException("wait for forked process failed"); + if (WEXITSTATUS(status) != 0 && sig != 9) + throw qpid::Exception(QPID_MSG("Forked broker exited with: " << WEXITSTATUS(status))); + running = false; + exitStatus = status; +} + +bool isLogOption(const std::string& s) { + const char * log_enable = "--log-enable", + * trace = "--trace"; + return( (! strncmp(s.c_str(), log_enable, strlen(log_enable))) || + (! strncmp(s.c_str(), trace, strlen(trace))) + ); +} + +namespace { + void ignore_signal(int) + { + } +} + +void ForkedBroker::init(const Args& userArgs) { + using qpid::ErrnoException; + port = 0; + int pipeFds[2]; + if(::pipe(pipeFds) < 0) throw ErrnoException("Can't create pipe"); + + // Ignore the SIGCHLD signal generated by an exitting child + // We will clean up any exitting children in the waitpid above + // This should really be neater (like only once not per fork) + struct ::sigaction sa; + sa.sa_handler = ignore_signal; + ::sigemptyset(&sa.sa_mask); + ::sigaddset(&sa.sa_mask, SIGCHLD); + sa.sa_flags = SA_NOCLDSTOP | SA_RESTART; + ::sigaction(SIGCHLD, &sa, 0); + + pid = ::fork(); + if (pid < 0) throw ErrnoException("Fork failed"); + if (pid) { // parent + ::close(pipeFds[1]); + FILE* f = ::fdopen(pipeFds[0], "r"); + if (!f) throw ErrnoException("fopen failed"); + if (::fscanf(f, "%d", &port) != 1) { + if (ferror(f)) throw ErrnoException("Error reading port number from child."); + else throw qpid::Exception("EOF reading port number from child."); + } + ::fclose(f); + running = true; + } + else { // child + ::close(pipeFds[0]); + int fd = ::dup2(pipeFds[1], 1); // pipe stdout to the parent. + if (fd < 0) throw ErrnoException("dup2 failed"); + const char* prog = ::getenv("QPIDD_EXEC"); + if (!prog) prog = "../qpidd"; // This only works from within svn checkout + Args args(userArgs); + args.push_back("--port=0"); + // Keep quiet except for errors. + if (!::getenv("QPID_TRACE") && !::getenv("QPID_LOG_ENABLE") + && find_if(userArgs.begin(), userArgs.end(), isLogOption) == userArgs.end()) + args.push_back("--log-enable=error+"); + std::vector<const char*> argv(args.size()); + std::transform(args.begin(), args.end(), argv.begin(), boost::bind(&std::string::c_str, _1)); + argv.push_back(0); + QPID_LOG(debug, "ForkedBroker exec " << prog << ": " << args); + + execv(prog, const_cast<char* const*>(&argv[0])); + QPID_LOG(critical, "execv failed to start broker: prog=\"" << prog << "\"; args=\"" << args << "\"; errno=" << errno << " (" << std::strerror(errno) << ")"); + ::exit(1); + } +} + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/ForkedBroker.h b/qpid/cpp/src/tests/ForkedBroker.h new file mode 100644 index 0000000000..87e141a425 --- /dev/null +++ b/qpid/cpp/src/tests/ForkedBroker.h @@ -0,0 +1,82 @@ +#ifndef TESTS_FORKEDBROKER_H +#define TESTS_FORKEDBROKER_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/Exception.h" +#include "qpid/log/Statement.h" +#include "qpid/broker/Broker.h" +#include <boost/lexical_cast.hpp> +#include <string> +#include <stdio.h> +#include <sys/wait.h> + +namespace qpid { +namespace tests { + +/** + * Class to fork a broker child process. + * + * For most tests a BrokerFixture may be more convenient as it starts + * a broker in the same process which allows you to easily debug into + * the broker. + * + * This useful for tests that need to start multiple brokers where + * those brokers can't coexist in the same process (e.g. for cluster + * tests where CPG doesn't allow multiple group members in a single + * process.) + * + */ +class ForkedBroker { + public: + typedef std::vector<std::string> Args; + + // argv args are passed to broker. + // + // Special value "TMP_DATA_DIR" is substituted with a temporary + // data directory for the broker. + // + ForkedBroker(const Args& argv); + ~ForkedBroker(); + + void kill(int sig=SIGINT); + int wait(); // Wait for exit, return exit status. + uint16_t getPort() { return port; } + pid_t getPID() { return pid; } + bool isRunning() { return running; } + + private: + + void init(const Args& args); + + bool running; + int exitStatus; + + pid_t pid; + int port; + std::string dataDir; +}; + +}} // namespace qpid::tests + +#endif /*!TESTS_FORKEDBROKER_H*/ diff --git a/qpid/cpp/src/tests/Frame.cpp b/qpid/cpp/src/tests/Frame.cpp new file mode 100644 index 0000000000..1270eabba3 --- /dev/null +++ b/qpid/cpp/src/tests/Frame.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 "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; +using namespace boost; + +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..f8795316cc --- /dev/null +++ b/qpid/cpp/src/tests/FramingTest.cpp @@ -0,0 +1,167 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 "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..40deb59c86 --- /dev/null +++ b/qpid/cpp/src/tests/HeadersExchangeTest.cpp @@ -0,0 +1,120 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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/framing/FieldTable.h" +#include "qpid/framing/FieldValue.h" +#include "unit_test.h" + +using namespace qpid::broker; +using namespace qpid::framing; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(HeadersExchangeTestSuite) + +QPID_AUTO_TEST_CASE(testMatchAll) +{ + FieldTable b, m, n; + b.setString("x-match", "all"); + b.setString("foo", "FOO"); + b.setInt("n", 42); + m.setString("foo", "FOO"); + m.setInt("n", 42); + BOOST_CHECK(HeadersExchange::match(b, m)); + + // Ignore extras. + m.setString("extra", "x"); + BOOST_CHECK(HeadersExchange::match(b, m)); + + // Fail mismatch, wrong value. + m.setString("foo", "NotFoo"); + BOOST_CHECK(!HeadersExchange::match(b, m)); + + // Fail mismatch, missing value + n.setInt("n", 42); + n.setString("extra", "x"); + BOOST_CHECK(!HeadersExchange::match(b, n)); +} + +QPID_AUTO_TEST_CASE(testMatchAny) +{ + FieldTable b, m, n; + b.setString("x-match", "any"); + b.setString("foo", "FOO"); + b.setInt("n", 42); + m.setString("foo", "FOO"); + BOOST_CHECK(!HeadersExchange::match(b, n)); + BOOST_CHECK(HeadersExchange::match(b, m)); + m.setInt("n", 42); + BOOST_CHECK(HeadersExchange::match(b, m)); +} + +QPID_AUTO_TEST_CASE(testMatchEmptyValue) +{ + FieldTable b, m; + b.setString("x-match", "all"); + b.set("foo", FieldTable::ValuePtr()); + b.set("n", FieldTable::ValuePtr()); + BOOST_CHECK(!HeadersExchange::match(b, m)); + m.setString("foo", "blah"); + m.setInt("n", 123); +} + +QPID_AUTO_TEST_CASE(testMatchEmptyArgs) +{ + FieldTable b, m; + m.setString("foo", "FOO"); + + b.setString("x-match", "all"); + BOOST_CHECK(HeadersExchange::match(b, m)); + b.setString("x-match", "any"); + BOOST_CHECK(!HeadersExchange::match(b, m)); +} + + +QPID_AUTO_TEST_CASE(testMatchNoXMatch) +{ + FieldTable b, m; + b.setString("foo", "FOO"); + m.setString("foo", "FOO"); + BOOST_CHECK(!HeadersExchange::match(b, m)); +} + +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_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/InitialStatusMap.cpp b/qpid/cpp/src/tests/InitialStatusMap.cpp new file mode 100644 index 0000000000..ecbe2d4161 --- /dev/null +++ b/qpid/cpp/src/tests/InitialStatusMap.cpp @@ -0,0 +1,235 @@ +/* + * + * 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/cluster/InitialStatusMap.h" +#include "qpid/framing/Uuid.h" +#include <boost/assign.hpp> + +using namespace std; +using namespace qpid::cluster; +using namespace qpid::framing; +using namespace qpid::framing::cluster; +using namespace boost::assign; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(InitialStatusMapTestSuite) + +typedef InitialStatusMap::Status Status; + +Status activeStatus(const Uuid& id=Uuid(), const MemberSet& ms=MemberSet()) { + return Status(ProtocolVersion(), 0, true, id, STORE_STATE_NO_STORE, Uuid(), + encodeMemberSet(ms)); +} + +Status newcomerStatus(const Uuid& id=Uuid(), const MemberSet& ms=MemberSet()) { + return Status(ProtocolVersion(), 0, false, id, STORE_STATE_NO_STORE, Uuid(), + encodeMemberSet(ms)); +} + +Status storeStatus(bool active, StoreState state, Uuid start=Uuid(), Uuid stop=Uuid(), + const MemberSet& ms=MemberSet()) +{ + return Status(ProtocolVersion(), 0, active, start, state, stop, + encodeMemberSet(ms)); +} + +QPID_AUTO_TEST_CASE(testFirstInCluster) { + // Single member is first in cluster. + InitialStatusMap map(MemberId(0), 1); + Uuid id(true); + BOOST_CHECK(!map.isComplete()); + MemberSet members = list_of(MemberId(0)); + map.configChange(members); + BOOST_CHECK(!map.isComplete()); + map.received(MemberId(0), newcomerStatus(id, list_of<MemberId>(0))); + BOOST_CHECK(map.isComplete()); + BOOST_CHECK(map.transitionToComplete()); + BOOST_CHECK(map.getElders().empty()); + BOOST_CHECK(!map.isUpdateNeeded()); + BOOST_CHECK_EQUAL(id, map.getClusterId()); +} + +QPID_AUTO_TEST_CASE(testJoinExistingCluster) { + // Single member 0 joins existing cluster 1,2 + InitialStatusMap map(MemberId(0), 1); + Uuid id(true); + MemberSet members = list_of(MemberId(0))(MemberId(1))(MemberId(2)); + map.configChange(members); + BOOST_CHECK(map.isResendNeeded()); + BOOST_CHECK(!map.isComplete()); + map.received(MemberId(0), newcomerStatus()); + map.received(MemberId(1), activeStatus(id)); + BOOST_CHECK(!map.isComplete()); + map.received(MemberId(2), activeStatus(id)); + BOOST_CHECK(map.isComplete()); + BOOST_CHECK(map.transitionToComplete()); + BOOST_CHECK_EQUAL(map.getElders(), list_of<MemberId>(1)(2)); + BOOST_CHECK(map.isUpdateNeeded()); + BOOST_CHECK_EQUAL(map.getClusterId(), id); + + // Check that transitionToComplete is reset. + map.configChange(list_of<MemberId>(0)(1)); + BOOST_CHECK(!map.transitionToComplete()); +} + +QPID_AUTO_TEST_CASE(testMultipleFirstInCluster) { + // Multiple members 0,1,2 join at same time. + InitialStatusMap map(MemberId(1), 1); // self is 1 + Uuid id(true); + MemberSet members = list_of(MemberId(0))(MemberId(1))(MemberId(2)); + map.configChange(members); + BOOST_CHECK(map.isResendNeeded()); + + // All new members + map.received(MemberId(0), newcomerStatus(id, list_of<MemberId>(0)(1)(2))); + map.received(MemberId(1), newcomerStatus(id, list_of<MemberId>(0)(1)(2))); + map.received(MemberId(2), newcomerStatus(id, list_of<MemberId>(0)(1)(2))); + BOOST_CHECK(!map.isResendNeeded()); + BOOST_CHECK(map.isComplete()); + BOOST_CHECK(map.transitionToComplete()); + BOOST_CHECK_EQUAL(map.getElders(), list_of(MemberId(2))); + BOOST_CHECK(!map.isUpdateNeeded()); + BOOST_CHECK_EQUAL(map.getClusterId(), id); +} + +QPID_AUTO_TEST_CASE(testMultipleJoinExisting) { + // Multiple members 2,3 join simultaneously a cluster containing 0,1. + InitialStatusMap map(MemberId(2), 1); // self is 2 + Uuid id(true); + MemberSet members = list_of(MemberId(0))(MemberId(1))(MemberId(2))(MemberId(3)); + map.configChange(members); + BOOST_CHECK(map.isResendNeeded()); + map.received(MemberId(0), activeStatus(id, list_of<MemberId>(0))); + map.received(MemberId(1), newcomerStatus(id, list_of<MemberId>(0)(1))); + map.received(MemberId(2), newcomerStatus(id, list_of<MemberId>(0)(1)(2)(3))); + map.received(MemberId(3), newcomerStatus(id, list_of<MemberId>(0)(1)(2)(3))); + BOOST_CHECK(!map.isResendNeeded()); + BOOST_CHECK(map.isComplete()); + BOOST_CHECK(map.transitionToComplete()); + BOOST_CHECK_EQUAL(map.getElders(), list_of<MemberId>(0)(1)(3)); + BOOST_CHECK(map.isUpdateNeeded()); + BOOST_CHECK_EQUAL(map.getClusterId(), id); +} + +QPID_AUTO_TEST_CASE(testMembersLeave) { + // Test that map completes if members leave rather than send status. + InitialStatusMap map(MemberId(0), 1); + Uuid id(true); + map.configChange(list_of(MemberId(0))(MemberId(1))(MemberId(2))); + map.received(MemberId(0), newcomerStatus()); + map.received(MemberId(1), activeStatus(id)); + BOOST_CHECK(!map.isComplete()); + map.configChange(list_of(MemberId(0))(MemberId(1))); // 2 left + BOOST_CHECK(map.isComplete()); + BOOST_CHECK(map.transitionToComplete()); + BOOST_CHECK_EQUAL(map.getElders(), list_of(MemberId(1))); + BOOST_CHECK_EQUAL(map.getClusterId(), id); +} + +QPID_AUTO_TEST_CASE(testInteveningConfig) { + // Multiple config changes arrives before we complete the map. + InitialStatusMap map(MemberId(0), 1); + Uuid id(true); + + map.configChange(list_of<MemberId>(0)(1)); + BOOST_CHECK(map.isResendNeeded()); + map.received(MemberId(0), newcomerStatus()); + BOOST_CHECK(!map.isComplete()); + BOOST_CHECK(!map.isResendNeeded()); + // New member 2 joins before we receive 1 + map.configChange(list_of<MemberId>(0)(1)(2)); + BOOST_CHECK(!map.isComplete()); + BOOST_CHECK(map.isResendNeeded()); + map.received(1, activeStatus(id)); + map.received(2, newcomerStatus()); + // We should not be complete as we haven't received 0 since new member joined + BOOST_CHECK(!map.isComplete()); + BOOST_CHECK(!map.isResendNeeded()); + + map.received(0, newcomerStatus()); + BOOST_CHECK(map.isComplete()); + BOOST_CHECK(map.transitionToComplete()); + BOOST_CHECK_EQUAL(map.getElders(), list_of<MemberId>(1)); + BOOST_CHECK_EQUAL(map.getClusterId(), id); +} + +QPID_AUTO_TEST_CASE(testAllCleanNoUpdate) { + InitialStatusMap map(MemberId(0), 3); + map.configChange(list_of<MemberId>(0)(1)(2)); + map.received(MemberId(0), storeStatus(false, STORE_STATE_CLEAN_STORE)); + map.received(MemberId(1), storeStatus(false, STORE_STATE_CLEAN_STORE)); + map.received(MemberId(2), storeStatus(false, STORE_STATE_CLEAN_STORE)); + BOOST_CHECK(!map.isUpdateNeeded()); +} + +QPID_AUTO_TEST_CASE(testAllEmptyNoUpdate) { + InitialStatusMap map(MemberId(0), 3); + map.configChange(list_of<MemberId>(0)(1)(2)); + map.received(MemberId(0), storeStatus(false, STORE_STATE_EMPTY_STORE)); + map.received(MemberId(1), storeStatus(false, STORE_STATE_EMPTY_STORE)); + map.received(MemberId(2), storeStatus(false, STORE_STATE_EMPTY_STORE)); + BOOST_CHECK(map.isComplete()); + BOOST_CHECK(!map.isUpdateNeeded()); +} + +QPID_AUTO_TEST_CASE(testAllNoStoreNoUpdate) { + InitialStatusMap map(MemberId(0), 3); + map.configChange(list_of<MemberId>(0)(1)(2)); + map.received(MemberId(0), storeStatus(false, STORE_STATE_NO_STORE)); + map.received(MemberId(1), storeStatus(false, STORE_STATE_NO_STORE)); + map.received(MemberId(2), storeStatus(false, STORE_STATE_NO_STORE)); + BOOST_CHECK(map.isComplete()); + BOOST_CHECK(!map.isUpdateNeeded()); +} + +QPID_AUTO_TEST_CASE(testDirtyNeedUpdate) { + InitialStatusMap map(MemberId(0), 3); + map.configChange(list_of<MemberId>(0)(1)(2)); + map.received(MemberId(0), storeStatus(false, STORE_STATE_DIRTY_STORE)); + map.received(MemberId(1), storeStatus(false, STORE_STATE_CLEAN_STORE)); + map.received(MemberId(2), storeStatus(false, STORE_STATE_CLEAN_STORE)); + BOOST_CHECK(map.transitionToComplete()); + BOOST_CHECK(map.isUpdateNeeded()); +} + +QPID_AUTO_TEST_CASE(testEmptyNeedUpdate) { + InitialStatusMap map(MemberId(0), 3); + map.configChange(list_of<MemberId>(0)(1)(2)); + map.received(MemberId(0), storeStatus(false, STORE_STATE_EMPTY_STORE)); + map.received(MemberId(1), storeStatus(false, STORE_STATE_CLEAN_STORE)); + map.received(MemberId(2), storeStatus(false, STORE_STATE_CLEAN_STORE)); + BOOST_CHECK(map.transitionToComplete()); + BOOST_CHECK(map.isUpdateNeeded()); +} + +QPID_AUTO_TEST_CASE(testEmptyAlone) { + InitialStatusMap map(MemberId(0), 1); + map.configChange(list_of<MemberId>(0)); + map.received(MemberId(0), storeStatus(false, STORE_STATE_EMPTY_STORE)); + BOOST_CHECK(map.transitionToComplete()); + BOOST_CHECK(!map.isUpdateNeeded()); +} + +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/Makefile.am b/qpid/cpp/src/tests/Makefile.am new file mode 100644 index 0000000000..ed97c41bff --- /dev/null +++ b/qpid/cpp/src/tests/Makefile.am @@ -0,0 +1,398 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +AM_CXXFLAGS = $(WARNING_CFLAGS) -DBOOST_TEST_DYN_LINK +INCLUDES = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_srcdir)/src -I$(top_builddir)/src +PUBLIC_INCLUDES = -I$(top_srcdir)/include -I$(top_builddir)/include # Use public API only +QMF_GEN=$(top_srcdir)/managementgen/qmf-gen + +abs_builddir=@abs_builddir@ +abs_srcdir=@abs_srcdir@ + +extra_libs = +lib_client = $(abs_builddir)/../libqpidclient.la +lib_messaging = $(abs_builddir)/../libqpidmessaging.la +lib_common = $(abs_builddir)/../libqpidcommon.la +lib_broker = $(abs_builddir)/../libqpidbroker.la +lib_console = $(abs_builddir)/../libqmfconsole.la +lib_qmf2 = $(abs_builddir)/../libqmf2.la +# lib_amqp_0_10 = $(abs_builddir)/../libqpidamqp_0_10.la + +# +# Initialize variables that are incremented with += +# +check_PROGRAMS= +check_LTLIBRARIES= +TESTS= +EXTRA_DIST= +CLEANFILES= +LONG_TESTS= +CLEAN_LOCAL= + +# +# Destination for intalled programs and tests defined here +# +qpidexecdir = $(libexecdir)/qpid +qpidexec_PROGRAMS = +qpidexec_SCRIPTS = +qpidtestdir = $(qpidexecdir)/tests +qpidtest_PROGRAMS = +qpidtest_SCRIPTS = +tmoduledir = $(libdir)/qpid/tests +tmodule_LTLIBRARIES= + +# +# 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 do +# rm -f unit_test; make unit_test unit_test_OBJECTS="unit_test.o SelectedTest.o" +# + +TESTS+=unit_test +check_PROGRAMS+=unit_test +unit_test_LDADD=-lboost_unit_test_framework \ + $(lib_messaging) $(lib_broker) $(lib_console) $(lib_qmf2) + +unit_test_SOURCES= unit_test.cpp unit_test.h \ + MessagingSessionTests.cpp \ + MessagingThreadTests.cpp \ + MessagingFixture.h \ + ClientSessionTest.cpp \ + BrokerFixture.h SocketProxy.h \ + exception_test.cpp \ + RefCounted.cpp \ + SessionState.cpp logging.cpp \ + AsyncCompletion.cpp \ + Url.cpp Uuid.cpp \ + Shlib.cpp FieldValue.cpp FieldTable.cpp Array.cpp \ + QueueOptionsTest.cpp \ + InlineAllocator.cpp \ + InlineVector.cpp \ + SequenceSet.cpp \ + StringUtils.cpp \ + RangeSet.cpp \ + AtomicValue.cpp \ + QueueTest.cpp \ + AccumulatedAckTest.cpp \ + DtxWorkRecordTest.cpp \ + DeliveryRecordTest.cpp \ + ExchangeTest.cpp \ + HeadersExchangeTest.cpp \ + MessageTest.cpp \ + QueueRegistryTest.cpp \ + QueuePolicyTest.cpp \ + QueueFlowLimitTest.cpp \ + FramingTest.cpp \ + HeaderTest.cpp \ + SequenceNumberTest.cpp \ + TimerTest.cpp \ + TopicExchangeTest.cpp \ + TxBufferTest.cpp \ + TxPublishTest.cpp \ + MessageBuilderTest.cpp \ + ConnectionOptions.h \ + ForkedBroker.h \ + ForkedBroker.cpp \ + ManagementTest.cpp \ + MessageReplayTracker.cpp \ + ConsoleTest.cpp \ + QueueEvents.cpp \ + ProxyTest.cpp \ + RetryList.cpp \ + RateFlowcontrolTest.cpp \ + FrameDecoder.cpp \ + ReplicationTest.cpp \ + ClientMessageTest.cpp \ + PollableCondition.cpp \ + Variant.cpp \ + Address.cpp \ + ClientMessage.cpp \ + Qmf2.cpp + +if HAVE_XML +unit_test_SOURCES+= XmlClientSessionTest.cpp +endif + +TESTLIBFLAGS = -module -rpath $(abs_builddir) + +check_LTLIBRARIES += libshlibtest.la +libshlibtest_la_LDFLAGS = $(TESTLIBFLAGS) +libshlibtest_la_SOURCES = shlibtest.cpp + +tmodule_LTLIBRARIES += test_store.la +test_store_la_SOURCES = test_store.cpp +test_store_la_LIBADD = $(lib_broker) +test_store_la_LDFLAGS = -module + +include cluster.mk +include sasl.mk +if SSL +include ssl.mk +endif + +# Test programs that are installed and therefore built as part of make, not make check + +qpidtest_SCRIPTS += qpid-cpp-benchmark install_env.sh +EXTRA_DIST += qpid-cpp-benchmark install_env.sh + +qpidtest_PROGRAMS += receiver +receiver_SOURCES = \ + receiver.cpp \ + TestOptions.h \ + ConnectionOptions.h +receiver_LDADD = $(lib_client) + +qpidtest_PROGRAMS += sender +sender_SOURCES = \ + sender.cpp \ + TestOptions.h \ + ConnectionOptions.h \ + Statistics.cpp +sender_LDADD = $(lib_messaging) + +qpidtest_PROGRAMS += qpid-receive +qpid_receive_SOURCES = \ + qpid-receive.cpp \ + TestOptions.h \ + ConnectionOptions.h \ + Statistics.h \ + Statistics.cpp +qpid_receive_LDADD = $(lib_messaging) + +qpidtest_PROGRAMS += qpid-send +qpid_send_SOURCES = \ + qpid-send.cpp \ + TestOptions.h \ + ConnectionOptions.h \ + Statistics.h \ + Statistics.cpp +qpid_send_LDADD = $(lib_messaging) + +qpidtest_PROGRAMS+=qpid-perftest +qpid_perftest_SOURCES=qpid-perftest.cpp test_tools.h TestOptions.h ConnectionOptions.h +qpid_perftest_INCLUDES=$(PUBLIC_INCLUDES) +qpid_perftest_LDADD=$(lib_client) + +qpidtest_PROGRAMS+=qpid-txtest +qpid_txtest_INCLUDES=$(PUBLIC_INCLUDES) +qpid_txtest_SOURCES=qpid-txtest.cpp TestOptions.h ConnectionOptions.h +qpid_txtest_LDADD=$(lib_client) + +qpidtest_PROGRAMS+=qpid-latency-test +qpid_latency_test_INCLUDES=$(PUBLIC_INCLUDES) +qpid_latency_test_SOURCES=qpid-latency-test.cpp TestOptions.h ConnectionOptions.h +qpid_latency_test_LDADD=$(lib_client) + +qpidtest_PROGRAMS+=qpid-client-test +qpid_client_test_INCLUDES=$(PUBLIC_INCLUDES) +qpid_client_test_SOURCES=qpid-client-test.cpp TestOptions.h ConnectionOptions.h +qpid_client_test_LDADD=$(lib_client) + +qpidtest_PROGRAMS+=qpid-topic-listener +qpid_topic_listener_INCLUDES=$(PUBLIC_INCLUDES) +qpid_topic_listener_SOURCES=qpid-topic-listener.cpp TestOptions.h ConnectionOptions.h +qpid_topic_listener_LDADD=$(lib_client) + +qpidtest_PROGRAMS+=qpid-topic-publisher +qpid_topic_publisher_INCLUDES=$(PUBLIC_INCLUDES) +qpid_topic_publisher_SOURCES=qpid-topic-publisher.cpp TestOptions.h ConnectionOptions.h +qpid_topic_publisher_LDADD=$(lib_client) + +qpidtest_PROGRAMS+=qpid-ping +qpid_ping_INCLUDES=$(PUBLIC_INCLUDES) +qpid_ping_SOURCES=qpid-ping.cpp test_tools.h TestOptions.h ConnectionOptions.h +qpid_ping_LDADD=$(lib_client) + +# +# Other test programs +# + +check_PROGRAMS+=echotest +echotest_INCLUDES=$(PUBLIC_INCLUDES) +echotest_SOURCES=echotest.cpp TestOptions.h ConnectionOptions.h +echotest_LDADD=$(lib_client) + +check_PROGRAMS+=publish +publish_INCLUDES=$(PUBLIC_INCLUDES) +publish_SOURCES=publish.cpp TestOptions.h ConnectionOptions.h +publish_LDADD=$(lib_client) + +check_PROGRAMS+=consume +consume_INCLUDES=$(PUBLIC_INCLUDES) +consume_SOURCES=consume.cpp TestOptions.h ConnectionOptions.h +consume_LDADD=$(lib_client) + +check_PROGRAMS+=header_test +header_test_INCLUDES=$(PUBLIC_INCLUDES) +header_test_SOURCES=header_test.cpp TestOptions.h ConnectionOptions.h +header_test_LDADD=$(lib_client) + +check_PROGRAMS+=failover_soak +failover_soak_INCLUDES=$(PUBLIC_INCLUDES) +failover_soak_SOURCES=failover_soak.cpp ForkedBroker.h ForkedBroker.cpp +failover_soak_LDADD=$(lib_client) $(lib_broker) + +check_PROGRAMS+=declare_queues +declare_queues_INCLUDES=$(PUBLIC_INCLUDES) +declare_queues_SOURCES=declare_queues.cpp +declare_queues_LDADD=$(lib_client) + +check_PROGRAMS+=replaying_sender +replaying_sender_INCLUDES=$(PUBLIC_INCLUDES) +replaying_sender_SOURCES=replaying_sender.cpp +replaying_sender_LDADD=$(lib_client) + +check_PROGRAMS+=resuming_receiver +resuming_receiver_INCLUDES=$(PUBLIC_INCLUDES) +resuming_receiver_SOURCES=resuming_receiver.cpp +resuming_receiver_LDADD=$(lib_client) + +check_PROGRAMS+=txshift +txshift_INCLUDES=$(PUBLIC_INCLUDES) +txshift_SOURCES=txshift.cpp TestOptions.h ConnectionOptions.h +txshift_LDADD=$(lib_client) + +check_PROGRAMS+=txjob +txjob_INCLUDES=$(PUBLIC_INCLUDES) +txjob_SOURCES=txjob.cpp TestOptions.h ConnectionOptions.h +txjob_LDADD=$(lib_client) + +check_PROGRAMS+=PollerTest +PollerTest_SOURCES=PollerTest.cpp +PollerTest_LDADD=$(lib_common) $(lib_client) $(SOCKLIBS) + +check_PROGRAMS+=DispatcherTest +DispatcherTest_SOURCES=DispatcherTest.cpp +DispatcherTest_LDADD=$(lib_common) $(lib_client) $(SOCKLIBS) + +check_PROGRAMS+=datagen +datagen_SOURCES=datagen.cpp +datagen_LDADD=$(lib_common) $(lib_client) + +check_PROGRAMS+=qpid-stream +qpid_stream_INCLUDES=$(PUBLIC_INCLUDES) +qpid_stream_SOURCES=qpid-stream.cpp +qpid_stream_LDADD=$(lib_messaging) + +TESTS_ENVIRONMENT = \ + VALGRIND=$(VALGRIND) \ + LIBTOOL="$(LIBTOOL)" \ + QPID_DATA_DIR= \ + $(srcdir)/run_test + +system_tests = qpid-client-test quick_perftest quick_topictest run_header_test quick_txtest +TESTS += start_broker $(system_tests) python_tests stop_broker run_federation_tests \ + run_acl_tests run_cli_tests replication_test dynamic_log_level_test \ + run_queue_flow_limit_tests + +EXTRA_DIST += \ + run_test vg_check \ + run-unit-tests start_broker python_tests stop_broker \ + quick_topictest \ + quick_perftest \ + quick_txtest \ + topictest \ + run_header_test \ + header_test.py \ + ssl_test \ + config.null \ + ais_check \ + run_federation_tests \ + run_cli_tests \ + run_acl_tests \ + .valgrind.supp \ + MessageUtils.h \ + TestMessageStore.h \ + TxMocks.h \ + replication_test \ + run_perftest \ + ring_queue_test \ + run_ring_queue_test \ + dynamic_log_level_test \ + qpid-ctrl \ + CMakeLists.txt \ + cluster.cmake \ + windows/DisableWin32ErrorWindows.cpp \ + background.ps1 \ + find_prog.ps1 \ + python_tests.ps1 \ + quick_topictest.ps1 \ + run_federation_tests.ps1 \ + run_header_test.ps1 \ + run_test.ps1 \ + start_broker.ps1 \ + stop_broker.ps1 \ + topictest.ps1 \ + run_queue_flow_limit_tests + +check_LTLIBRARIES += libdlclose_noop.la +libdlclose_noop_la_LDFLAGS = -module -rpath $(abs_builddir) +libdlclose_noop_la_SOURCES = dlclose_noop.c + +CLEANFILES+=valgrind.out *.log *.vglog* dummy_test qpidd.port $(unit_wrappers) + +# Longer running stability tests, not run by default check: target. +# Not run under valgrind, too slow + +LONG_TESTS+=start_broker fanout_perftest shared_perftest multiq_perftest topic_perftest run_ring_queue_test stop_broker \ + run_failover_soak reliable_replication_test \ + federated_cluster_test_with_node_failure + +EXTRA_DIST+= \ + fanout_perftest \ + shared_perftest \ + multiq_perftest \ + topic_perftest \ + run_failover_soak \ + reliable_replication_test \ + federated_cluster_test_with_node_failure \ + sasl_test_setup.sh + +check-long: + $(MAKE) check TESTS="$(LONG_TESTS)" VALGRIND= + +# Things that should be built before the check target runs. +check-am: python_prep test_env.sh install_env.sh sasl_config + +PYTHON_SRC_DIR=$(abs_srcdir)/../../../python +PYTHON_BLD_DIR=$(abs_builddir)/python + +# Generate python client as part of the all-am target so it gets built before tests. +all-am: python_prep + +python_prep: + if test -d $(PYTHON_SRC_DIR); \ + then cd $(PYTHON_SRC_DIR) && python $(PYTHON_SRC_DIR)/setup.py install \ + --prefix=$(PYTHON_BLD_DIR) --install-lib=$(PYTHON_BLD_DIR) \ + --install-scripts=$(PYTHON_BLD_DIR)/commands; \ + else echo "WARNING: python client not built, missing $(PYTHON_SRC_DIR)"; fi + +sasl_config: sasl_test_setup.sh + sh $(srcdir)/sasl_test_setup.sh + touch sasl_config + +CLEAN_LOCAL += sasl_config + +clean-local: + rm -rf $(CLEAN_LOCAL) + +include testagent.mk +include brokermgmt.mk + diff --git a/qpid/cpp/src/tests/ManagementTest.cpp b/qpid/cpp/src/tests/ManagementTest.cpp new file mode 100644 index 0000000000..8944c084c0 --- /dev/null +++ b/qpid/cpp/src/tests/ManagementTest.cpp @@ -0,0 +1,124 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 "qpid/console/ObjectId.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_CASE(testConsoleObjectId) { + qpid::console::ObjectId oid1, oid2; + + oid1.setValue(1, 2); + oid2.setValue(3, 4); + + BOOST_CHECK(oid1 < oid2); + BOOST_CHECK(oid1 <= oid2); + BOOST_CHECK(oid2 > oid1); + BOOST_CHECK(oid2 >= oid1); + BOOST_CHECK(oid1 != oid2); + BOOST_CHECK(oid1 == oid1); + + oid1.setValue(3, 6); + oid2.setValue(3, 4); + + BOOST_CHECK(oid1 > oid2); + BOOST_CHECK(oid1 >= oid2); + BOOST_CHECK(oid2 < oid1); + BOOST_CHECK(oid2 <= oid1); + BOOST_CHECK(oid1 != oid2); + + oid2.setValue(3, 6); + BOOST_CHECK(oid1 == oid2); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/MessageBuilderTest.cpp b/qpid/cpp/src/tests/MessageBuilderTest.cpp new file mode 100644 index 0000000000..c3d40ed88a --- /dev/null +++ b/qpid/cpp/src/tests/MessageBuilderTest.cpp @@ -0,0 +1,190 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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/MessageBuilder.h" +#include "qpid/broker/NullMessageStore.h" +#include "qpid/framing/frame_functors.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/framing/TypeFilter.h" +#include "unit_test.h" +#include <list> + +using namespace qpid::broker; +using namespace qpid::framing; +using namespace qpid::sys; + +namespace qpid { +namespace tests { + +class MockMessageStore : public NullMessageStore +{ + enum Op {STAGE=1, APPEND=2}; + + uint64_t id; + boost::intrusive_ptr<PersistableMessage> expectedMsg; + string expectedData; + std::list<Op> ops; + + void checkExpectation(Op actual) + { + BOOST_CHECK_EQUAL(ops.front(), actual); + ops.pop_front(); + } + + public: + MockMessageStore() : id(0), expectedMsg(0) {} + + void expectStage(PersistableMessage& msg) + { + expectedMsg = &msg; + ops.push_back(STAGE); + } + + void expectAppendContent(PersistableMessage& msg, const string& data) + { + expectedMsg = &msg; + expectedData = data; + ops.push_back(APPEND); + } + + void stage(const boost::intrusive_ptr<PersistableMessage>& msg) + { + checkExpectation(STAGE); + BOOST_CHECK_EQUAL(expectedMsg, msg); + msg->setPersistenceId(++id); + } + + void appendContent(const boost::intrusive_ptr<const PersistableMessage>& msg, + const string& data) + { + checkExpectation(APPEND); + BOOST_CHECK_EQUAL(boost::static_pointer_cast<const PersistableMessage>(expectedMsg), msg); + BOOST_CHECK_EQUAL(expectedData, data); + } + + bool expectationsMet() + { + return ops.empty(); + } + + //don't treat this store as a null impl + bool isNull() const + { + return false; + } + +}; + +QPID_AUTO_TEST_SUITE(MessageBuilderTestSuite) + +QPID_AUTO_TEST_CASE(testHeaderOnly) +{ + MessageBuilder builder(0); + builder.start(SequenceNumber()); + + std::string exchange("builder-exchange"); + std::string key("builder-exchange"); + + AMQFrame method((MessageTransferBody(ProtocolVersion(), exchange, 0, 0))); + AMQFrame header((AMQHeaderBody())); + + header.castBody<AMQHeaderBody>()->get<MessageProperties>(true)->setContentLength(0); + header.castBody<AMQHeaderBody>()->get<DeliveryProperties>(true)->setRoutingKey(key); + + builder.handle(method); + builder.handle(header); + + BOOST_CHECK(builder.getMessage()); + BOOST_CHECK_EQUAL(exchange, builder.getMessage()->getExchangeName()); + BOOST_CHECK_EQUAL(key, builder.getMessage()->getRoutingKey()); + BOOST_CHECK(builder.getMessage()->getFrames().isComplete()); +} + +QPID_AUTO_TEST_CASE(test1ContentFrame) +{ + MessageBuilder builder(0); + builder.start(SequenceNumber()); + + std::string data("abcdefg"); + std::string exchange("builder-exchange"); + std::string key("builder-exchange"); + + AMQFrame method((MessageTransferBody(ProtocolVersion(), exchange, 0, 0))); + AMQFrame header((AMQHeaderBody())); + AMQFrame content((AMQContentBody(data))); + method.setEof(false); + header.setBof(false); + header.setEof(false); + content.setBof(false); + + header.castBody<AMQHeaderBody>()->get<MessageProperties>(true)->setContentLength(data.size()); + header.castBody<AMQHeaderBody>()->get<DeliveryProperties>(true)->setRoutingKey(key); + + builder.handle(method); + BOOST_CHECK(builder.getMessage()); + BOOST_CHECK(!builder.getMessage()->getFrames().isComplete()); + + builder.handle(header); + BOOST_CHECK(builder.getMessage()); + BOOST_CHECK(!builder.getMessage()->getFrames().isComplete()); + + builder.handle(content); + BOOST_CHECK(builder.getMessage()); + BOOST_CHECK(builder.getMessage()->getFrames().isComplete()); +} + +QPID_AUTO_TEST_CASE(test2ContentFrames) +{ + MessageBuilder builder(0); + builder.start(SequenceNumber()); + + std::string data1("abcdefg"); + std::string data2("hijklmn"); + std::string exchange("builder-exchange"); + std::string key("builder-exchange"); + + AMQFrame method((MessageTransferBody(ProtocolVersion(), exchange, 0, 0))); + AMQFrame header((AMQHeaderBody())); + AMQFrame content1((AMQContentBody(data1))); + AMQFrame content2((AMQContentBody(data2))); + method.setEof(false); + header.setBof(false); + header.setEof(false); + content1.setBof(false); + content1.setEof(false); + content2.setBof(false); + + header.castBody<AMQHeaderBody>()->get<MessageProperties>(true)->setContentLength(data1.size() + data2.size()); + header.castBody<AMQHeaderBody>()->get<DeliveryProperties>(true)->setRoutingKey(key); + + builder.handle(method); + builder.handle(header); + builder.handle(content1); + BOOST_CHECK(builder.getMessage()); + BOOST_CHECK(!builder.getMessage()->getFrames().isComplete()); + + builder.handle(content2); + BOOST_CHECK(builder.getMessage()); + BOOST_CHECK(builder.getMessage()->getFrames().isComplete()); +} +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..3d79ee53c2 --- /dev/null +++ b/qpid/cpp/src/tests/MessageReplayTracker.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 "unit_test.h" +#include "BrokerFixture.h" +#include "qpid/client/MessageReplayTracker.h" +#include "qpid/sys/Time.h" + +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) +{ + ProxySessionFixture 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) +{ + ProxySessionFixture 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..7d67c92b37 --- /dev/null +++ b/qpid/cpp/src/tests/MessageTest.cpp @@ -0,0 +1,92 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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/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 "qpid/sys/alloca.h" + +#include "unit_test.h" + +#include <iostream> + +using namespace qpid::broker; +using namespace qpid::framing; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(MessageTestSuite) + +QPID_AUTO_TEST_CASE(testEncodeDecode) +{ + string exchange = "MyExchange"; + string routingKey = "MyRoutingKey"; + Uuid messageId(true); + string data1("abcdefg"); + string data2("hijklmn"); + + boost::intrusive_ptr<Message> msg(new Message()); + + AMQFrame method((MessageTransferBody(ProtocolVersion(), exchange, 0, 0))); + AMQFrame header((AMQHeaderBody())); + AMQFrame content1((AMQContentBody(data1))); + AMQFrame content2((AMQContentBody(data2))); + + msg->getFrames().append(method); + msg->getFrames().append(header); + msg->getFrames().append(content1); + msg->getFrames().append(content2); + + MessageProperties* mProps = msg->getFrames().getHeaders()->get<MessageProperties>(true); + mProps->setContentLength(data1.size() + data2.size()); + mProps->setMessageId(messageId); + FieldTable applicationHeaders; + applicationHeaders.setString("abc", "xyz"); + mProps->setApplicationHeaders(applicationHeaders); + DeliveryProperties* dProps = msg->getFrames().getHeaders()->get<DeliveryProperties>(true); + dProps->setRoutingKey(routingKey); + dProps->setDeliveryMode(PERSISTENT); + BOOST_CHECK(msg->isPersistent()); + + char* buff = static_cast<char*>(::alloca(msg->encodedSize())); + Buffer wbuffer(buff, msg->encodedSize()); + msg->encode(wbuffer); + + Buffer rbuffer(buff, msg->encodedSize()); + msg = new Message(); + msg->decodeHeader(rbuffer); + msg->decodeContent(rbuffer); + BOOST_CHECK_EQUAL(exchange, msg->getExchangeName()); + BOOST_CHECK_EQUAL(routingKey, msg->getRoutingKey()); + BOOST_CHECK_EQUAL((uint64_t) data1.size() + data2.size(), msg->contentSize()); + BOOST_CHECK_EQUAL((uint64_t) data1.size() + data2.size(), msg->getProperties<MessageProperties>()->getContentLength()); + BOOST_CHECK_EQUAL(messageId, msg->getProperties<MessageProperties>()->getMessageId()); + BOOST_CHECK_EQUAL(string("xyz"), msg->getProperties<MessageProperties>()->getApplicationHeaders().getAsString("abc")); + BOOST_CHECK_EQUAL((uint8_t) PERSISTENT, msg->getProperties<DeliveryProperties>()->getDeliveryMode()); + BOOST_CHECK(msg->isPersistent()); +} + +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..a1b140d484 --- /dev/null +++ b/qpid/cpp/src/tests/MessageUtils.h @@ -0,0 +1,63 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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/framing/AMQFrame.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/framing/Uuid.h" + +using namespace qpid; +using namespace broker; +using namespace framing; + +namespace qpid { +namespace tests { + +struct MessageUtils +{ + static boost::intrusive_ptr<Message> createMessage(const string& exchange="", const string& routingKey="", + const bool durable = false, const Uuid& messageId=Uuid(true), + uint64_t contentSize = 0) + { + boost::intrusive_ptr<broker::Message> msg(new broker::Message()); + + 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); + msg->getFrames().getHeaders()->get<DeliveryProperties>(true)->setRoutingKey(routingKey); + if (durable) + msg->getFrames().getHeaders()->get<DeliveryProperties>(true)->setDeliveryMode(2); + return msg; + } + + static void addContent(boost::intrusive_ptr<Message> msg, const string& data) + { + AMQFrame content((AMQContentBody(data))); + msg->getFrames().append(content); + } +}; + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/MessagingFixture.h b/qpid/cpp/src/tests/MessagingFixture.h new file mode 100644 index 0000000000..2312a87e9d --- /dev/null +++ b/qpid/cpp/src/tests/MessagingFixture.h @@ -0,0 +1,345 @@ +#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" + +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(Broker::Options opts = Broker::Options(), 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) + { + Variant::Map content; + Variant::Map objectId; + objectId["_object_name"] = "org.apache.qpid.broker:broker:amqp-broker"; + 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/MessagingSessionTests.cpp b/qpid/cpp/src/tests/MessagingSessionTests.cpp new file mode 100644 index 0000000000..6aa4c63ed7 --- /dev/null +++ b/qpid/cpp/src/tests/MessagingSessionTests.cpp @@ -0,0 +1,997 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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::Broker; +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); + } + 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(); + } +} + +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 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("#; " + 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(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()); + 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(Broker::Options(), 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_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/PartialFailure.cpp b/qpid/cpp/src/tests/PartialFailure.cpp new file mode 100644 index 0000000000..63ee28017a --- /dev/null +++ b/qpid/cpp/src/tests/PartialFailure.cpp @@ -0,0 +1,291 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 Tests for partial failure in a cluster. + * Partial failure means some nodes experience a failure while others do not. + * In this case the failed nodes must shut down. + */ + +#include "test_tools.h" +#include "unit_test.h" +#include "ClusterFixture.h" +#include <boost/assign.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/bind.hpp> + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(PartialFailureTestSuite) + +using namespace std; +using namespace qpid; +using namespace qpid::cluster; +using namespace qpid::framing; +using namespace qpid::client; +using namespace qpid::client::arg; +using namespace boost::assign; +using broker::Broker; +using boost::shared_ptr; + +// Timeout for tests that wait for messages +const sys::Duration TIMEOUT=sys::TIME_SEC/4; + +static bool isLogOption(const std::string& s) { return boost::starts_with(s, "--log-enable"); } + +void updateArgs(ClusterFixture::Args& args, size_t index) { + ostringstream clusterLib, testStoreLib, storeName; + clusterLib << getLibPath("CLUSTER_LIB"); + testStoreLib << getLibPath("TEST_STORE_LIB"); + storeName << "s" << index; + args.push_back("--auth"); + args.push_back("no"); + args.push_back("--no-module-dir"); + args.push_back("--load-module"); + args.push_back(clusterLib.str()); + args.push_back("--load-module"); + args.push_back(testStoreLib.str()); + args.push_back("--test-store-name"); + args.push_back(storeName.str()); + args.push_back("TMP_DATA_DIR"); + + // These tests generate errors deliberately, disable error logging unless a log env var is set. + if (!::getenv("QPID_TRACE") && !::getenv("QPID_LOG_ENABLE")) { + remove_if(args.begin(), args.end(), isLogOption); + args.push_back("--log-enable=critical+:DISABLED"); // hacky way to disable logs. + } +} + +Message pMessage(string data, string q) { + Message msg(data, q); + msg.getDeliveryProperties().setDeliveryMode(PERSISTENT); + return msg; +} + +void queueAndSub(Client& c) { + c.session.queueDeclare(c.name, durable=true); + c.subs.subscribe(c.lq, c.name); +} + +// Handle near-simultaneous errors +QPID_AUTO_TEST_CASE(testCoincidentErrors) { + ClusterFixture cluster(2, updateArgs, -1); + Client c0(cluster[0], "c0"); + Client c1(cluster[1], "c1"); + + c0.session.queueDeclare("q", durable=true); + { + ScopedSuppressLogging allQuiet; + async(c0.session).messageTransfer(content=pMessage("TEST_STORE_DO: s0[exception]", "q")); + async(c1.session).messageTransfer(content=pMessage("TEST_STORE_DO: s1[exception]", "q")); + + int alive=0; + try { Client c00(cluster[0], "c00"); ++alive; c00.close(); } catch (...) {} + try { Client c11(cluster[1], "c11"); ++alive; c11.close(); } catch (...) {} + + BOOST_CHECK_EQUAL(alive, 1); + + // Close inside ScopedSuppressLogging to avoid warnings + c0.close(); + c1.close(); + } +} + +// Verify normal cluster-wide errors. +QPID_AUTO_TEST_CASE(testNormalErrors) { + // FIXME aconway 2009-04-10: Would like to put a scope just around + // the statements expected to fail (in BOOST_CHECK_yTHROW) but that + // sproadically lets out messages, possibly because they're in + // Connection thread. + + ClusterFixture cluster(3, updateArgs, -1); + Client c0(cluster[0], "c0"); + Client c1(cluster[1], "c1"); + Client c2(cluster[2], "c2"); + + { + ScopedSuppressLogging allQuiet; + queueAndSub(c0); + c0.session.messageTransfer(content=Message("x", "c0")); + BOOST_CHECK_EQUAL(c0.lq.get(TIMEOUT).getData(), "x"); + + // Session error. + BOOST_CHECK_THROW(c0.session.exchangeBind(), SessionException); + c1.session.messageTransfer(content=Message("stay", "c0")); // Will stay on queue, session c0 is dead. + + // Connection error, kill c1 on all members. + queueAndSub(c1); + BOOST_CHECK_THROW( + c1.session.messageTransfer( + content=pMessage("TEST_STORE_DO: s0[exception] s1[exception] s2[exception] testNormalErrors", "c1")), + ConnectionException); + c2.session.messageTransfer(content=Message("stay", "c1")); // Will stay on queue, session/connection c1 is dead. + + BOOST_CHECK_EQUAL(3u, knownBrokerPorts(c2.connection, 3).size()); + BOOST_CHECK_EQUAL(c2.subs.get("c0", TIMEOUT).getData(), "stay"); + BOOST_CHECK_EQUAL(c2.subs.get("c1", TIMEOUT).getData(), "stay"); + + // Close inside ScopedSuppressLogging to avoid warnings + c0.close(); + c1.close(); + c2.close(); + } +} + + +// Test errors after a new member joins to verify frame-sequence-numbers are ok in update. +QPID_AUTO_TEST_CASE(testErrorAfterJoin) { + ClusterFixture cluster(1, updateArgs, -1); + Client c0(cluster[0]); + { + ScopedSuppressLogging allQuiet; + + c0.session.queueDeclare("q", durable=true); + c0.session.messageTransfer(content=pMessage("a", "q")); + + // Kill the new guy + cluster.add(); + Client c1(cluster[1]); + c0.session.messageTransfer(content=pMessage("TEST_STORE_DO: s1[exception] testErrorAfterJoin", "q")); + BOOST_CHECK_THROW(c1.session.messageTransfer(content=pMessage("xxx", "q")), TransportFailure); + BOOST_CHECK_EQUAL(1u, knownBrokerPorts(c0.connection, 1).size()); + + // Kill the old guy + cluster.add(); + Client c2(cluster[2]); + c2.session.messageTransfer(content=pMessage("TEST_STORE_DO: s0[exception] testErrorAfterJoin2", "q")); + BOOST_CHECK_THROW(c0.session.messageTransfer(content=pMessage("xxx", "q")), TransportFailure); + + BOOST_CHECK_EQUAL(1u, knownBrokerPorts(c2.connection, 1).size()); + + // Close inside ScopedSuppressLogging to avoid warnings + c0.close(); + c1.close(); + c2.close(); + } +} + +// Test that if one member fails and others do not, the failure leaves the cluster. +QPID_AUTO_TEST_CASE(testSinglePartialFailure) { + ClusterFixture cluster(3, updateArgs, -1); + Client c0(cluster[0], "c0"); + Client c1(cluster[1], "c1"); + Client c2(cluster[2], "c2"); + + { + ScopedSuppressLogging allQuiet; + + c0.session.queueDeclare("q", durable=true); + c0.session.messageTransfer(content=pMessage("a", "q")); + // Cause partial failure on c1 + c0.session.messageTransfer(content=pMessage("TEST_STORE_DO: s1[exception] testSinglePartialFailure", "q")); + BOOST_CHECK_THROW(c1.session.queueQuery("q"), TransportFailure); + + c0.session.messageTransfer(content=pMessage("b", "q")); + BOOST_CHECK_EQUAL(c0.session.queueQuery("q").getMessageCount(), 3u); + BOOST_CHECK_EQUAL(2u, knownBrokerPorts(c0.connection, 2).size()); + + // Cause partial failure on c2 + c0.session.messageTransfer(content=pMessage("TEST_STORE_DO: s2[exception] testSinglePartialFailure2", "q")); + BOOST_CHECK_THROW(c2.session.queueQuery("q"), TransportFailure); + + c0.session.messageTransfer(content=pMessage("c", "q")); + BOOST_CHECK_EQUAL(c0.session.queueQuery("q").getMessageCount(), 5u); + BOOST_CHECK_EQUAL(1u, knownBrokerPorts(c0.connection, 1).size()); + + // Close inside ScopedSuppressLogging to avoid warnings + c0.close(); + c1.close(); + c2.close(); + } +} + +// Test multiple partial falures: 2 fail 2 pass +QPID_AUTO_TEST_CASE(testMultiPartialFailure) { + ClusterFixture cluster(4, updateArgs, -1); + Client c0(cluster[0], "c0"); + Client c1(cluster[1], "c1"); + Client c2(cluster[2], "c2"); + Client c3(cluster[3], "c3"); + + { + ScopedSuppressLogging allQuiet; + + c0.session.queueDeclare("q", durable=true); + c0.session.messageTransfer(content=pMessage("a", "q")); + + // Cause partial failure on c1, c2 + c0.session.messageTransfer(content=pMessage("TEST_STORE_DO: s1[exception] s2[exception] testMultiPartialFailure", "q")); + BOOST_CHECK_THROW(c1.session.queueQuery("q"), TransportFailure); + BOOST_CHECK_THROW(c2.session.queueQuery("q"), TransportFailure); + + c0.session.messageTransfer(content=pMessage("b", "q")); + c3.session.messageTransfer(content=pMessage("c", "q")); + BOOST_CHECK_EQUAL(c3.session.queueQuery("q").getMessageCount(), 4u); + // FIXME aconway 2009-06-30: This check fails sporadically with 2 != 3. + // It should pass reliably. + // BOOST_CHECK_EQUAL(2u, knownBrokerPorts(c0.connection, 2).size()); + + // Close inside ScopedSuppressLogging to avoid warnings + c0.close(); + c1.close(); + c2.close(); + c3.close(); + } +} + +/** FIXME aconway 2009-04-10: + * The current approach to shutting down a process in test_store + * sometimes leads to assertion failures and errors in the shut-down + * process. Need a cleaner solution + */ +#if 0 +QPID_AUTO_TEST_CASE(testPartialFailureMemberLeaves) { + ClusterFixture cluster(2, updateArgs, -1); + Client c0(cluster[0], "c0"); + Client c1(cluster[1], "c1"); + + { + ScopedSuppressLogging allQuiet; + + c0.session.queueDeclare("q", durable=true); + c0.session.messageTransfer(content=pMessage("a", "q")); + + // Cause failure on member 0 and simultaneous crash on member 1. + BOOST_CHECK_THROW( + c0.session.messageTransfer( + content=pMessage("TEST_STORE_DO: s0[exception] s1[exit_process] testPartialFailureMemberLeaves", "q")), + ConnectionException); + cluster.wait(1); + + Client c00(cluster[0], "c00"); // Old connection is dead. + BOOST_CHECK_EQUAL(c00.session.queueQuery("q").getMessageCount(), 1u); + BOOST_CHECK_EQUAL(1u, knownBrokerPorts(c00.connection, 1).size()); + + // Close inside ScopedSuppressLogging to avoid warnings + c0.close(); + } +} +#endif + +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..9fa5689c5f --- /dev/null +++ b/qpid/cpp/src/tests/PollerTest.cpp @@ -0,0 +1,263 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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/IOHandle.h" +#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); + + PosixIOHandle f0(sv[0]); + PosixIOHandle 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); + + PosixIOHandle f2(sv[0]); + PosixIOHandle 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..66c774accd --- /dev/null +++ b/qpid/cpp/src/tests/Qmf2.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 <iostream> +#include "qpid/types/Variant.h" +#include "qmf/QueryImpl.h" +#include "qmf/SchemaImpl.h" +#include "qmf/exceptions.h" + +#include "unit_test.h" + +using namespace qpid::types; +using namespace qmf; + +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_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/QueueEvents.cpp b/qpid/cpp/src/tests/QueueEvents.cpp new file mode 100644 index 0000000000..bd18fa45fb --- /dev/null +++ b/qpid/cpp/src/tests/QueueEvents.cpp @@ -0,0 +1,238 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 "BrokerFixture.h" +#include "qpid/broker/Message.h" +#include "qpid/broker/Queue.h" +#include "qpid/broker/QueueEvents.h" +#include "qpid/client/QueueOptions.h" +#include "qpid/framing/SequenceNumber.h" +#include "qpid/sys/Dispatcher.h" +#include <boost/bind.hpp> +#include <boost/format.hpp> + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(QueueEventsSuite) + +using namespace qpid::client; +using namespace qpid::broker; +using namespace qpid::sys; +using qpid::framing::SequenceNumber; + +struct EventChecker +{ + typedef std::deque<QueueEvents::Event> Events; + + Events events; + boost::shared_ptr<Poller> poller; + + void handle(QueueEvents::Event e) + { + if (events.empty()) { + BOOST_FAIL("Unexpected event received"); + } else { + BOOST_CHECK_EQUAL(events.front().type, e.type); + BOOST_CHECK_EQUAL(events.front().msg.queue, e.msg.queue); + BOOST_CHECK_EQUAL(events.front().msg.payload, e.msg.payload); + BOOST_CHECK_EQUAL(events.front().msg.position, e.msg.position); + events.pop_front(); + } + if (events.empty() && poller) poller->shutdown(); + } + + void expect(QueueEvents::Event e) + { + events.push_back(e); + } +}; + +QPID_AUTO_TEST_CASE(testBasicEventProcessing) +{ + boost::shared_ptr<Poller> poller(new Poller()); + sys::Dispatcher dispatcher(poller); + Thread dispatchThread(dispatcher); + QueueEvents events(poller); + EventChecker listener; + listener.poller = poller; + events.registerListener("dummy", boost::bind(&EventChecker::handle, &listener, _1)); + //signal occurence of some events: + Queue queue("queue1"); + SequenceNumber id; + QueuedMessage event1(&queue, MessageUtils::createMessage(), id); + QueuedMessage event2(&queue, MessageUtils::createMessage(), ++id); + + //define events expected by listener: + listener.expect(QueueEvents::Event(QueueEvents::ENQUEUE, event1)); + listener.expect(QueueEvents::Event(QueueEvents::ENQUEUE, event2)); + listener.expect(QueueEvents::Event(QueueEvents::DEQUEUE, event1)); + + events.enqueued(event1); + events.enqueued(event2); + events.dequeued(event1); + + dispatchThread.join(); + events.shutdown(); + events.unregisterListener("dummy"); +} + + +struct EventRecorder +{ + struct EventRecord + { + QueueEvents::EventType type; + std::string queue; + std::string content; + SequenceNumber position; + }; + + typedef std::deque<EventRecord> Events; + + Events events; + + void handle(QueueEvents::Event event) + { + EventRecord record; + record.type = event.type; + record.queue = event.msg.queue->getName(); + event.msg.payload->getFrames().getContent(record.content); + record.position = event.msg.position; + events.push_back(record); + } + + void check(QueueEvents::EventType type, const std::string& queue, const std::string& content, const SequenceNumber& position) + { + if (events.empty()) { + BOOST_FAIL("Missed event"); + } else { + BOOST_CHECK_EQUAL(events.front().type, type); + BOOST_CHECK_EQUAL(events.front().queue, queue); + BOOST_CHECK_EQUAL(events.front().content, content); + BOOST_CHECK_EQUAL(events.front().position, position); + events.pop_front(); + } + } + void checkEnqueue(const std::string& queue, const std::string& data, const SequenceNumber& position) + { + check(QueueEvents::ENQUEUE, queue, data, position); + } + + void checkDequeue(const std::string& queue, const std::string& data, const SequenceNumber& position) + { + check(QueueEvents::DEQUEUE, queue, data, position); + } +}; + +QPID_AUTO_TEST_CASE(testSystemLevelEventProcessing) +{ + ProxySessionFixture fixture; + //register dummy event listener to broker + EventRecorder listener; + fixture.broker->getQueueEvents().registerListener("recorder", boost::bind(&EventRecorder::handle, &listener, _1)); + + //declare queue with event options specified + QueueOptions options; + options.enableQueueEvents(false); + std::string q("queue-events-test"); + fixture.session.queueDeclare(arg::queue=q, arg::arguments=options); + //send and consume some messages + LocalQueue incoming; + Subscription sub = fixture.subs.subscribe(incoming, q); + for (int i = 0; i < 5; i++) { + fixture.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q)); + } + for (int i = 0; i < 3; i++) { + BOOST_CHECK_EQUAL(incoming.pop().getData(), (boost::format("%1%_%2%") % "Message" % (i+1)).str()); + } + for (int i = 5; i < 10; i++) { + fixture.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q)); + } + for (int i = 3; i < 10; i++) { + BOOST_CHECK_EQUAL(incoming.pop().getData(), (boost::format("%1%_%2%") % "Message" % (i+1)).str()); + } + fixture.connection.close(); + fixture.broker->getQueueEvents().shutdown(); + + //check listener was notified of all events, and in correct order + SequenceNumber enqueueId(1); + SequenceNumber dequeueId(1); + for (int i = 0; i < 5; i++) { + listener.checkEnqueue(q, (boost::format("%1%_%2%") % "Message" % (i+1)).str(), enqueueId++); + } + for (int i = 0; i < 3; i++) { + listener.checkDequeue(q, (boost::format("%1%_%2%") % "Message" % (i+1)).str(), dequeueId++); + } + for (int i = 5; i < 10; i++) { + listener.checkEnqueue(q, (boost::format("%1%_%2%") % "Message" % (i+1)).str(), enqueueId++); + } + for (int i = 3; i < 10; i++) { + listener.checkDequeue(q, (boost::format("%1%_%2%") % "Message" % (i+1)).str(), dequeueId++); + } +} + +QPID_AUTO_TEST_CASE(testSystemLevelEventProcessing_enqueuesOnly) +{ + ProxySessionFixture fixture; + //register dummy event listener to broker + EventRecorder listener; + fixture.broker->getQueueEvents().registerListener("recorder", boost::bind(&EventRecorder::handle, &listener, _1)); + + //declare queue with event options specified + QueueOptions options; + options.enableQueueEvents(true); + std::string q("queue-events-test"); + fixture.session.queueDeclare(arg::queue=q, arg::arguments=options); + //send and consume some messages + LocalQueue incoming; + Subscription sub = fixture.subs.subscribe(incoming, q); + for (int i = 0; i < 5; i++) { + fixture.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q)); + } + for (int i = 0; i < 3; i++) { + BOOST_CHECK_EQUAL(incoming.pop().getData(), (boost::format("%1%_%2%") % "Message" % (i+1)).str()); + } + for (int i = 5; i < 10; i++) { + fixture.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q)); + } + for (int i = 3; i < 10; i++) { + BOOST_CHECK_EQUAL(incoming.pop().getData(), (boost::format("%1%_%2%") % "Message" % (i+1)).str()); + } + fixture.connection.close(); + fixture.broker->getQueueEvents().shutdown(); + + //check listener was notified of all events, and in correct order + SequenceNumber enqueueId(1); + SequenceNumber dequeueId(1); + for (int i = 0; i < 5; i++) { + listener.checkEnqueue(q, (boost::format("%1%_%2%") % "Message" % (i+1)).str(), enqueueId++); + } + for (int i = 5; i < 10; i++) { + listener.checkEnqueue(q, (boost::format("%1%_%2%") % "Message" % (i+1)).str(), enqueueId++); + } +} + +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..8a6923fb09 --- /dev/null +++ b/qpid/cpp/src/tests/QueueFlowLimitTest.cpp @@ -0,0 +1,463 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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/QueuePolicy.h" +#include "qpid/broker/QueueFlowLimit.h" +#include "qpid/sys/Time.h" +#include "qpid/framing/reply_exceptions.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(0, 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 QueueFlowLimit *getQueueFlowLimit(const qpid::framing::FieldTable& settings) + { + return QueueFlowLimit::createLimit(0, settings); + } +}; + + + +QueuedMessage createMessage(uint32_t size) +{ + static uint32_t seqNum; + QueuedMessage msg; + msg.payload = MessageUtils::createMessage(); + msg.position = ++seqNum; + MessageUtils::addContent(msg.payload, std::string (size, 'x')); + 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<QueuedMessage> msgs; + for (size_t i = 0; i < 6; i++) { + msgs.push_back(createMessage(10)); + flow->enqueued(msgs.back()); + BOOST_CHECK(!flow->isFlowControlActive()); + } + BOOST_CHECK(!flow->isFlowControlActive()); // 6 on queue + msgs.push_back(createMessage(10)); + flow->enqueued(msgs.back()); + BOOST_CHECK(!flow->isFlowControlActive()); // 7 on queue + msgs.push_back(createMessage(10)); + flow->enqueued(msgs.back()); + BOOST_CHECK(flow->isFlowControlActive()); // 8 on queue, ON + msgs.push_back(createMessage(10)); + 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, 70); + args.setUInt64(QueueFlowLimit::flowResumeSizeKey, 50); + + 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) 70, flow->getFlowStopSize()); + BOOST_CHECK_EQUAL((uint32_t) 50, flow->getFlowResumeSize()); + BOOST_CHECK(!flow->isFlowControlActive()); + BOOST_CHECK(flow->monitorFlowControl()); + + std::deque<QueuedMessage> msgs; + for (size_t i = 0; i < 6; i++) { + msgs.push_back(createMessage(10)); + flow->enqueued(msgs.back()); + BOOST_CHECK(!flow->isFlowControlActive()); + } + BOOST_CHECK(!flow->isFlowControlActive()); // 60 on queue + BOOST_CHECK_EQUAL(6u, flow->getFlowCount()); + BOOST_CHECK_EQUAL(60u, flow->getFlowSize()); + + QueuedMessage msg_9 = createMessage(9); + flow->enqueued(msg_9); + BOOST_CHECK(!flow->isFlowControlActive()); // 69 on queue + QueuedMessage tinyMsg_1 = createMessage(1); + flow->enqueued(tinyMsg_1); + BOOST_CHECK(!flow->isFlowControlActive()); // 70 on queue + + QueuedMessage tinyMsg_2 = createMessage(1); + flow->enqueued(tinyMsg_2); + BOOST_CHECK(flow->isFlowControlActive()); // 71 on queue, ON + msgs.push_back(createMessage(10)); + flow->enqueued(msgs.back()); + BOOST_CHECK(flow->isFlowControlActive()); // 81 on queue + BOOST_CHECK_EQUAL(10u, flow->getFlowCount()); + BOOST_CHECK_EQUAL(81u, flow->getFlowSize()); + + flow->dequeued(msgs.front()); + msgs.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); // 71 on queue + flow->dequeued(msgs.front()); + msgs.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); // 61 on queue + flow->dequeued(msgs.front()); + msgs.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); // 51 on queue + + flow->dequeued(tinyMsg_1); + BOOST_CHECK(flow->isFlowControlActive()); // 50 on queue + flow->dequeued(tinyMsg_2); + BOOST_CHECK(!flow->isFlowControlActive()); // 49 on queue, OFF + + flow->dequeued(msg_9); + BOOST_CHECK(!flow->isFlowControlActive()); // 40 on queue + flow->dequeued(msgs.front()); + msgs.pop_front(); + BOOST_CHECK(!flow->isFlowControlActive()); // 30 on queue + flow->dequeued(msgs.front()); + msgs.pop_front(); + BOOST_CHECK(!flow->isFlowControlActive()); // 20 on queue + BOOST_CHECK_EQUAL(2u, flow->getFlowCount()); + BOOST_CHECK_EQUAL(20u, 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, 200); + args.setUInt64(QueueFlowLimit::flowResumeSizeKey, 100); + + std::deque<QueuedMessage> msgs_1; + std::deque<QueuedMessage> msgs_10; + std::deque<QueuedMessage> msgs_50; + std::deque<QueuedMessage> msgs_100; + + QueuedMessage 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_10.push_back(createMessage(10)); + flow->enqueued(msgs_10.back()); + BOOST_CHECK(!flow->isFlowControlActive()); + } + // count:10 size:100 + + msgs_1.push_back(createMessage(1)); + flow->enqueued(msgs_1.back()); // count:11 size: 101 ->ON + BOOST_CHECK(flow->isFlowControlActive()); + + for (size_t i = 0; i < 6; i++) { + flow->dequeued(msgs_10.front()); + msgs_10.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); + } + // count:5 size: 41 + + flow->dequeued(msgs_1.front()); // count: 4 size: 40 ->OFF + msgs_1.pop_front(); + BOOST_CHECK(!flow->isFlowControlActive()); + + for (size_t i = 0; i < 4; i++) { + flow->dequeued(msgs_10.front()); + msgs_10.pop_front(); + BOOST_CHECK(!flow->isFlowControlActive()); + } + // count:0 size:0 + + // verify flow control comes ON when only size passes its stop point. + + msgs_100.push_back(createMessage(100)); + flow->enqueued(msgs_100.back()); // count:1 size: 100 + BOOST_CHECK(!flow->isFlowControlActive()); + + msgs_50.push_back(createMessage(50)); + flow->enqueued(msgs_50.back()); // count:2 size: 150 + BOOST_CHECK(!flow->isFlowControlActive()); + + msgs_50.push_back(createMessage(50)); + flow->enqueued(msgs_50.back()); // count:3 size: 200 + BOOST_CHECK(!flow->isFlowControlActive()); + + msgs_1.push_back(createMessage(1)); + flow->enqueued(msgs_1.back()); // count:4 size: 201 ->ON + BOOST_CHECK(flow->isFlowControlActive()); + + flow->dequeued(msgs_100.front()); // count:3 size:101 + msgs_100.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); + + flow->dequeued(msgs_1.front()); // count:2 size:100 + msgs_1.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); + + flow->dequeued(msgs_50.front()); // count:1 size:50 ->OFF + msgs_50.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_10.push_back(createMessage(10)); + flow->enqueued(msgs_10.back()); + BOOST_CHECK(!flow->isFlowControlActive()); + } + // count:9 size:130 + + msgs_10.push_back(createMessage(10)); + flow->enqueued(msgs_10.back()); // count:10 size: 140 + BOOST_CHECK(!flow->isFlowControlActive()); + + msgs_1.push_back(createMessage(1)); + flow->enqueued(msgs_1.back()); // count:11 size: 141 ->ON + BOOST_CHECK(flow->isFlowControlActive()); + + msgs_100.push_back(createMessage(100)); + flow->enqueued(msgs_100.back()); // count:12 size: 241 (both thresholds crossed) + BOOST_CHECK(flow->isFlowControlActive()); + + // at this point: 9@10 + 1@50 + 1@100 + 1@1 == 12@241 + + flow->dequeued(msgs_50.front()); // count:11 size:191 + msgs_50.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); + + for (size_t i = 0; i < 9; i++) { + flow->dequeued(msgs_10.front()); + msgs_10.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); + } + // count:2 size:101 + flow->dequeued(msgs_1.front()); // count:1 size:100 + msgs_1.pop_front(); + BOOST_CHECK(flow->isFlowControlActive()); // still active due to size + + flow->dequeued(msgs_100.front()); // count:0 size:0 ->OFF + msgs_100.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; + QueueFlowLimit *ptr = TestFlow::getQueueFlowLimit(args); + + BOOST_CHECK(ptr); + std::auto_ptr<QueueFlowLimit> flow(ptr); + 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(2950001, // max queue byte count + 80, // 80% stop threshold + 70); // 70% resume threshold + { + FieldTable args; + args.setInt(QueueFlowLimit::flowStopCountKey, 35000); + args.setInt(QueueFlowLimit::flowResumeCountKey, 30000); + + QueueFlowLimit *ptr = TestFlow::getQueueFlowLimit(args); + BOOST_CHECK(ptr); + std::auto_ptr<QueueFlowLimit> flow(ptr); + + 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); + + QueueFlowLimit *ptr = TestFlow::getQueueFlowLimit(args); + BOOST_CHECK(ptr); + std::auto_ptr<QueueFlowLimit> flow(ptr); + + 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); + + QueueFlowLimit *ptr = TestFlow::getQueueFlowLimit(args); + BOOST_CHECK(ptr); + std::auto_ptr<QueueFlowLimit> flow(ptr); + + 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; + QueueFlowLimit *ptr = TestFlow::getQueueFlowLimit(args); + BOOST_CHECK(ptr); + std::auto_ptr<QueueFlowLimit> flow(ptr); + + 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); + QueueFlowLimit *ptr = TestFlow::getQueueFlowLimit(args); + BOOST_CHECK(!ptr); + } + { + FieldTable args; + args.setInt(QueueFlowLimit::flowStopSizeKey, 0); + QueueFlowLimit *ptr = TestFlow::getQueueFlowLimit(args); + BOOST_CHECK(!ptr); + } +} + + +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..f2fbaba2c1 --- /dev/null +++ b/qpid/cpp/src/tests/QueueOptionsTest.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 "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.setPersistLastNode(); + ft.setOrdering(LVQ); + + BOOST_CHECK(1 == ft.getAsInt(QueueOptions::strPersistLastNode)); + BOOST_CHECK(1 == ft.getAsInt(QueueOptions::strLastValueQueue)); + + ft.clearPersistLastNode(); + ft.setOrdering(FIFO); + + BOOST_CHECK(!ft.isSet(QueueOptions::strPersistLastNode)); + 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_CASE(testClearPersistLastNode) +{ + //ensure clear works even if not preceded by the setting on the + //option + QueueOptions ft; + ft.clearPersistLastNode(); + BOOST_CHECK(!ft.isSet(QueueOptions::strPersistLastNode)); +} + + +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..5455105078 --- /dev/null +++ b/qpid/cpp/src/tests/QueuePolicyTest.cpp @@ -0,0 +1,406 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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/QueuePolicy.h" +#include "qpid/broker/QueueFlowLimit.h" +#include "qpid/client/QueueOptions.h" +#include "qpid/sys/Time.h" +#include "qpid/framing/reply_exceptions.h" +#include "MessageUtils.h" +#include "BrokerFixture.h" + +using namespace qpid::broker; +using namespace qpid::client; +using namespace qpid::framing; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(QueuePolicyTestSuite) + +namespace { +QueuedMessage createMessage(uint32_t size) +{ + QueuedMessage msg; + msg.payload = MessageUtils::createMessage(); + MessageUtils::addContent(msg.payload, std::string (size, 'x')); + return msg; +} +} + +QPID_AUTO_TEST_CASE(testCount) +{ + std::auto_ptr<QueuePolicy> policy(QueuePolicy::createQueuePolicy("test", 5, 0)); + BOOST_CHECK_EQUAL((uint64_t) 0, policy->getMaxSize()); + BOOST_CHECK_EQUAL((uint32_t) 5, policy->getMaxCount()); + + QueuedMessage msg = createMessage(10); + for (size_t i = 0; i < 5; i++) { + policy->tryEnqueue(msg.payload); + } + try { + policy->tryEnqueue(msg.payload); + BOOST_FAIL("Policy did not fail on enqueuing sixth message"); + } catch (const ResourceLimitExceededException&) {} + + policy->dequeued(msg); + policy->tryEnqueue(msg.payload); + + try { + policy->tryEnqueue(msg.payload); + BOOST_FAIL("Policy did not fail on enqueuing sixth message (after dequeue)"); + } catch (const ResourceLimitExceededException&) {} +} + +QPID_AUTO_TEST_CASE(testSize) +{ + std::auto_ptr<QueuePolicy> policy(QueuePolicy::createQueuePolicy("test", 0, 50)); + QueuedMessage msg = createMessage(10); + + for (size_t i = 0; i < 5; i++) { + policy->tryEnqueue(msg.payload); + } + try { + policy->tryEnqueue(msg.payload); + BOOST_FAIL("Policy did not fail on aggregate size exceeding 50. " << *policy); + } catch (const ResourceLimitExceededException&) {} + + policy->dequeued(msg); + policy->tryEnqueue(msg.payload); + + try { + policy->tryEnqueue(msg.payload); + BOOST_FAIL("Policy did not fail on aggregate size exceeding 50 (after dequeue). " << *policy); + } catch (const ResourceLimitExceededException&) {} +} + +QPID_AUTO_TEST_CASE(testBoth) +{ + std::auto_ptr<QueuePolicy> policy(QueuePolicy::createQueuePolicy("test", 5, 50)); + try { + QueuedMessage msg = createMessage(51); + policy->tryEnqueue(msg.payload); + BOOST_FAIL("Policy did not fail on single message exceeding 50. " << *policy); + } catch (const ResourceLimitExceededException&) {} + + std::vector<QueuedMessage> messages; + messages.push_back(createMessage(15)); + messages.push_back(createMessage(10)); + messages.push_back(createMessage(11)); + messages.push_back(createMessage(2)); + messages.push_back(createMessage(7)); + for (size_t i = 0; i < messages.size(); i++) { + policy->tryEnqueue(messages[i].payload); + } + //size = 45 at this point, count = 5 + try { + QueuedMessage msg = createMessage(5); + policy->tryEnqueue(msg.payload); + BOOST_FAIL("Policy did not fail on count exceeding 6. " << *policy); + } catch (const ResourceLimitExceededException&) {} + try { + QueuedMessage msg = createMessage(10); + policy->tryEnqueue(msg.payload); + BOOST_FAIL("Policy did not fail on aggregate size exceeding 50. " << *policy); + } catch (const ResourceLimitExceededException&) {} + + + policy->dequeued(messages[0]); + try { + QueuedMessage msg = createMessage(20); + policy->tryEnqueue(msg.payload); + } catch (const ResourceLimitExceededException&) { + BOOST_FAIL("Policy failed incorrectly after dequeue. " << *policy); + } +} + +QPID_AUTO_TEST_CASE(testSettings) +{ + //test reading and writing the policy from/to field table + std::auto_ptr<QueuePolicy> a(QueuePolicy::createQueuePolicy("test", 101, 303)); + FieldTable settings; + a->update(settings); + std::auto_ptr<QueuePolicy> b(QueuePolicy::createQueuePolicy("test", settings)); + BOOST_CHECK_EQUAL(a->getMaxCount(), b->getMaxCount()); + BOOST_CHECK_EQUAL(a->getMaxSize(), b->getMaxSize()); +} + +QPID_AUTO_TEST_CASE(testRingPolicyCount) +{ + FieldTable args; + std::auto_ptr<QueuePolicy> policy = QueuePolicy::createQueuePolicy("test", 5, 0, QueuePolicy::RING); + policy->update(args); + + ProxySessionFixture 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) +{ + std::string hundredBytes = std::string(100, 'h'); + std::string fourHundredBytes = std::string (400, 'f'); + std::string thousandBytes = std::string(1000, 't'); + + // Ring queue, 500 bytes maxSize + + FieldTable args; + std::auto_ptr<QueuePolicy> policy = QueuePolicy::createQueuePolicy("test", 0, 500, QueuePolicy::RING); + policy->update(args); + + ProxySessionFixture f; + std::string q("my-ring-queue"); + 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) +{ + FieldTable args; + std::auto_ptr<QueuePolicy> policy = QueuePolicy::createQueuePolicy("test", 5, 0, QueuePolicy::RING_STRICT); + policy->update(args); + + ProxySessionFixture 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) +{ + FieldTable args; + std::auto_ptr<QueuePolicy> policy = QueuePolicy::createQueuePolicy("test", 5, 0, QueuePolicy::REJECT); + policy->update(args); + + ProxySessionFixture 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); + + ProxySessionFixture 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) +{ + FieldTable args; + std::auto_ptr<QueuePolicy> policy = QueuePolicy::createQueuePolicy("test", 5, 0, QueuePolicy::REJECT); + policy->update(args); + + ProxySessionFixture 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"); + + ProxySessionFixture 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..ae555539a4 --- /dev/null +++ b/qpid/cpp/src/tests/QueueRegistryTest.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 "qpid/broker/QueueRegistry.h" +#include "qpid/broker/Queue.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, false, 0, 0); + 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, false, 0, 0); + BOOST_CHECK_EQUAL(q, qc.first); + BOOST_CHECK(!qc.second); + + qc = reg.declare(bar, false, 0, 0); + q = qc.first; + BOOST_CHECK(q); + BOOST_CHECK_EQUAL(true, qc.second); + BOOST_CHECK_EQUAL(bar, q->getName()); +} + +QPID_AUTO_TEST_CASE(testDeclareTmp) +{ + QueueRegistry reg; + std::pair<Queue::shared_ptr, bool> qc; + + qc = reg.declare(std::string(), false, 0, 0); + BOOST_CHECK(qc.second); + BOOST_CHECK_EQUAL(std::string("tmp_1"), qc.first->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, false, 0, 0); + reg.declare(bar, false, 0, 0); + 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, false, 0, 0); + 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..34e4592a15 --- /dev/null +++ b/qpid/cpp/src/tests/QueueTest.cpp @@ -0,0 +1,1124 @@ + /* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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/broker/ExpiryPolicy.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/QueuePolicy.h" +#include "qpid/broker/QueueFlowLimit.h" + +#include <iostream> +#include "boost/format.hpp" + +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; + + intrusive_ptr<Message> last; + bool received; + TestConsumer(bool acquire = true):Consumer(acquire), received(false) {}; + + virtual bool deliver(QueuedMessage& msg){ + last = msg.payload; + received = true; + return true; + }; + void notify() {} + OwnershipToken* getSession() { return 0; } +}; + +class FailOnDeliver : public Deliverable +{ + boost::intrusive_ptr<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.get()); } +}; + +intrusive_ptr<Message> create_message(std::string exchange, std::string routingKey) { + intrusive_ptr<Message> msg(new Message()); + AMQFrame method((MessageTransferBody(ProtocolVersion(), exchange, 0, 0))); + AMQFrame header((AMQHeaderBody())); + msg->getFrames().append(method); + msg->getFrames().append(header); + msg->getFrames().getHeaders()->get<DeliveryProperties>(true)->setRoutingKey(routingKey); + return msg; +} + +QPID_AUTO_TEST_SUITE(QueueTestSuite) + +QPID_AUTO_TEST_CASE(testAsyncMessage) { + Queue::shared_ptr queue(new Queue("my_test_queue", true)); + intrusive_ptr<Message> received; + + TestConsumer::shared_ptr c1(new TestConsumer()); + queue->consume(c1); + + + //Test basic delivery: + intrusive_ptr<Message> msg1 = create_message("e", "A"); + msg1->enqueueAsync(queue, 0);//this is done on enqueue which is not called from process + queue->process(msg1); + sleep(2); + + BOOST_CHECK(!c1->received); + msg1->enqueueComplete(); + + received = queue->get().payload; + BOOST_CHECK_EQUAL(msg1.get(), received.get()); +} + + +QPID_AUTO_TEST_CASE(testAsyncMessageCount){ + Queue::shared_ptr queue(new Queue("my_test_queue", true)); + intrusive_ptr<Message> msg1 = create_message("e", "A"); + msg1->enqueueAsync(queue, 0);//this is done on enqueue which is not called from process + + queue->process(msg1); + sleep(2); + uint32_t compval=0; + BOOST_CHECK_EQUAL(compval, queue->getEnqueueCompleteMessageCount()); + msg1->enqueueComplete(); + compval=1; + BOOST_CHECK_EQUAL(compval, queue->getEnqueueCompleteMessageCount()); + BOOST_CHECK_EQUAL(compval, queue->getMessageCount()); +} + +QPID_AUTO_TEST_CASE(testConsumers){ + Queue::shared_ptr queue(new Queue("my_queue", true)); + + //Test adding consumers: + TestConsumer::shared_ptr c1(new TestConsumer()); + TestConsumer::shared_ptr c2(new TestConsumer()); + queue->consume(c1); + queue->consume(c2); + + BOOST_CHECK_EQUAL(uint32_t(2), queue->getConsumerCount()); + + //Test basic delivery: + intrusive_ptr<Message> msg1 = create_message("e", "A"); + intrusive_ptr<Message> msg2 = create_message("e", "B"); + intrusive_ptr<Message> msg3 = create_message("e", "C"); + + queue->deliver(msg1); + BOOST_CHECK(queue->dispatch(c1)); + BOOST_CHECK_EQUAL(msg1.get(), c1->last.get()); + + queue->deliver(msg2); + BOOST_CHECK(queue->dispatch(c2)); + BOOST_CHECK_EQUAL(msg2.get(), c2->last.get()); + + c1->received = false; + queue->deliver(msg3); + BOOST_CHECK(queue->dispatch(c1)); + BOOST_CHECK_EQUAL(msg3.get(), c1->last.get()); + + //Test cancellation: + queue->cancel(c1); + BOOST_CHECK_EQUAL(uint32_t(1), queue->getConsumerCount()); + queue->cancel(c2); + BOOST_CHECK_EQUAL(uint32_t(0), queue->getConsumerCount()); +} + +QPID_AUTO_TEST_CASE(testRegistry){ + //Test use of queues in registry: + QueueRegistry registry; + registry.declare("queue1", true, true); + registry.declare("queue2", true, true); + registry.declare("queue3", true, true); + + BOOST_CHECK(registry.find("queue1")); + BOOST_CHECK(registry.find("queue2")); + BOOST_CHECK(registry.find("queue3")); + + registry.destroy("queue1"); + registry.destroy("queue2"); + registry.destroy("queue3"); + + BOOST_CHECK(!registry.find("queue1")); + BOOST_CHECK(!registry.find("queue2")); + BOOST_CHECK(!registry.find("queue3")); +} + +QPID_AUTO_TEST_CASE(testDequeue){ + Queue::shared_ptr queue(new Queue("my_queue", true)); + intrusive_ptr<Message> msg1 = create_message("e", "A"); + intrusive_ptr<Message> msg2 = create_message("e", "B"); + intrusive_ptr<Message> msg3 = create_message("e", "C"); + intrusive_ptr<Message> received; + + queue->deliver(msg1); + queue->deliver(msg2); + queue->deliver(msg3); + + BOOST_CHECK_EQUAL(uint32_t(3), queue->getMessageCount()); + + received = queue->get().payload; + BOOST_CHECK_EQUAL(msg1.get(), received.get()); + BOOST_CHECK_EQUAL(uint32_t(2), queue->getMessageCount()); + + received = queue->get().payload; + BOOST_CHECK_EQUAL(msg2.get(), received.get()); + BOOST_CHECK_EQUAL(uint32_t(1), queue->getMessageCount()); + + TestConsumer::shared_ptr consumer(new TestConsumer()); + queue->consume(consumer); + queue->dispatch(consumer); + if (!consumer->received) + sleep(2); + + BOOST_CHECK_EQUAL(msg3.get(), consumer->last.get()); + BOOST_CHECK_EQUAL(uint32_t(0), queue->getMessageCount()); + + received = queue->get().payload; + BOOST_CHECK(!received); + BOOST_CHECK_EQUAL(uint32_t(0), queue->getMessageCount()); + +} + +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", true)); + 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, key, &args); + exchange3->route(deliverable, key, &args); +} + +QPID_AUTO_TEST_CASE(testPersistLastNodeStanding){ + client::QueueOptions args; + args.setPersistLastNode(); + + Queue::shared_ptr queue(new Queue("my-queue", true)); + queue->configure(args); + + intrusive_ptr<Message> msg1 = create_message("e", "A"); + intrusive_ptr<Message> msg2 = create_message("e", "B"); + intrusive_ptr<Message> msg3 = create_message("e", "C"); + + //enqueue 2 messages + queue->deliver(msg1); + queue->deliver(msg2); + + //change mode + queue->setLastNodeFailure(); + + //enqueue 1 message + queue->deliver(msg3); + + //check all have persistent ids. + BOOST_CHECK(msg1->isPersistent()); + BOOST_CHECK(msg2->isPersistent()); + BOOST_CHECK(msg3->isPersistent()); + +} + + +QPID_AUTO_TEST_CASE(testSeek){ + + Queue::shared_ptr queue(new Queue("my-queue", true)); + + intrusive_ptr<Message> msg1 = create_message("e", "A"); + intrusive_ptr<Message> msg2 = create_message("e", "B"); + intrusive_ptr<Message> msg3 = create_message("e", "C"); + + //enqueue 2 messages + queue->deliver(msg1); + queue->deliver(msg2); + queue->deliver(msg3); + + TestConsumer::shared_ptr consumer(new TestConsumer(false)); + SequenceNumber seq(2); + consumer->position = seq; + + QueuedMessage qm; + queue->dispatch(consumer); + + BOOST_CHECK_EQUAL(msg3.get(), consumer->last.get()); + queue->dispatch(consumer); + queue->dispatch(consumer); // make sure over-run is safe + +} + +QPID_AUTO_TEST_CASE(testSearch){ + + Queue::shared_ptr queue(new Queue("my-queue", true)); + + intrusive_ptr<Message> msg1 = create_message("e", "A"); + intrusive_ptr<Message> msg2 = create_message("e", "B"); + intrusive_ptr<Message> msg3 = create_message("e", "C"); + + //enqueue 2 messages + queue->deliver(msg1); + queue->deliver(msg2); + queue->deliver(msg3); + + SequenceNumber seq(2); + QueuedMessage qm = queue->find(seq); + + BOOST_CHECK_EQUAL(seq.getValue(), qm.position.getValue()); + + queue->acquire(qm); + BOOST_CHECK_EQUAL(queue->getMessageCount(), 2u); + SequenceNumber seq1(3); + QueuedMessage qm1 = queue->find(seq1); + BOOST_CHECK_EQUAL(seq1.getValue(), qm1.position.getValue()); + +} +const std::string nullxid = ""; + +class SimpleDummyCtxt : public TransactionContext {}; + +class DummyCtxt : public TPCTransactionContext +{ + const std::string xid; + public: + DummyCtxt(const std::string& _xid) : xid(_xid) {} + static std::string getXid(TransactionContext& ctxt) + { + DummyCtxt* c(dynamic_cast<DummyCtxt*>(&ctxt)); + return c ? c->xid : nullxid; + } +}; + +class TestMessageStoreOC : public MessageStore +{ + std::set<std::string> prepared; + uint64_t nextPersistenceId; + public: + + uint enqCnt; + uint deqCnt; + bool error; + + TestMessageStoreOC() : MessageStore(),nextPersistenceId(1),enqCnt(0),deqCnt(0),error(false) {} + ~TestMessageStoreOC(){} + + virtual void dequeue(TransactionContext*, + const boost::intrusive_ptr<PersistableMessage>& /*msg*/, + const PersistableQueue& /*queue*/) + { + if (error) throw Exception("Dequeue error test"); + deqCnt++; + } + + virtual void enqueue(TransactionContext*, + const boost::intrusive_ptr<PersistableMessage>& msg, + const PersistableQueue& /* queue */) + { + if (error) throw Exception("Enqueue error test"); + enqCnt++; + msg->enqueueComplete(); + } + + void createError() + { + error=true; + } + + bool init(const Options*) { return true; } + void truncateInit(const bool) {} + void create(PersistableQueue& queue, const framing::FieldTable&) { queue.setPersistenceId(nextPersistenceId++); } + void destroy(PersistableQueue&) {} + void create(const PersistableExchange& exchange, const framing::FieldTable&) { exchange.setPersistenceId(nextPersistenceId++); } + void destroy(const PersistableExchange&) {} + void bind(const PersistableExchange&, const PersistableQueue&, const std::string&, const framing::FieldTable&) {} + void unbind(const PersistableExchange&, const PersistableQueue&, const std::string&, const framing::FieldTable&) {} + void create(const PersistableConfig& config) { config.setPersistenceId(nextPersistenceId++); } + void destroy(const PersistableConfig&) {} + void stage(const boost::intrusive_ptr<PersistableMessage>&) {} + void destroy(PersistableMessage&) {} + void appendContent(const boost::intrusive_ptr<const PersistableMessage>&, const std::string&) {} + void loadContent(const qpid::broker::PersistableQueue&, const boost::intrusive_ptr<const PersistableMessage>&, + std::string&, uint64_t, uint32_t) { throw qpid::framing::InternalErrorException("Can't load content; persistence not enabled"); } + void flush(const qpid::broker::PersistableQueue&) {} + uint32_t outstandingQueueAIO(const PersistableQueue&) { return 0; } + + std::auto_ptr<TransactionContext> begin() { return std::auto_ptr<TransactionContext>(new SimpleDummyCtxt()); } + std::auto_ptr<TPCTransactionContext> begin(const std::string& xid) { return std::auto_ptr<TPCTransactionContext>(new DummyCtxt(xid)); } + void prepare(TPCTransactionContext& ctxt) { prepared.insert(DummyCtxt::getXid(ctxt)); } + void commit(TransactionContext& ctxt) { prepared.erase(DummyCtxt::getXid(ctxt)); } + void abort(TransactionContext& ctxt) { prepared.erase(DummyCtxt::getXid(ctxt)); } + void collectPreparedXids(std::set<std::string>& out) { out.insert(prepared.begin(), prepared.end()); } + + void recover(RecoveryManager&) {} +}; + + +QPID_AUTO_TEST_CASE(testLVQOrdering){ + + client::QueueOptions args; + // set queue mode + args.setOrdering(client::LVQ); + + Queue::shared_ptr queue(new Queue("my-queue", true )); + queue->configure(args); + + intrusive_ptr<Message> msg1 = create_message("e", "A"); + intrusive_ptr<Message> msg2 = create_message("e", "B"); + intrusive_ptr<Message> msg3 = create_message("e", "C"); + intrusive_ptr<Message> msg4 = create_message("e", "D"); + intrusive_ptr<Message> received; + + //set deliever match for LVQ a,b,c,a + + string key; + args.getLVQKey(key); + BOOST_CHECK_EQUAL(key, "qpid.LVQ_key"); + + + msg1->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"a"); + msg2->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"b"); + msg3->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"c"); + msg4->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"a"); + + //enqueue 4 message + queue->deliver(msg1); + queue->deliver(msg2); + queue->deliver(msg3); + queue->deliver(msg4); + + BOOST_CHECK_EQUAL(queue->getMessageCount(), 3u); + + received = queue->get().payload; + BOOST_CHECK_EQUAL(msg4.get(), received.get()); + + received = queue->get().payload; + BOOST_CHECK_EQUAL(msg2.get(), received.get()); + + received = queue->get().payload; + BOOST_CHECK_EQUAL(msg3.get(), received.get()); + + intrusive_ptr<Message> msg5 = create_message("e", "A"); + intrusive_ptr<Message> msg6 = create_message("e", "B"); + intrusive_ptr<Message> msg7 = create_message("e", "C"); + msg5->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"a"); + msg6->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"b"); + msg7->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"c"); + queue->deliver(msg5); + queue->deliver(msg6); + queue->deliver(msg7); + + BOOST_CHECK_EQUAL(queue->getMessageCount(), 3u); + + received = queue->get().payload; + BOOST_CHECK_EQUAL(msg5.get(), received.get()); + + received = queue->get().payload; + BOOST_CHECK_EQUAL(msg6.get(), received.get()); + + received = queue->get().payload; + BOOST_CHECK_EQUAL(msg7.get(), received.get()); + +} + +QPID_AUTO_TEST_CASE(testLVQEmptyKey){ + + client::QueueOptions args; + // set queue mode + args.setOrdering(client::LVQ); + + Queue::shared_ptr queue(new Queue("my-queue", true )); + queue->configure(args); + + intrusive_ptr<Message> msg1 = create_message("e", "A"); + intrusive_ptr<Message> msg2 = create_message("e", "B"); + + string key; + args.getLVQKey(key); + BOOST_CHECK_EQUAL(key, "qpid.LVQ_key"); + + + msg1->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"a"); + queue->deliver(msg1); + queue->deliver(msg2); + BOOST_CHECK_EQUAL(queue->getMessageCount(), 2u); + +} + +QPID_AUTO_TEST_CASE(testLVQAcquire){ + + client::QueueOptions args; + // set queue mode + args.setOrdering(client::LVQ); + // disable flow control, as this test violates the enqueue/dequeue sequence. + args.setInt(QueueFlowLimit::flowStopCountKey, 0); + + Queue::shared_ptr queue(new Queue("my-queue", true )); + queue->configure(args); + + intrusive_ptr<Message> msg1 = create_message("e", "A"); + intrusive_ptr<Message> msg2 = create_message("e", "B"); + intrusive_ptr<Message> msg3 = create_message("e", "C"); + intrusive_ptr<Message> msg4 = create_message("e", "D"); + intrusive_ptr<Message> msg5 = create_message("e", "F"); + intrusive_ptr<Message> msg6 = create_message("e", "G"); + + //set deliever match for LVQ a,b,c,a + + string key; + args.getLVQKey(key); + BOOST_CHECK_EQUAL(key, "qpid.LVQ_key"); + + + msg1->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"a"); + msg2->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"b"); + msg3->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"c"); + msg4->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"a"); + msg5->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"b"); + msg6->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"c"); + + //enqueue 4 message + queue->deliver(msg1); + queue->deliver(msg2); + queue->deliver(msg3); + queue->deliver(msg4); + + BOOST_CHECK_EQUAL(queue->getMessageCount(), 3u); + + framing::SequenceNumber sequence(1); + QueuedMessage qmsg(queue.get(), msg1, sequence); + QueuedMessage qmsg2(queue.get(), msg2, ++sequence); + framing::SequenceNumber sequence1(10); + QueuedMessage qmsg3(queue.get(), 0, sequence1); + + BOOST_CHECK(!queue->acquire(qmsg)); + BOOST_CHECK(queue->acquire(qmsg2)); + // Acquire the massage again to test failure case. + BOOST_CHECK(!queue->acquire(qmsg2)); + BOOST_CHECK(!queue->acquire(qmsg3)); + + BOOST_CHECK_EQUAL(queue->getMessageCount(), 2u); + + queue->deliver(msg5); + BOOST_CHECK_EQUAL(queue->getMessageCount(), 3u); + + // set mode to no browse and check + args.setOrdering(client::LVQ_NO_BROWSE); + queue->configure(args); + TestConsumer::shared_ptr c1(new TestConsumer(false)); + + queue->dispatch(c1); + queue->dispatch(c1); + queue->dispatch(c1); + + queue->deliver(msg6); + BOOST_CHECK_EQUAL(queue->getMessageCount(), 3u); + + intrusive_ptr<Message> received; + received = queue->get().payload; + BOOST_CHECK_EQUAL(msg4.get(), received.get()); + +} + +QPID_AUTO_TEST_CASE(testLVQMultiQueue){ + + client::QueueOptions args; + // set queue mode + args.setOrdering(client::LVQ); + + Queue::shared_ptr queue1(new Queue("my-queue", true )); + Queue::shared_ptr queue2(new Queue("my-queue", true )); + intrusive_ptr<Message> received; + queue1->configure(args); + queue2->configure(args); + + intrusive_ptr<Message> msg1 = create_message("e", "A"); + intrusive_ptr<Message> msg2 = create_message("e", "A"); + + string key; + args.getLVQKey(key); + BOOST_CHECK_EQUAL(key, "qpid.LVQ_key"); + + msg1->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"a"); + msg2->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"a"); + + queue1->deliver(msg1); + queue2->deliver(msg1); + queue1->deliver(msg2); + + received = queue1->get().payload; + BOOST_CHECK_EQUAL(msg2.get(), received.get()); + + received = queue2->get().payload; + BOOST_CHECK_EQUAL(msg1.get(), received.get()); + +} + +QPID_AUTO_TEST_CASE(testLVQRecover){ + +/* simulate this + 1. start 2 nodes + 2. create cluster durable lvq + 3. send a transient message to the queue + 4. kill one of the nodes (to trigger force persistent behaviour)... + 5. then restart it (to turn off force persistent behaviour) + 6. send another transient message with same lvq key as in 3 + 7. kill the second node again (retrigger force persistent) + 8. stop and recover the first node +*/ + TestMessageStoreOC testStore; + client::QueueOptions args; + // set queue mode + args.setOrdering(client::LVQ); + args.setPersistLastNode(); + + Queue::shared_ptr queue1(new Queue("my-queue", true, &testStore)); + intrusive_ptr<Message> received; + queue1->create(args); + + intrusive_ptr<Message> msg1 = create_message("e", "A"); + intrusive_ptr<Message> msg2 = create_message("e", "A"); + // 2 + string key; + args.getLVQKey(key); + BOOST_CHECK_EQUAL(key, "qpid.LVQ_key"); + + msg1->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"a"); + msg2->getProperties<MessageProperties>()->getApplicationHeaders().setString(key,"a"); + // 3 + queue1->deliver(msg1); + // 4 + queue1->setLastNodeFailure(); + BOOST_CHECK_EQUAL(testStore.enqCnt, 1u); + // 5 + queue1->clearLastNodeFailure(); + BOOST_CHECK_EQUAL(testStore.enqCnt, 1u); + // 6 + queue1->deliver(msg2); + BOOST_CHECK_EQUAL(testStore.enqCnt, 1u); + queue1->setLastNodeFailure(); + BOOST_CHECK_EQUAL(testStore.enqCnt, 2u); + BOOST_CHECK_EQUAL(testStore.deqCnt, 1u); +} + +void addMessagesToQueue(uint count, Queue& queue, uint oddTtl = 200, uint evenTtl = 0) +{ + for (uint i = 0; i < count; i++) { + intrusive_ptr<Message> m = create_message("exchange", "key"); + if (i % 2) { + if (oddTtl) m->getProperties<DeliveryProperties>()->setTtl(oddTtl); + } else { + if (evenTtl) m->getProperties<DeliveryProperties>()->setTtl(evenTtl); + } + m->setTimestamp(new broker::ExpiryPolicy); + 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(); + BOOST_CHECK_EQUAL(queue.getMessageCount(), 5u); +} + +QPID_AUTO_TEST_CASE(testQueueCleaner) { + Timer timer; + QueueRegistry queues; + Queue::shared_ptr queue = queues.declare("my-queue").first; + addMessagesToQueue(10, *queue, 200, 400); + BOOST_CHECK_EQUAL(queue->getMessageCount(), 10u); + + QueueCleaner cleaner(queues, 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); +} + +QPID_AUTO_TEST_CASE(testMultiQueueLastNode){ + + TestMessageStoreOC testStore; + client::QueueOptions args; + args.setPersistLastNode(); + + Queue::shared_ptr queue1(new Queue("queue1", true, &testStore )); + queue1->create(args); + Queue::shared_ptr queue2(new Queue("queue2", true, &testStore )); + queue2->create(args); + + intrusive_ptr<Message> msg1 = create_message("e", "A"); + + queue1->deliver(msg1); + queue2->deliver(msg1); + + //change mode + queue1->setLastNodeFailure(); + BOOST_CHECK_EQUAL(testStore.enqCnt, 1u); + queue2->setLastNodeFailure(); + BOOST_CHECK_EQUAL(testStore.enqCnt, 2u); + + // check they don't get stored twice + queue1->setLastNodeFailure(); + queue2->setLastNodeFailure(); + BOOST_CHECK_EQUAL(testStore.enqCnt, 2u); + + intrusive_ptr<Message> msg2 = create_message("e", "B"); + queue1->deliver(msg2); + queue2->deliver(msg2); + + queue1->clearLastNodeFailure(); + queue2->clearLastNodeFailure(); + // check only new messages get forced + queue1->setLastNodeFailure(); + queue2->setLastNodeFailure(); + BOOST_CHECK_EQUAL(testStore.enqCnt, 4u); + + // check no failure messages are stored + queue1->clearLastNodeFailure(); + queue2->clearLastNodeFailure(); + + intrusive_ptr<Message> msg3 = create_message("e", "B"); + queue1->deliver(msg3); + queue2->deliver(msg3); + BOOST_CHECK_EQUAL(testStore.enqCnt, 4u); + queue1->setLastNodeFailure(); + queue2->setLastNodeFailure(); + BOOST_CHECK_EQUAL(testStore.enqCnt, 6u); + + // check requeue 1 + intrusive_ptr<Message> msg4 = create_message("e", "C"); + intrusive_ptr<Message> msg5 = create_message("e", "D"); + + framing::SequenceNumber sequence(1); + QueuedMessage qmsg1(queue1.get(), msg4, sequence); + QueuedMessage qmsg2(queue2.get(), msg5, ++sequence); + + queue1->requeue(qmsg1); + BOOST_CHECK_EQUAL(testStore.enqCnt, 7u); + + // check requeue 2 + queue2->clearLastNodeFailure(); + queue2->requeue(qmsg2); + BOOST_CHECK_EQUAL(testStore.enqCnt, 7u); + queue2->setLastNodeFailure(); + BOOST_CHECK_EQUAL(testStore.enqCnt, 8u); + + queue2->clearLastNodeFailure(); + queue2->setLastNodeFailure(); + BOOST_CHECK_EQUAL(testStore.enqCnt, 8u); +} + +QPID_AUTO_TEST_CASE(testLastNodeRecoverAndFail){ +/* +simulate this: + 1. start two nodes + 2. create cluster durable queue and add some messages + 3. kill one node (trigger force-persistent behaviour) + 4. stop and recover remaining node + 5. add another node + 6. kill that new node again +make sure that an attempt to re-enqueue a message does not happen which will +result in the last man standing exiting with an error. + +we need to make sure that recover is safe, i.e. messages are +not requeued to the store. +*/ + TestMessageStoreOC testStore; + client::QueueOptions args; + // set queue mode + args.setPersistLastNode(); + + Queue::shared_ptr queue1(new Queue("my-queue", true, &testStore)); + intrusive_ptr<Message> received; + queue1->create(args); + + // check requeue 1 + intrusive_ptr<Message> msg1 = create_message("e", "C"); + intrusive_ptr<Message> msg2 = create_message("e", "D"); + + queue1->recover(msg1); + + queue1->setLastNodeFailure(); + BOOST_CHECK_EQUAL(testStore.enqCnt, 0u); + + queue1->clearLastNodeFailure(); + BOOST_CHECK_EQUAL(testStore.enqCnt, 0u); + + queue1->deliver(msg2); + BOOST_CHECK_EQUAL(testStore.enqCnt, 0u); + queue1->setLastNodeFailure(); + BOOST_CHECK_EQUAL(testStore.enqCnt, 1u); + +} + +QPID_AUTO_TEST_CASE(testLastNodeJournalError){ +/* +simulate store exception going into last node standing + +*/ + TestMessageStoreOC testStore; + client::QueueOptions args; + // set queue mode + args.setPersistLastNode(); + + Queue::shared_ptr queue1(new Queue("my-queue", true, &testStore)); + intrusive_ptr<Message> received; + queue1->configure(args); + + // check requeue 1 + intrusive_ptr<Message> msg1 = create_message("e", "C"); + + queue1->deliver(msg1); + testStore.createError(); + + ScopedSuppressLogging sl; // Suppress messages for expected errors. + queue1->setLastNodeFailure(); + BOOST_CHECK_EQUAL(testStore.enqCnt, 0u); + +} + +intrusive_ptr<Message> mkMsg(MessageStore& store, std::string content = "", bool durable = false) +{ + intrusive_ptr<Message> msg = MessageUtils::createMessage("", "", durable); + if (content.size()) MessageUtils::addContent(msg, content); + msg->setStore(&store); + return msg; +} + +QPID_AUTO_TEST_CASE(testFlowToDiskBlocking){ + + TestMessageStoreOC testStore; + client::QueueOptions args0; // No size policy + client::QueueOptions args1; + args1.setSizePolicy(FLOW_TO_DISK, 0, 1); + client::QueueOptions args2; + args2.setSizePolicy(FLOW_TO_DISK, 0, 2); + + // --- Fanout exchange bound to single transient queue ------------------------------------------------------------- + + FanOutExchange sbtFanout1("sbtFanout1", false, args0); // single binding to transient queue + Queue::shared_ptr tq1(new Queue("tq1", true)); // transient w/ limit + tq1->configure(args1); + sbtFanout1.bind(tq1, "", 0); + + intrusive_ptr<Message> msg01 = mkMsg(testStore, std::string(5, 'X')); // transient w/ content + DeliverableMessage dmsg01(msg01); + sbtFanout1.route(dmsg01, "", 0); // Brings queue 1 to capacity limit + msg01->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg01->isContentReleased(), false); + BOOST_CHECK_EQUAL(1u, tq1->getMessageCount()); + + intrusive_ptr<Message> msg02 = mkMsg(testStore, std::string(5, 'X')); // transient w/ content + DeliverableMessage dmsg02(msg02); + { + ScopedSuppressLogging sl; // suppress expected error messages. + BOOST_CHECK_THROW(sbtFanout1.route(dmsg02, "", 0), ResourceLimitExceededException); + } + msg02->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg02->isContentReleased(), false); + BOOST_CHECK_EQUAL(1u, tq1->getMessageCount()); + + intrusive_ptr<Message> msg03 = mkMsg(testStore, std::string(5, 'X'), true); // durable w/ content + DeliverableMessage dmsg03(msg03); + { + ScopedSuppressLogging sl; // suppress expected error messages. + BOOST_CHECK_THROW(sbtFanout1.route(dmsg03, "", 0), ResourceLimitExceededException); + } + msg03->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg03->isContentReleased(), false); + BOOST_CHECK_EQUAL(1u, tq1->getMessageCount()); + + intrusive_ptr<Message> msg04 = mkMsg(testStore); // transient no content + DeliverableMessage dmsg04(msg04); + { + ScopedSuppressLogging sl; // suppress expected error messages. + BOOST_CHECK_THROW(sbtFanout1.route(dmsg04, "", 0), ResourceLimitExceededException); + } + msg04->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg04->isContentReleased(), false); + BOOST_CHECK_EQUAL(1u, tq1->getMessageCount()); + + intrusive_ptr<Message> msg05 = mkMsg(testStore, "", true); // durable no content + DeliverableMessage dmsg05(msg05); + { + ScopedSuppressLogging sl; // suppress expected error messages. + BOOST_CHECK_THROW(sbtFanout1.route(dmsg05, "", 0), ResourceLimitExceededException); + } + msg05->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg05->isContentReleased(), false); + BOOST_CHECK_EQUAL(1u, tq1->getMessageCount()); + + // --- Fanout exchange bound to single durable queue --------------------------------------------------------------- + + FanOutExchange sbdFanout2("sbdFanout2", false, args0); // single binding to durable queue + Queue::shared_ptr dq2(new Queue("dq2", true, &testStore)); // durable w/ limit + dq2->configure(args1); + sbdFanout2.bind(dq2, "", 0); + + intrusive_ptr<Message> msg06 = mkMsg(testStore, std::string(5, 'X')); // transient w/ content + DeliverableMessage dmsg06(msg06); + sbdFanout2.route(dmsg06, "", 0); // Brings queue 2 to capacity limit + msg06->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg06->isContentReleased(), false); + BOOST_CHECK_EQUAL(1u, dq2->getMessageCount()); + + intrusive_ptr<Message> msg07 = mkMsg(testStore, std::string(5, 'X')); // transient w/ content + DeliverableMessage dmsg07(msg07); + sbdFanout2.route(dmsg07, "", 0); + msg07->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg07->isContentReleased(), true); + BOOST_CHECK_EQUAL(2u, dq2->getMessageCount()); + + intrusive_ptr<Message> msg08 = mkMsg(testStore, std::string(5, 'X'), true); // durable w/ content + DeliverableMessage dmsg08(msg08); + sbdFanout2.route(dmsg08, "", 0); + msg08->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg08->isContentReleased(), true); + BOOST_CHECK_EQUAL(3u, dq2->getMessageCount()); + + intrusive_ptr<Message> msg09 = mkMsg(testStore); // transient no content + DeliverableMessage dmsg09(msg09); + sbdFanout2.route(dmsg09, "", 0); + msg09->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg09->isContentReleased(), true); + BOOST_CHECK_EQUAL(4u, dq2->getMessageCount()); + + intrusive_ptr<Message> msg10 = mkMsg(testStore, "", true); // durable no content + DeliverableMessage dmsg10(msg10); + sbdFanout2.route(dmsg10, "", 0); + msg10->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg10->isContentReleased(), true); + BOOST_CHECK_EQUAL(5u, dq2->getMessageCount()); + + // --- Fanout exchange bound to multiple durable queues ------------------------------------------------------------ + + FanOutExchange mbdFanout3("mbdFanout3", false, args0); // multiple bindings to durable queues + Queue::shared_ptr dq3(new Queue("dq3", true, &testStore)); // durable w/ limit 2 + dq3->configure(args2); + mbdFanout3.bind(dq3, "", 0); + Queue::shared_ptr dq4(new Queue("dq4", true, &testStore)); // durable w/ limit 1 + dq4->configure(args1); + mbdFanout3.bind(dq4, "", 0); + Queue::shared_ptr dq5(new Queue("dq5", true, &testStore)); // durable no limit + dq5->configure(args0); + mbdFanout3.bind(dq5, "", 0); + + intrusive_ptr<Message> msg11 = mkMsg(testStore, std::string(5, 'X')); // transient w/ content + DeliverableMessage dmsg11(msg11); + mbdFanout3.route(dmsg11, "", 0); // Brings queues 3 and 4 to capacity limit + msg11->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg11->isContentReleased(), false); + BOOST_CHECK_EQUAL(1u, dq3->getMessageCount()); + BOOST_CHECK_EQUAL(1u, dq4->getMessageCount()); + BOOST_CHECK_EQUAL(1u, dq5->getMessageCount()); + + intrusive_ptr<Message> msg12 = mkMsg(testStore, std::string(5, 'X')); // transient w/ content + DeliverableMessage dmsg12(msg12); + mbdFanout3.route(dmsg12, "", 0); + msg12->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg12->isContentReleased(), false); // XXXX - consequence of transient msg multi-queue ftd policy-handling limitations, fix in broker at some point! + BOOST_CHECK_EQUAL(2u, dq3->getMessageCount()); + BOOST_CHECK_EQUAL(2u, dq4->getMessageCount()); + BOOST_CHECK_EQUAL(2u, dq5->getMessageCount()); + + intrusive_ptr<Message> msg13 = mkMsg(testStore, std::string(5, 'X'), true); // durable w/ content + DeliverableMessage dmsg13(msg13); + mbdFanout3.route(dmsg13, "", 0); + msg13->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg13->isContentReleased(), true); + BOOST_CHECK_EQUAL(3u, dq3->getMessageCount()); + BOOST_CHECK_EQUAL(3u, dq4->getMessageCount()); + BOOST_CHECK_EQUAL(3u, dq5->getMessageCount()); + + intrusive_ptr<Message> msg14 = mkMsg(testStore); // transient no content + DeliverableMessage dmsg14(msg14); + mbdFanout3.route(dmsg14, "", 0); + msg14->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg14->isContentReleased(), false); // XXXX - consequence of transient msg multi-queue ftd policy-handling limitations, fix in broker at some point! + BOOST_CHECK_EQUAL(4u, dq3->getMessageCount()); + BOOST_CHECK_EQUAL(4u, dq4->getMessageCount()); + BOOST_CHECK_EQUAL(4u, dq5->getMessageCount()); + + intrusive_ptr<Message> msg15 = mkMsg(testStore, "", true); // durable no content + DeliverableMessage dmsg15(msg15); + mbdFanout3.route(dmsg15, "", 0); + msg15->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg15->isContentReleased(), true); + BOOST_CHECK_EQUAL(5u, dq3->getMessageCount()); + BOOST_CHECK_EQUAL(5u, dq4->getMessageCount()); + BOOST_CHECK_EQUAL(5u, dq5->getMessageCount()); + + // Bind a transient queue, this should block the release of any further messages. + // Note: this will result in a violation of the count policy of dq3 and dq4 - but this + // is expected until a better overall multi-queue design is implemented. Similarly + // for the other tests in this section. + + Queue::shared_ptr tq6(new Queue("tq6", true)); // transient no limit + tq6->configure(args0); + mbdFanout3.bind(tq6, "", 0); + + intrusive_ptr<Message> msg16 = mkMsg(testStore, std::string(5, 'X')); // transient w/ content + DeliverableMessage dmsg16(msg16); + mbdFanout3.route(dmsg16, "", 0); + msg16->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg16->isContentReleased(), false); + BOOST_CHECK_EQUAL(6u, dq3->getMessageCount()); + BOOST_CHECK_EQUAL(6u, dq4->getMessageCount()); + BOOST_CHECK_EQUAL(6u, dq5->getMessageCount()); + + intrusive_ptr<Message> msg17 = mkMsg(testStore, std::string(5, 'X'), true); // durable w/ content + DeliverableMessage dmsg17(msg17); + mbdFanout3.route(dmsg17, "", 0); + msg17->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg17->isContentReleased(), false); + BOOST_CHECK_EQUAL(7u, dq3->getMessageCount()); + BOOST_CHECK_EQUAL(7u, dq4->getMessageCount()); + BOOST_CHECK_EQUAL(7u, dq5->getMessageCount()); + + intrusive_ptr<Message> msg18 = mkMsg(testStore); // transient no content + DeliverableMessage dmsg18(msg18); + mbdFanout3.route(dmsg18, "", 0); + msg18->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg18->isContentReleased(), false); + BOOST_CHECK_EQUAL(8u, dq3->getMessageCount()); + BOOST_CHECK_EQUAL(8u, dq4->getMessageCount()); + BOOST_CHECK_EQUAL(8u, dq5->getMessageCount()); + + intrusive_ptr<Message> msg19 = mkMsg(testStore, "", true); // durable no content + DeliverableMessage dmsg19(msg19); + mbdFanout3.route(dmsg19, "", 0); + msg19->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg19->isContentReleased(), false); + BOOST_CHECK_EQUAL(9u, dq3->getMessageCount()); + BOOST_CHECK_EQUAL(9u, dq4->getMessageCount()); + BOOST_CHECK_EQUAL(9u, dq5->getMessageCount()); + + + // --- Fanout exchange bound to multiple durable and transient queues ---------------------------------------------- + + FanOutExchange mbmFanout4("mbmFanout4", false, args0); // multiple bindings to durable/transient queues + Queue::shared_ptr dq7(new Queue("dq7", true, &testStore)); // durable no limit + dq7->configure(args0); + mbmFanout4.bind(dq7, "", 0); + Queue::shared_ptr dq8(new Queue("dq8", true, &testStore)); // durable w/ limit + dq8->configure(args1); + mbmFanout4.bind(dq8, "", 0); + Queue::shared_ptr tq9(new Queue("tq9", true)); // transient no limit + tq9->configure(args0); + mbmFanout4.bind(tq9, "", 0); + + intrusive_ptr<Message> msg20 = mkMsg(testStore, std::string(5, 'X')); // transient w/ content + DeliverableMessage dmsg20(msg20); + mbmFanout4.route(dmsg20, "", 0); // Brings queue 7 to capacity limit + msg20->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg20->isContentReleased(), false); + BOOST_CHECK_EQUAL(1u, dq7->getMessageCount()); + BOOST_CHECK_EQUAL(1u, dq8->getMessageCount()); + BOOST_CHECK_EQUAL(1u, tq9->getMessageCount()); + + intrusive_ptr<Message> msg21 = mkMsg(testStore, std::string(5, 'X')); // transient w/ content + DeliverableMessage dmsg21(msg21); + mbmFanout4.route(dmsg21, "", 0); + msg21->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg21->isContentReleased(), false); + BOOST_CHECK_EQUAL(2u, dq7->getMessageCount()); // over limit + BOOST_CHECK_EQUAL(2u, dq8->getMessageCount()); + BOOST_CHECK_EQUAL(2u, tq9->getMessageCount()); + + intrusive_ptr<Message> msg22 = mkMsg(testStore, std::string(5, 'X'), true); // durable w/ content + DeliverableMessage dmsg22(msg22); + mbmFanout4.route(dmsg22, "", 0); + msg22->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg22->isContentReleased(), false); + BOOST_CHECK_EQUAL(3u, dq7->getMessageCount()); // over limit + BOOST_CHECK_EQUAL(3u, dq8->getMessageCount()); // over limit + BOOST_CHECK_EQUAL(3u, tq9->getMessageCount()); + + intrusive_ptr<Message> msg23 = mkMsg(testStore); // transient no content + DeliverableMessage dmsg23(msg23); + mbmFanout4.route(dmsg23, "", 0); + msg23->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg23->isContentReleased(), false); + BOOST_CHECK_EQUAL(4u, dq7->getMessageCount()); // over limit + BOOST_CHECK_EQUAL(4u, dq8->getMessageCount()); // over limit + BOOST_CHECK_EQUAL(4u, tq9->getMessageCount()); + + intrusive_ptr<Message> msg24 = mkMsg(testStore, "", true); // durable no content + DeliverableMessage dmsg24(msg24); + mbmFanout4.route(dmsg24, "", 0); + msg24->tryReleaseContent(); + BOOST_CHECK_EQUAL(msg24->isContentReleased(), false); + BOOST_CHECK_EQUAL(5u, dq7->getMessageCount()); // over limit + BOOST_CHECK_EQUAL(5u, dq8->getMessageCount()); // over limit + BOOST_CHECK_EQUAL(5u, tq9->getMessageCount()); +} + + +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..0f4edee493 --- /dev/null +++ b/qpid/cpp/src/tests/README.txt @@ -0,0 +1,54 @@ += Running Qpid C++ tests = + +General philosophy is that "make check" run all tests by default, but +developers can run tests selectively as explained below. + +== Valgrind == + +By default we run tests under valgrind to detect memory errors if valgrind +is present. ./configure --disable-valgrind will disable it. + +Default valgrind options are specified in .valgrindrc-default, which a +checked-in file. The actual options used are in .valgrindrc which is a +local file. Normally it is a copy of valgrindrc-default but you can +modify at will. + +Supressed errors are listed in .valgrind.supp. If you want to change +suppressions for local testing, just modify .valgrindrc to point to a +different file. Do NOT add suppressions to .valgrindrc.supp unless +they are known problems outside of Qpid that can't reasonably be +worked around in Qpid. + + +== Unit Tests == +Unit tests use the boost test framework, and are compiled to rogram + 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 + +NOTE: some unit tests are written as CppUnit plug-ins, we are moving away +from CppUnit so new tests should use the boost framework. + +CppUnit tests are run by the script run-unit-tests. + +== System Tests == + +System tests are self contained AMQP client executables or scripts. +They are listed in the TESTS make variable, which can be over-ridden. + +The ./start_broker "test" launches the broker, ./stop_broker" stops it. +Tests in between assume the broker is running. + +./python_tests: runs ../python/run_tests. This is the main set of +system testss for the broker. + +Other C++ client test executables and scripts under client/test are +system tests for the client. + +By setting TESTS in a make command you can run a different subset of tests +against an already-running broker. + + + + diff --git a/qpid/cpp/src/tests/RangeSet.cpp b/qpid/cpp/src/tests/RangeSet.cpp new file mode 100644 index 0000000000..db3a964086 --- /dev/null +++ b/qpid/cpp/src/tests/RangeSet.cpp @@ -0,0 +1,146 @@ +/* + * + * 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> TestRange; +typedef qpid::RangeSet<int> TestRangeSet; + +QPID_AUTO_TEST_CASE(testEmptyRange) { + TestRange r; + BOOST_CHECK(r.empty()); + BOOST_CHECK(!r.contains(0)); + // BOOST_CHECK(r.contiguous(0)); +} + +QPID_AUTO_TEST_CASE(testRangeSetAddPoint) { + TestRangeSet r; + BOOST_CHECK(r.empty()); + r += 3; + BOOST_CHECK_MESSAGE(r.contains(3), r); + BOOST_CHECK_MESSAGE(r.contains(TestRange(3,4)), r); + BOOST_CHECK(!r.empty()); + r += 5; + BOOST_CHECK_MESSAGE(r.contains(5), r); + BOOST_CHECK_MESSAGE(r.contains(TestRange(5,6)), r); + BOOST_CHECK_MESSAGE(!r.contains(TestRange(3,6)), r); + r += 4; + BOOST_CHECK_MESSAGE(r.contains(TestRange(3,6)), r); +} + +QPID_AUTO_TEST_CASE(testRangeSetAddRange) { + TestRangeSet r; + r += TestRange(0,3); + BOOST_CHECK(r.contains(TestRange(0,3))); + r += TestRange(4,6); + BOOST_CHECK_MESSAGE(r.contains(TestRange(4,6)), r); + r += 3; + BOOST_CHECK_MESSAGE(r.contains(TestRange(0,6)), r); + BOOST_CHECK(r.front() == 0); + BOOST_CHECK(r.back() == 6); +} + +QPID_AUTO_TEST_CASE(testRangeSetAddSet) { + TestRangeSet r; + TestRangeSet s = TestRangeSet(0,3)+TestRange(5,10); + r += s; + BOOST_CHECK_EQUAL(r,s); + r += TestRangeSet(3,5) + TestRange(7,12) + 15; + BOOST_CHECK_EQUAL(r, TestRangeSet(0,12) + 15); + + r.clear(); + BOOST_CHECK(r.empty()); + r += TestRange::makeClosed(6,10); + BOOST_CHECK_EQUAL(r, TestRangeSet(6,11)); + r += TestRangeSet(2,6)+8; + BOOST_CHECK_EQUAL(r, TestRangeSet(2,11)); +} + +QPID_AUTO_TEST_CASE(testRangeSetIterate) { + TestRangeSet r; + (((r += 1) += 10) += TestRange(4,7)) += 2; + BOOST_MESSAGE(r); + 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(TestRangeSet(0,5)-3, TestRangeSet(0,3)+TestRange(4,5)); + BOOST_CHECK_EQUAL(TestRangeSet(1,5)-5, TestRangeSet(1,5)); + BOOST_CHECK_EQUAL(TestRangeSet(1,5)-0, TestRangeSet(1,5)); + + TestRangeSet r(TestRangeSet(0,5)+TestRange(10,15)+TestRange(20,25)); + + // TestRanges + BOOST_CHECK_EQUAL(r-TestRange(0,5), TestRangeSet(10,15)+TestRange(20,25)); + BOOST_CHECK_EQUAL(r-TestRange(10,15), TestRangeSet(0,5)+TestRange(20,25)); + BOOST_CHECK_EQUAL(r-TestRange(20,25), TestRangeSet(0,5)+TestRange(10,15)); + + BOOST_CHECK_EQUAL(r-TestRange(-5, 30), TestRangeSet()); + + BOOST_CHECK_EQUAL(r-TestRange(-5, 7), TestRangeSet(10,15)+TestRange(20,25)); + BOOST_CHECK_EQUAL(r-TestRange(8,19), TestRangeSet(0,5)+TestRange(20,25)); + BOOST_CHECK_EQUAL(r-TestRange(17,30), TestRangeSet(0,5)+TestRange(10,15)); + BOOST_CHECK_EQUAL(r-TestRange(17,30), TestRangeSet(0,5)+TestRange(10,15)); + + BOOST_CHECK_EQUAL(r-TestRange(-5, 5), TestRangeSet(10,15)+TestRange(20,25)); + BOOST_CHECK_EQUAL(r-TestRange(10,19), TestRangeSet(0,5)+TestRange(20,25)); + BOOST_CHECK_EQUAL(r-TestRange(18,25), TestRangeSet(0,5)+TestRange(10,15)); + BOOST_CHECK_EQUAL(r-TestRange(23,25), TestRangeSet(0,5)+TestRange(10,15)+TestRange(20,23)); + + BOOST_CHECK_EQUAL(r-TestRange(-3, 3), TestRangeSet(3,5)+TestRange(10,15)+TestRange(20,25)); + BOOST_CHECK_EQUAL(r-TestRange(3, 7), TestRangeSet(0,2)+TestRange(10,15)+TestRange(20,25)); + BOOST_CHECK_EQUAL(r-TestRange(3, 12), TestRangeSet(0,3)+TestRange(12,15)+TestRange(20,25)); + BOOST_CHECK_EQUAL(r-TestRange(3, 22), TestRangeSet(12,15)+TestRange(22,25)); + BOOST_CHECK_EQUAL(r-TestRange(12, 22), TestRangeSet(0,5)+TestRange(10,11)+TestRange(22,25)); + + // Sets + BOOST_CHECK_EQUAL(r-(TestRangeSet(-1,6)+TestRange(11,14)+TestRange(23,25)), + TestRangeSet(10,11)+TestRange(14,15)+TestRange(20,23)); +} + +QPID_AUTO_TEST_CASE(testRangeContaining) { + TestRangeSet r; + (((r += 1) += TestRange(3,5)) += 7); + BOOST_CHECK_EQUAL(r.rangeContaining(0), TestRange(0,0)); + BOOST_CHECK_EQUAL(r.rangeContaining(1), TestRange(1,2)); + BOOST_CHECK_EQUAL(r.rangeContaining(2), TestRange(2,2)); + BOOST_CHECK_EQUAL(r.rangeContaining(3), TestRange(3,5)); + BOOST_CHECK_EQUAL(r.rangeContaining(4), TestRange(3,5)); + BOOST_CHECK_EQUAL(r.rangeContaining(5), TestRange(5,5)); + BOOST_CHECK_EQUAL(r.rangeContaining(6), TestRange(6,6)); + BOOST_CHECK_EQUAL(r.rangeContaining(7), TestRange(7,8)); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/RateFlowcontrolTest.cpp b/qpid/cpp/src/tests/RateFlowcontrolTest.cpp new file mode 100644 index 0000000000..80ad06af8c --- /dev/null +++ b/qpid/cpp/src/tests/RateFlowcontrolTest.cpp @@ -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. + * + */ + +#include "unit_test.h" + +#include "qpid/broker/RateFlowcontrol.h" +#include "qpid/sys/Time.h" + +using namespace qpid::broker; +using namespace qpid::sys; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(RateFlowcontrolTestSuite) + +QPID_AUTO_TEST_CASE(RateFlowcontrolTest) +{ + // BOOST_CHECK(predicate); + // BOOST_CHECK_EQUAL(a, b); + + RateFlowcontrol fc(100); + AbsTime n=AbsTime::now(); + + BOOST_CHECK_EQUAL( fc.receivedMessage(n, 0), 0U ); + + fc.sentCredit(n, 0); + + BOOST_CHECK_EQUAL( fc.receivedMessage(n, 0), 0U ); + fc.sentCredit(n, 50); + + Duration d=250*TIME_MSEC; + + n = AbsTime(n,d); + BOOST_CHECK_EQUAL( fc.receivedMessage(n, 25), 0U ); + BOOST_CHECK_EQUAL( fc.availableCredit(n), 25U ); + + n = AbsTime(n,d); + BOOST_CHECK_EQUAL( fc.receivedMessage(n, 23), 48U ); + BOOST_CHECK_EQUAL( fc.availableCredit(n), 48U ); + fc.sentCredit(n, 48); + BOOST_CHECK_EQUAL( fc.receivedMessage(n, 50), 0U); + BOOST_CHECK(fc.flowStopped()); + + n = AbsTime(n,d); + BOOST_CHECK_EQUAL( fc.receivedMessage(n, 0), 25U); + n = AbsTime(n,d); + BOOST_CHECK_EQUAL( fc.receivedMessage(n, 0), 50U); +} + +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..e4c1da5696 --- /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" + +QPID_AUTO_TEST_SUITE(RefCountedTestSuiteTestSuite) + +using boost::intrusive_ptr; +using namespace std; +using namespace qpid; + +namespace qpid { +namespace tests { + +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/ReplicationTest.cpp b/qpid/cpp/src/tests/ReplicationTest.cpp new file mode 100644 index 0000000000..7310a3fe20 --- /dev/null +++ b/qpid/cpp/src/tests/ReplicationTest.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 "unit_test.h" +#include "test_tools.h" +#include "config.h" +#include "BrokerFixture.h" + +#include "qpid/Plugin.h" +#include "qpid/broker/Broker.h" +#include "qpid/client/QueueOptions.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/framing/SequenceNumber.h" +#include "qpid/replication/constants.h" +#include "qpid/sys/Shlib.h" + +#include <string> +#include <sstream> +#include <vector> + +#include <boost/assign.hpp> +#include <boost/bind.hpp> + +using namespace qpid::client; +using namespace qpid::framing; +using namespace qpid::replication::constants; +using boost::assign::list_of; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(ReplicationTestSuite) + +// FIXME aconway 2009-11-26: clean this up. +// The CMake-based build passes in the module suffix; if it's not there, this +// is a Linux/UNIX libtool-based build. +#if defined (QPID_MODULE_PREFIX) && defined (QPID_MODULE_SUFFIX) +static const char *default_shlib = + QPID_MODULE_PREFIX "replicating_listener" QPID_MODULE_POSTFIX QPID_MODULE_SUFFIX; +#else +static const char *default_shlib = ".libs/replicating_listener.so"; +#endif +qpid::sys::Shlib plugin(getLibPath("REPLICATING_LISTENER_LIB", default_shlib)); + +qpid::broker::Broker::Options getBrokerOpts(const std::vector<std::string>& args) +{ + std::vector<const char*> argv(args.size()); + transform(args.begin(), args.end(), argv.begin(), boost::bind(&string::c_str, _1)); + + qpid::broker::Broker::Options opts; + qpid::Plugin::addOptions(opts); + opts.parse(argv.size(), &argv[0], "", true); + return opts; +} + +QPID_AUTO_TEST_CASE(testReplicationExchange) +{ + qpid::broker::Broker::Options brokerOpts(getBrokerOpts(list_of<string>("qpidd") + ("--replication-exchange-name=qpid.replication"))); + ProxySessionFixture f(brokerOpts); + + + std::string dataQ("queue-1"); + std::string eventQ("event-queue-1"); + std::string dataQ2("queue-2"); + std::string eventQ2("event-queue-2"); + FieldTable eventQopts; + eventQopts.setString("qpid.insert_sequence_numbers", REPLICATION_EVENT_SEQNO); + + f.session.queueDeclare(arg::queue=eventQ, arg::exclusive=true, arg::autoDelete=true, arg::arguments=eventQopts); + f.session.exchangeBind(arg::exchange="qpid.replication", arg::queue=eventQ, arg::bindingKey=dataQ); + + f.session.queueDeclare(arg::queue=eventQ2, arg::exclusive=true, arg::autoDelete=true, arg::arguments=eventQopts); + f.session.exchangeBind(arg::exchange="qpid.replication", arg::queue=eventQ2, arg::bindingKey=dataQ2); + + QueueOptions args; + args.enableQueueEvents(false); + f.session.queueDeclare(arg::queue=dataQ, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args); + f.session.queueDeclare(arg::queue=dataQ2, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args); + for (int i = 0; i < 10; i++) { + f.session.messageTransfer(arg::content=Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), dataQ)); + f.session.messageTransfer(arg::content=Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), dataQ2)); + } + Message msg; + LocalQueue incoming; + Subscription sub = f.subs.subscribe(incoming, dataQ); + for (int i = 0; i < 10; i++) { + BOOST_CHECK(incoming.get(msg, qpid::sys::TIME_SEC)); + BOOST_CHECK_EQUAL((boost::format("%1%_%2%") % "Message" % (i+1)).str(), msg.getData()); + } + BOOST_CHECK(!f.subs.get(msg, dataQ)); + + sub.cancel(); + sub = f.subs.subscribe(incoming, eventQ); + //check that we received enqueue events for first queue: + for (int i = 0; i < 10; i++) { + BOOST_CHECK(incoming.get(msg, qpid::sys::TIME_SEC)); + BOOST_CHECK_EQUAL(msg.getHeaders().getAsString(REPLICATION_TARGET_QUEUE), dataQ); + BOOST_CHECK_EQUAL(msg.getHeaders().getAsInt(REPLICATION_EVENT_TYPE), ENQUEUE); + BOOST_CHECK_EQUAL(msg.getHeaders().getAsInt64(REPLICATION_EVENT_SEQNO), (i+1)); + BOOST_CHECK_EQUAL((boost::format("%1%_%2%") % "Message" % (i+1)).str(), msg.getData()); + } + //check that we received dequeue events for first queue: + for (int i = 0; i < 10; i++) { + BOOST_CHECK(incoming.get(msg, qpid::sys::TIME_SEC)); + BOOST_CHECK_EQUAL(msg.getHeaders().getAsString(REPLICATION_TARGET_QUEUE), dataQ); + BOOST_CHECK_EQUAL(msg.getHeaders().getAsInt(REPLICATION_EVENT_TYPE), DEQUEUE); + BOOST_CHECK_EQUAL(msg.getHeaders().getAsInt(DEQUEUED_MESSAGE_POSITION), (i+1)); + BOOST_CHECK_EQUAL(msg.getHeaders().getAsInt64(REPLICATION_EVENT_SEQNO), (i+11)); + } + + sub.cancel(); + sub = f.subs.subscribe(incoming, eventQ2); + //check that we received enqueue events for second queue: + for (int i = 0; i < 10; i++) { + BOOST_CHECK(incoming.get(msg, qpid::sys::TIME_SEC)); + BOOST_CHECK_EQUAL(msg.getHeaders().getAsString(REPLICATION_TARGET_QUEUE), dataQ2); + BOOST_CHECK_EQUAL(msg.getHeaders().getAsInt(REPLICATION_EVENT_TYPE), ENQUEUE); + BOOST_CHECK_EQUAL(msg.getHeaders().getAsInt64(REPLICATION_EVENT_SEQNO), (i+1)); + BOOST_CHECK_EQUAL((boost::format("%1%_%2%") % "Message" % (i+1)).str(), msg.getData()); + } +} + + +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/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..157cabfb63 --- /dev/null +++ b/qpid/cpp/src/tests/SessionState.cpp @@ -0,0 +1,304 @@ +/* + * + * 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 boost; +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, bind(std::plus<T>(), _1, 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, + bind(&send, ref(s), + 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, + bind(transfer1Char, 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..d8ad4c14d8 --- /dev/null +++ b/qpid/cpp/src/tests/Shlib.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 "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) { + // The CMake-based build passes in the shared lib suffix; if it's not + // there, this is a Linux/UNIX libtool-based build. +#if defined (QPID_SHLIB_PREFIX) && defined (QPID_SHLIB_SUFFIX) + Shlib sh("./" QPID_SHLIB_PREFIX "shlibtest" QPID_SHLIB_POSTFIX QPID_SHLIB_SUFFIX); +#else + Shlib sh(".libs/libshlibtest.so"); +#endif + // 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; + { +#if defined (QPID_SHLIB_PREFIX) && defined (QPID_SHLIB_SUFFIX) + AutoShlib sh("./" QPID_SHLIB_PREFIX "shlibtest" QPID_SHLIB_POSTFIX QPID_SHLIB_SUFFIX); +#else + AutoShlib sh(".libs/libshlibtest.so"); +#endif + 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/SocketProxy.h b/qpid/cpp/src/tests/SocketProxy.h new file mode 100644 index 0000000000..d195f11aa9 --- /dev/null +++ b/qpid/cpp/src/tests/SocketProxy.h @@ -0,0 +1,183 @@ +#ifndef SOCKETPROXY_H +#define SOCKETPROXY_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/IOHandle.h" +#ifdef _WIN32 +# include "qpid/sys/windows/IoHandlePrivate.h" + typedef SOCKET FdType; +#else +# include "qpid/sys/posix/PrivatePosix.h" + typedef int FdType; +#endif +#include "qpid/sys/Socket.h" +#include "qpid/sys/Runnable.h" +#include "qpid/sys/Thread.h" +#include "qpid/sys/Mutex.h" +#include "qpid/log/Statement.h" + +#include <boost/lexical_cast.hpp> + +namespace qpid { +namespace tests { + +/** + * A simple socket proxy that forwards to another socket. + * Used between client & local broker to simulate network failures. + */ +class SocketProxy : private qpid::sys::Runnable +{ + // Need a Socket we can get the fd from + class LowSocket : public qpid::sys::Socket { + public: +#ifdef _WIN32 + FdType getFd() { return toSocketHandle(*this); } +#else + FdType getFd() { return toFd(impl); } +#endif + }; + + public: + /** Connect to connectPort on host, start a forwarding thread. + * Listen for connection on getPort(). + */ + SocketProxy(int connectPort, const std::string host="localhost") + : closed(false), joined(true), + port(listener.listen()), dropClient(), dropServer() + { + client.connect(host, boost::lexical_cast<std::string>(connectPort)); + joined = false; + thread = qpid::sys::Thread(static_cast<qpid::sys::Runnable*>(this)); + } + + ~SocketProxy() { close(); if (!joined) thread.join(); } + + /** Simulate a network disconnect. */ + void close() { + { + qpid::sys::Mutex::ScopedLock l(lock); + if (closed) { return; } + closed=true; + } + if (thread && thread != qpid::sys::Thread::current()) { + thread.join(); + joined = true; + } + client.close(); + } + + /** Simulate lost packets, drop data from client */ + void dropClientData(bool drop=true) { dropClient=drop; } + + /** Simulate lost packets, drop data from server */ + void dropServerData(bool drop=true) { dropServer=drop; } + + bool isClosed() const { + qpid::sys::Mutex::ScopedLock l(lock); + return closed; + } + + uint16_t getPort() const { return port; } + + private: + static void throwErrno(const std::string& msg) { + throw qpid::Exception(msg+":"+qpid::sys::strError(errno)); + } + static void throwIf(bool condition, const std::string& msg) { + if (condition) throw qpid::Exception(msg); + } + + void run() { + std::auto_ptr<LowSocket> server; + try { + fd_set socks; + FdType maxFd = listener.getFd(); + struct timeval tmo; + for (;;) { + FD_ZERO(&socks); + FD_SET(maxFd, &socks); + tmo.tv_sec = 0; + tmo.tv_usec = 500 * 1000; + if (select(maxFd+1, &socks, 0, 0, &tmo) == 0) { + qpid::sys::Mutex::ScopedLock l(lock); + throwIf(closed, "SocketProxy: Closed by close()"); + continue; + } + throwIf(!FD_ISSET(maxFd, &socks), "SocketProxy: Accept failed"); + break; // Accept ready... go to next step + } + server.reset(reinterpret_cast<LowSocket *>(listener.accept())); + maxFd = server->getFd(); + if (client.getFd() > maxFd) + maxFd = client.getFd(); + char buffer[1024]; + for (;;) { + FD_ZERO(&socks); + tmo.tv_sec = 0; + tmo.tv_usec = 500 * 1000; + FD_SET(client.getFd(), &socks); + FD_SET(server->getFd(), &socks); + if (select(maxFd+1, &socks, 0, 0, &tmo) == 0) { + qpid::sys::Mutex::ScopedLock l(lock); + throwIf(closed, "SocketProxy: Closed by close()"); + continue; + } + // Something is set; relay data as needed until something closes + if (FD_ISSET(server->getFd(), &socks)) { + int n = server->read(buffer, sizeof(buffer)); + throwIf(n <= 0, "SocketProxy: server disconnected"); + if (!dropServer) client.write(buffer, n); + } + if (FD_ISSET(client.getFd(), &socks)) { + int n = client.read(buffer, sizeof(buffer)); + throwIf(n <= 0, "SocketProxy: client disconnected"); + if (!dropServer) server->write(buffer, n); + } + if (!FD_ISSET(client.getFd(), &socks) && + !FD_ISSET(server->getFd(), &socks)) + throwIf(true, "SocketProxy: No handle ready"); + } + } + catch (const std::exception& e) { + QPID_LOG(debug, "SocketProxy::run exception: " << e.what()); + } + try { + if (server.get()) server->close(); + close(); + } + catch (const std::exception& e) { + QPID_LOG(debug, "SocketProxy::run exception in client/server close()" << e.what()); + } + } + + mutable qpid::sys::Mutex lock; + mutable bool closed; + bool joined; + LowSocket client, listener; + uint16_t port; + qpid::sys::Thread thread; + bool dropClient, dropServer; +}; + +}} // namespace qpid::tests + +#endif diff --git a/qpid/cpp/src/tests/Statistics.cpp b/qpid/cpp/src/tests/Statistics.cpp new file mode 100644 index 0000000000..19531762b1 --- /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(sys::EPOCH, sys::now())); + 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/StoreStatus.cpp b/qpid/cpp/src/tests/StoreStatus.cpp new file mode 100644 index 0000000000..43d4cfd920 --- /dev/null +++ b/qpid/cpp/src/tests/StoreStatus.cpp @@ -0,0 +1,117 @@ +/* + * + * 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/cluster/StoreStatus.h" +#include "qpid/framing/Uuid.h" +#include <boost/assign.hpp> +#include <boost/filesystem/operations.hpp> + +using namespace std; +using namespace qpid::cluster; +using namespace qpid::framing; +using namespace qpid::framing::cluster; +using namespace boost::assign; +using namespace boost::filesystem; + + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(StoreStatusTestSuite) + +const char* TEST_DIR = "StoreStatus.tmp"; + +struct TestDir { + TestDir() { + remove_all(TEST_DIR); + create_directory(TEST_DIR); + } + ~TestDir() { + remove_all(TEST_DIR); + } +}; + +QPID_AUTO_TEST_CASE(testLoadEmpty) { + TestDir td; + StoreStatus ss(TEST_DIR); + BOOST_CHECK_EQUAL(ss.getState(), STORE_STATE_NO_STORE); + BOOST_CHECK(!ss.getClusterId()); + BOOST_CHECK(!ss.getShutdownId()); + ss.load(); + BOOST_CHECK_EQUAL(ss.getState(), STORE_STATE_EMPTY_STORE); + BOOST_CHECK(!ss.getShutdownId()); +} + +QPID_AUTO_TEST_CASE(testSaveLoadDirty) { + TestDir td; + Uuid clusterId = Uuid(true); + StoreStatus ss(TEST_DIR); + ss.load(); + ss.setClusterId(clusterId); + ss.dirty(); + BOOST_CHECK_EQUAL(ss.getState(), STORE_STATE_DIRTY_STORE); + + StoreStatus ss2(TEST_DIR); + ss2.load(); + BOOST_CHECK_EQUAL(ss2.getState(), STORE_STATE_DIRTY_STORE); + BOOST_CHECK_EQUAL(ss2.getClusterId(), clusterId); + BOOST_CHECK(!ss2.getShutdownId()); +} + +QPID_AUTO_TEST_CASE(testSaveLoadClean) { + TestDir td; + Uuid clusterId = Uuid(true); + Uuid shutdownId = Uuid(true); + StoreStatus ss(TEST_DIR); + ss.load(); + ss.setClusterId(clusterId); + ss.clean(shutdownId); + BOOST_CHECK_EQUAL(ss.getState(), STORE_STATE_CLEAN_STORE); + + StoreStatus ss2(TEST_DIR); + ss2.load(); + BOOST_CHECK_EQUAL(ss2.getState(), STORE_STATE_CLEAN_STORE); + BOOST_CHECK_EQUAL(ss2.getClusterId(), clusterId); + BOOST_CHECK_EQUAL(ss2.getShutdownId(), shutdownId); +} + +QPID_AUTO_TEST_CASE(testMarkDirty) { + // Save clean then mark to dirty. + TestDir td; + Uuid clusterId = Uuid(true); + Uuid shutdownId = Uuid(true); + StoreStatus ss(TEST_DIR); + ss.load(); + ss.setClusterId(clusterId); + ss.dirty(); + ss.clean(shutdownId); + ss.dirty(); + + StoreStatus ss2(TEST_DIR); + ss2.load(); + BOOST_CHECK_EQUAL(ss2.getState(), STORE_STATE_DIRTY_STORE); + BOOST_CHECK_EQUAL(ss2.getClusterId(), clusterId); + BOOST_CHECK(!ss2.getShutdownId()); +} + +QPID_AUTO_TEST_SUITE_END() + + }} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/StringUtils.cpp b/qpid/cpp/src/tests/StringUtils.cpp new file mode 100644 index 0000000000..6a19119288 --- /dev/null +++ b/qpid/cpp/src/tests/StringUtils.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 <iostream> +#include "qpid/StringUtils.h" + +#include "unit_test.h" + +QPID_AUTO_TEST_SUITE(StringUtilsTestSuite) + +using namespace qpid; +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/TestMessageStore.h b/qpid/cpp/src/tests/TestMessageStore.h new file mode 100644 index 0000000000..20e0b755b2 --- /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<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..6a0a196f4e --- /dev/null +++ b/qpid/cpp/src/tests/TimerTest.cpp @@ -0,0 +1,130 @@ + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 "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); +#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); +} + +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..ff8931f9c9 --- /dev/null +++ b/qpid/cpp/src/tests/TopicExchangeTest.cpp @@ -0,0 +1,406 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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/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; + +private: + // binding node iterator that collects all routes that are bound + class TestFinder : public TopicExchange::BindingNode::TreeIterator { + public: + TestFinder(BindingVec& m) : bv(m) {}; + ~TestFinder() {}; + bool visit(BindingNode& 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.addBindingKey(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.getBindingKey(routingPattern); + if (bk) { + bk->bindingVector.pop_back(); + if (bk->bindingVector.empty()) { + // no more bindings - remove this node + bindingTree.removeBindingKey(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: + TopicExchange::BindingNode 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/TxBufferTest.cpp b/qpid/cpp/src/tests/TxBufferTest.cpp new file mode 100644 index 0000000000..4807026ab7 --- /dev/null +++ b/qpid/cpp/src/tests/TxBufferTest.cpp @@ -0,0 +1,181 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 <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)); + + BOOST_CHECK(buffer.commitLocal(&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)); + + BOOST_CHECK(!buffer.commitLocal(&store)); + 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..72cb50cd21 --- /dev/null +++ b/qpid/cpp/src/tests/TxMocks.h @@ -0,0 +1,235 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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); + } + 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); + } + + void accept(TxOpConstVisitor&) const {} + + ~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/TxPublishTest.cpp b/qpid/cpp/src/tests/TxPublishTest.cpp new file mode 100644 index 0000000000..210abf0a5b --- /dev/null +++ b/qpid/cpp/src/tests/TxPublishTest.cpp @@ -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. + * + */ +#include "qpid/broker/NullMessageStore.h" +#include "qpid/broker/RecoveryManager.h" +#include "qpid/broker/TxPublish.h" +#include "unit_test.h" +#include <iostream> +#include <list> +#include <vector> +#include "MessageUtils.h" +#include "TestMessageStore.h" + +using std::list; +using std::pair; +using std::vector; +using boost::intrusive_ptr; +using namespace qpid::broker; +using namespace qpid::framing; + +namespace qpid { +namespace tests { + +struct TxPublishTest +{ + + TestMessageStore store; + Queue::shared_ptr queue1; + Queue::shared_ptr queue2; + intrusive_ptr<Message> msg; + TxPublish op; + + TxPublishTest() : + queue1(new Queue("queue1", false, &store, 0)), + queue2(new Queue("queue2", false, &store, 0)), + msg(MessageUtils::createMessage("exchange", "routing_key", false, "id")), + op(msg) + { + msg->getProperties<DeliveryProperties>()->setDeliveryMode(PERSISTENT); + op.deliverTo(queue1); + op.deliverTo(queue2); + } +}; + + +QPID_AUTO_TEST_SUITE(TxPublishTestSuite) + +QPID_AUTO_TEST_CASE(testPrepare) +{ + TxPublishTest t; + + intrusive_ptr<PersistableMessage> pmsg = boost::static_pointer_cast<PersistableMessage>(t.msg); + //ensure messages are enqueued in store + t.op.prepare(0); + BOOST_CHECK_EQUAL((size_t) 2, t.store.enqueued.size()); + BOOST_CHECK_EQUAL(string("queue1"), t.store.enqueued[0].first); + BOOST_CHECK_EQUAL(pmsg, t.store.enqueued[0].second); + BOOST_CHECK_EQUAL(string("queue2"), t.store.enqueued[1].first); + BOOST_CHECK_EQUAL(pmsg, t.store.enqueued[1].second); + BOOST_CHECK_EQUAL( true, ( boost::static_pointer_cast<PersistableMessage>(t.msg))->isIngressComplete()); +} + +QPID_AUTO_TEST_CASE(testCommit) +{ + TxPublishTest t; + + //ensure messages are delivered to queue + t.op.prepare(0); + t.op.commit(); + BOOST_CHECK_EQUAL((uint32_t) 1, t.queue1->getMessageCount()); + intrusive_ptr<Message> msg_dequeue = t.queue1->get().payload; + + BOOST_CHECK_EQUAL( true, (boost::static_pointer_cast<PersistableMessage>(msg_dequeue))->isIngressComplete()); + BOOST_CHECK_EQUAL(t.msg, msg_dequeue); + + BOOST_CHECK_EQUAL((uint32_t) 1, t.queue2->getMessageCount()); + BOOST_CHECK_EQUAL(t.msg, t.queue2->get().payload); +} + +QPID_AUTO_TEST_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/Url.cpp b/qpid/cpp/src/tests/Url.cpp new file mode 100644 index 0000000000..234a62ee91 --- /dev/null +++ b/qpid/cpp/src/tests/Url.cpp @@ -0,0 +1,90 @@ +/* + * + * 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(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..0195455ca3 --- /dev/null +++ b/qpid/cpp/src/tests/Uuid.cpp @@ -0,0 +1,137 @@ +/* + * + * 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 "qpid/sys/alloca.h" + +#include "unit_test.h" + +#include <set> + +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 = {{'\x1b', '\x4e', '\x28', '\xba', '\x2f', '\xa1', '\x11', '\xd2', '\x88', '\x3f', '\xb9', '\xa7', '\x61', '\xbd', '\xe3', '\xfb'}}; +const string sampleStr("1b4e28ba-2fa1-11d2-883f-b9a761bde3fb"); +const string zeroStr("00000000-0000-0000-0000-000000000000"); + +QPID_AUTO_TEST_CASE(testUuidIstream) { + Uuid uuid; + istringstream in(sampleStr); + in >> uuid; + BOOST_CHECK(!in.fail()); + BOOST_CHECK(uuid == sample); + + istringstream is(zeroStr); + Uuid zero; + is >> zero; + BOOST_CHECK(!in.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(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) { + char* buff = static_cast<char*>(::alloca(Uuid::size())); + Buffer wbuf(buff, Uuid::size()); + Uuid uuid(sample.c_array()); + uuid.encode(wbuf); + + Buffer rbuf(buff, Uuid::size()); + Uuid decoded; + decoded.decode(rbuf); + BOOST_CHECK_EQUAL(string(sample.begin(), sample.end()), + string(decoded.begin(), decoded.end())); +} + +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..40f1c0cf75 --- /dev/null +++ b/qpid/cpp/src/tests/Variant.cpp @@ -0,0 +1,775 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 "qpid/amqp_0_10/Codecs.h" + +#include "unit_test.h" + +using namespace qpid::types; +using namespace qpid::amqp_0_10; + +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 = "-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_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..b3b7f12b53 --- /dev/null +++ b/qpid/cpp/src/tests/XmlClientSessionTest.cpp @@ -0,0 +1,297 @@ +/* + * + * 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) + +using namespace qpid::client; + +using namespace qpid::client::arg; +using namespace qpid::framing; +using namespace qpid; +using qpid::sys::Shlib; +using qpid::sys::Monitor; +using std::string; +using std::cout; +using std::endl; + + +Shlib shlib(getLibPath("XML_LIB")); + +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 ProxySessionFixture +{ + 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_AUTO_TEST_CASE(testXmlBinding) { + 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_AUTO_TEST_CASE(testXMLBindMultipleQueues) { + 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_AUTO_TEST_CASE(testXMLSendBadXML) { + 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_AUTO_TEST_CASE(testXMLBadXQuery) { + 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_AUTO_TEST_CASE(testXmlBindingUntyped) { + 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_AUTO_TEST_CASE(testXmlBindingTyped) { + 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..5e9a150d8f --- /dev/null +++ b/qpid/cpp/src/tests/acl.py @@ -0,0 +1,1077 @@ +#!/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 + +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): + + 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): + acl = self.qmf.getObjects(_class="acl")[0] + return acl.reloadACLFile() + + 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.startQmf() + 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) + + #===================================== + # 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.text.find("format error",0,len(result.text)) != -1): + 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.text.find("format error",0,len(result.text)) != -1): + 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) + + + #===================================== + # 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.text.find("Insufficient tokens for acl definition",0,len(result.text)) == -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.text.find("Unknown ACL permission",0,len(result.text)) == -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.text.find("contains an illegal extension",0,len(result.text)) == -1): + self.fail(result) + + if (result.text.find("Non-continuation line must start with \"group\" or \"acl\"",0,len(result.text)) == -1): + self.fail(result) + + def test_llegal_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.text.find("ACL format error",0,len(result.text)) != -1): + self.fail(result) + + def test_user_realm(self): + """ + Test a user defined without a realm + Ex. group admin rajith + """ + 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.text.find("Username 'bob' must contain a realm",0,len(result.text)) == -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.text.find("ACL format error",0,len(result.text)) != -1): + 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.text.find("Username \"joe$H@EXAMPLE.com\" contains illegal characters",0,len(result.text)) == -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" \ + " { 'ring' 'ring_strict' 'flow_to_disk' 'reject' }"; + if (result.text != expected): + self.fail(result) + + def test_illegal_queue_size(self): + """ + Test illegal queue policy + """ + + 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 'maxqueuesize', " \ + "values should be between 0 and 9223372036854775807"; + if (result.text != expected): + 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 'maxqueuesize', " \ + "values should be between 0 and 9223372036854775807"; + if (result.text != expected): + self.fail(result) + + + def test_illegal_queue_count(self): + """ + Test illegal queue policy + """ + + 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 'maxqueuecount', " \ + "values should be between 0 and 9223372036854775807"; + if (result.text != expected): + 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 'maxqueuecount', " \ + "values should be between 0 and 9223372036854775807"; + if (result.text != expected): + 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 create queue name=q1 durable=true passive=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 purge 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 allow all all') + aclf.close() + + result = self.reload_acl() + if (result.text.find("format error",0,len(result.text)) != -1): + self.fail(result) + + session = self.get_session('bob','bob') + + try: + session.queue_declare(queue="q1", durable=True, passive=True) + self.fail("ACL should deny queue create request with name=q1 durable=true passive=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.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') + + try: + session.queue_purge(queue="q3") + self.fail("ACL should deny queue purge 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_purge(queue="q4") + except qpid.session.SessionException, e: + if (403 == e.args[0].error_code): + self.fail("ACL should allow queue purge request for q4"); + + 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 create queue name=q1 durable=true passive=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 anonymous all all\n') + aclf.write('acl deny all all') + aclf.close() + + result = self.reload_acl() + if (result.text.find("format error",0,len(result.text)) != -1): + self.fail(result) + + session = self.get_session('bob','bob') + + 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 create 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=q2 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=q2 maxqueuesize=500 maxqueuecount=200"); + + 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="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 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 create exchange name=testEx durable=true passive=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.text.find("format error",0,len(result.text)) != -1): + 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, passive=True) + self.fail("ACL should deny exchange create 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=True, passive=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 and passive=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 passive=false\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.text.find("format error",0,len(result.text)) != -1): + 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='myXml') + self.fail("ACL should deny exchange delete request for myXml"); + except qpid.session.SessionException, e: + self.assertEqual(403,e.args[0].error_code) + session = self.get_session('bob','bob') + + 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.text.find("format error",0,len(result.text)) != -1): + 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 + + + #===================================== + # 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.text.find("format error",0,len(result.text)) != -1): + 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='myq1') + 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.text.find("format error",0,len(result.text)) != -1): + 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 allow all all') + aclf.close() + + result = self.reload_acl() + if (result.text.find("format error",0,len(result.text)) != -1): + 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"); + + + 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 anonymous all all \n') + aclf.write('acl deny all all') + aclf.close() + + result = self.reload_acl() + if (result.text.find("format error",0,len(result.text)) != -1): + 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"); + +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/ais_check b/qpid/cpp/src/tests/ais_check new file mode 100755 index 0000000000..92eaa9dd39 --- /dev/null +++ b/qpid/cpp/src/tests/ais_check @@ -0,0 +1,34 @@ +#!/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. +# + +srcdir=`dirname $0` + +# Check AIS requirements and run tests if found. +ps -u root | grep 'aisexec\|corosync' >/dev/null || { + echo WARNING: Skipping cluster tests, the aisexec or corosync daemon is not running. + exit 0; # A warning, not a failure. +} + +# Execute command with the ais group set if user is a member. +with_ais_group() { + if id -nG | grep '\<ais\>' >/dev/null; then sg ais -c "$*" + else "$@" + fi +} 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..e43571aed4 --- /dev/null +++ b/qpid/cpp/src/tests/allhosts @@ -0,0 +1,77 @@ +#!/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. +# + +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 +" + exit 1 +} + +while getopts "tl:bs:dqo:" 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 ;; + *) 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/amqp_0_10/Map.cpp b/qpid/cpp/src/tests/amqp_0_10/Map.cpp new file mode 100644 index 0000000000..ffb235829e --- /dev/null +++ b/qpid/cpp/src/tests/amqp_0_10/Map.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 "amqp_0_10/unit_test.h" +#include "qpid/amqp_0_10/Map.h" +#include "qpid/amqp_0_10/Array.h" +#include "qpid/amqp_0_10/Struct32.h" +#include "qpid/amqp_0_10/UnknownType.h" +#include "qpid/amqp_0_10/Codec.h" +#include <iostream> + +using namespace qpid::amqp_0_10; +using namespace std; + +QPID_AUTO_TEST_SUITE(MapTestSuite) + + QPID_AUTO_TEST_CASE(testGetSet) { + MapValue v; + v = Str8("foo"); + BOOST_CHECK(v.get<Str8>()); + BOOST_CHECK(!v.get<uint8_t>()); + BOOST_CHECK_EQUAL(*v.get<Str8>(), "foo"); + + v = uint8_t(42); + BOOST_CHECK(!v.get<Str8>()); + BOOST_CHECK(v.get<uint8_t>()); + BOOST_CHECK_EQUAL(*v.get<uint8_t>(), 42); + + v = uint16_t(12); + BOOST_CHECK(v.get<uint16_t>()); + BOOST_CHECK_EQUAL(*v.get<uint16_t>(), 12); +} + +template <class R> struct TestVisitor : public MapValue::Visitor<R> { + template <class T> R operator()(const T&) const { throw MapValue::BadTypeException(); } + R operator()(const R& r) const { return r; } +}; + +QPID_AUTO_TEST_CASE(testVisit) { + MapValue v; + v = Str8("foo"); + BOOST_CHECK_EQUAL(v.apply_visitor(TestVisitor<Str8>()), "foo"); + v = Uint16(42); + BOOST_CHECK_EQUAL(v.apply_visitor(TestVisitor<Uint16>()), 42); + try { + v.apply_visitor(TestVisitor<bool>()); + BOOST_FAIL("Expecting exception"); + } + catch(const MapValue::BadTypeException&) {} +} + + +QPID_AUTO_TEST_CASE(testEncodeMapValue) { + MapValue mv; + std::string data; + mv = Str8("hello"); + Codec::encode(back_inserter(data))(mv); + BOOST_CHECK_EQUAL(data.size(), Codec::size(mv)); + MapValue mv2; + Codec::decode(data.begin())(mv2); + BOOST_CHECK_EQUAL(mv2.getCode(), 0x85); + BOOST_REQUIRE(mv2.get<Str8>()); + BOOST_CHECK_EQUAL(*mv2.get<Str8>(), "hello"); +} + +QPID_AUTO_TEST_CASE(testEncode) { + Map map; + std::string data; + map["A"] = true; + map["b"] = Str8("hello"); + Codec::encode(back_inserter(data))(map); + BOOST_CHECK_EQUAL(Codec::size(map), data.size()); + Map map2; + Codec::decode(data.begin())(map2); + BOOST_CHECK_EQUAL(map.size(), 2u); + BOOST_CHECK(map["A"].get<bool>()); + BOOST_CHECK_EQUAL(*map["b"].get<Str8>(), "hello"); +} + + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/amqp_0_10/ProxyTemplate.cpp b/qpid/cpp/src/tests/amqp_0_10/ProxyTemplate.cpp new file mode 100644 index 0000000000..f54ee0da22 --- /dev/null +++ b/qpid/cpp/src/tests/amqp_0_10/ProxyTemplate.cpp @@ -0,0 +1,49 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 "amqp_0_10/unit_test.h" +#include "qpid/amqp_0_10/ProxyTemplate.h" +#include <boost/any.hpp> + +QPID_AUTO_TEST_SUITE(ProxyTemplateTestSuite) + +using namespace qpid::amqp_0_10; + +struct ToAny { + template <class T> + boost::any operator()(const T& t) { return boost::any(t); } +}; + +struct AnyProxy : public ProxyTemplate<ToAny, boost::any> {}; + +QPID_AUTO_TEST_CASE(testAnyProxy) { + AnyProxy p; + boost::any a=p.connectionTune(1,2,3,4); + BOOST_CHECK_EQUAL(a.type().name(), typeid(connection::Tune).name()); + connection::Tune* tune=boost::any_cast<connection::Tune>(&a); + BOOST_REQUIRE(tune); + BOOST_CHECK_EQUAL(tune->channelMax, 1u); + BOOST_CHECK_EQUAL(tune->maxFrameSize, 2u); + BOOST_CHECK_EQUAL(tune->heartbeatMin, 3u); + BOOST_CHECK_EQUAL(tune->heartbeatMax, 4u); +} + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/amqp_0_10/apply.cpp b/qpid/cpp/src/tests/amqp_0_10/apply.cpp new file mode 100644 index 0000000000..0aa4421791 --- /dev/null +++ b/qpid/cpp/src/tests/amqp_0_10/apply.cpp @@ -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. + * + */ +#include "amqp_0_10/unit_test.h" +#include "qpid/amqp_0_10/specification.h" +#include "qpid/amqp_0_10/ApplyControl.h" + +QPID_AUTO_TEST_SUITE(VisitorTestSuite) + +using namespace qpid::amqp_0_10; + +struct GetCode : public ApplyFunctor<uint8_t> { + template <class T> uint8_t operator()(const T&) const { return T::CODE; } +}; + +struct SetChannelMax : ApplyFunctor<void> { + template <class T> void operator()(T&) const { BOOST_FAIL(""); } + void operator()(connection::Tune& t) const { t.channelMax=42; } +}; + +struct TestFunctor { + typedef bool result_type; + bool operator()(const connection::Tune& tune) { + BOOST_CHECK_EQUAL(tune.channelMax, 1u); + BOOST_CHECK_EQUAL(tune.maxFrameSize, 2u); + BOOST_CHECK_EQUAL(tune.heartbeatMin, 3u); + BOOST_CHECK_EQUAL(tune.heartbeatMax, 4u); + return true; + } + template <class T> + bool operator()(const T&) { return false; } +}; + +QPID_AUTO_TEST_CASE(testApply) { + connection::Tune tune(1,2,3,4); + Control* p = &tune; + + // boost oddity - without the cast we get undefined symbol errors. + BOOST_CHECK_EQUAL(apply(GetCode(), *p), (uint8_t)connection::Tune::CODE); + + TestFunctor tf; + BOOST_CHECK(apply(tf, *p)); + + connection::Start start; + p = &start; + BOOST_CHECK(!apply(tf, *p)); + + apply(SetChannelMax(), tune); + BOOST_CHECK_EQUAL(tune.channelMax, 42); +} + +struct VoidTestFunctor { + typedef void result_type; + + int code; + VoidTestFunctor() : code() {} + + void operator()(const connection::Tune& tune) { + BOOST_CHECK_EQUAL(tune.channelMax, 1u); + BOOST_CHECK_EQUAL(tune.maxFrameSize, 2u); + BOOST_CHECK_EQUAL(tune.heartbeatMin, 3u); + BOOST_CHECK_EQUAL(tune.heartbeatMax, 4u); + code=connection::Tune::CODE; + } + template <class T> + void operator()(const T&) { code=0xFF; } +}; + +QPID_AUTO_TEST_CASE(testApplyVoid) { + connection::Tune tune(1,2,3,4); + Control* p = &tune; + VoidTestFunctor tf; + apply(tf, *p); + BOOST_CHECK_EQUAL(uint8_t(connection::Tune::CODE), tf.code); + + connection::Start start; + p = &start; + apply(tf, *p); + BOOST_CHECK_EQUAL(0xFF, tf.code); +} + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/amqp_0_10/handlers.cpp b/qpid/cpp/src/tests/amqp_0_10/handlers.cpp new file mode 100644 index 0000000000..91bb304a17 --- /dev/null +++ b/qpid/cpp/src/tests/amqp_0_10/handlers.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 "amqp_0_10/unit_test.h" +#include "qpid/Exception.h" +#include "qpid/amqp_0_10/Unit.h" +#include "qpid/amqp_0_10/ControlHolder.h" +#include "qpid/amqp_0_10/CommandHolder.h" +#include "qpid/amqp_0_10/handlers.h" +#include "qpid/amqp_0_10/specification.h" + +QPID_AUTO_TEST_SUITE(handler_tests) + +using namespace qpid::amqp_0_10; +using namespace std; + +string called; // Set by called handler function + +// Note on handlers: +// +// Control and Command handlers are separate, both behave the same way, +// so substitute "control or command" for command in the following. +// +// Command handlers derive from CommandHandler and implement functions +// for all the commands they handle. Handling an unimplemented command +// will raise NotImplementedException. +// +// Using virtual inheritance from CommandHandler allows multiple +// handlers to be aggregated into one with multiple inheritance, +// See test code for example. +// +// E.g. the existing broker model would have two control handlers: +// - ConnectionHandler: ControlHandler for connection controls. +// - SessionHandler: ControlHandler for session controls. +// It would have class-command handlers for each AMQP class: +// - QueueHandler, MessageHandler etc.. handle each class. +// And an aggregate handler in place of BrokerAdapter +// - BrokerCommandHandler: public QueueHandler, MessageHandler ... +// +// In other applications (e.g. cluster) any combination of commands +// can be handled by a given handler. It _might_ simplify the code +// to collaps ConnectionHandler and SessionHandler into a single +// ControlHandler (or it might not.) + +struct TestExecutionHandler : public virtual CommandHandler { + void executionSync() { called = "executionSync"; } + // ... etc. for all execution commands +}; + +struct TestMessageHandler : public virtual CommandHandler { + void messageCancel(const Str8&) { called="messageCancel"; } + // ... etc. +}; + +// Aggregate handler for all recognised commands. +struct TestCommandHandler : + public TestExecutionHandler, + public TestMessageHandler + // ... etc. handlers for all command classes. +{}; // Nothing to do. + + +// Sample unit handler, written as a static_visitor. +// Note it could equally be written with if/else statements +// in handle. +// +struct TestUnitHandler : public boost::static_visitor<void> { + TestCommandHandler handler; + void handle(const Unit& u) { u.applyVisitor(*this); } + + void operator()(const Body&) { called="Body"; } + void operator()(const Header&) { called="Header"; } + void operator()(const ControlHolder&) { throw qpid::Exception("I don't do controls."); } + void operator()(const CommandHolder& c) { c.invoke(handler); } +}; + +QPID_AUTO_TEST_CASE(testHandlers) { + TestUnitHandler handler; + Unit u; + + u = Body(); + handler.handle(u); + BOOST_CHECK_EQUAL("Body", called); + + u = Header(); + handler.handle(u); + BOOST_CHECK_EQUAL("Header", called); + + // in_place<Foo>(...) is equivalent to Foo(...) but + // constructs Foo directly in the holder, avoiding + // a copy. + + u = CommandHolder(in_place<execution::Sync>()); + handler.handle(u); + BOOST_CHECK_EQUAL("executionSync", called); + + u = ControlHolder(in_place<connection::Start>(Map(), Str16Array(), Str16Array())); + try { + handler.handle(u); + } catch (const qpid::Exception&) {} + + u = CommandHolder(in_place<message::Cancel>(Str8())); + handler.handle(u); + BOOST_CHECK_EQUAL("messageCancel", called); +} + +QPID_AUTO_TEST_SUITE_END() diff --git a/qpid/cpp/src/tests/amqp_0_10/serialize.cpp b/qpid/cpp/src/tests/amqp_0_10/serialize.cpp new file mode 100644 index 0000000000..975d6206ec --- /dev/null +++ b/qpid/cpp/src/tests/amqp_0_10/serialize.cpp @@ -0,0 +1,429 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 "amqp_0_10/unit_test.h" +#include "amqp_0_10/allSegmentTypes.h" + +#include "qpid/framing/AMQFrame.h" +#include "qpid/framing/Buffer.h" + +#include "qpid/amqp_0_10/Packer.h" +#include "qpid/amqp_0_10/built_in_types.h" +#include "qpid/amqp_0_10/Codec.h" +#include "qpid/amqp_0_10/specification.h" +#include "qpid/amqp_0_10/ControlHolder.h" +#include "qpid/amqp_0_10/Struct32.h" +#include "qpid/amqp_0_10/FrameHeader.h" +#include "qpid/amqp_0_10/Map.h" +#include "qpid/amqp_0_10/Unit.h" +#include "allSegmentTypes.h" + +#include <boost/test/test_case_template.hpp> +#include <boost/type_traits/is_arithmetic.hpp> +#include <boost/utility/enable_if.hpp> +#include <boost/optional.hpp> +#include <boost/mpl/vector.hpp> +#include <boost/mpl/back_inserter.hpp> +#include <boost/mpl/copy.hpp> +#include <boost/mpl/empty_sequence.hpp> +#include <boost/current_function.hpp> +#include <iterator> +#include <string> +#include <sstream> +#include <iostream> +#include <netinet/in.h> + +// Missing operators needed for tests. +namespace boost { +template <class T, size_t N> +std::ostream& operator<<(std::ostream& out, const array<T,N>& a) { + std::ostream_iterator<T> o(out, " "); + std::copy(a.begin(), a.end(), o); + return out; +} +} // boost + +QPID_AUTO_TEST_SUITE(SerializeTestSuite) + +using namespace std; +namespace mpl=boost::mpl; +using namespace qpid::amqp_0_10; +using qpid::framing::in_place; + +template <class A, class B> struct concat2 { typedef typename mpl::copy<B, typename mpl::back_inserter<A> >::type type; }; +template <class A, class B, class C> struct concat3 { typedef typename concat2<A, typename concat2<B, C>::type>::type type; }; +template <class A, class B, class C, class D> struct concat4 { typedef typename concat2<A, typename concat3<B, C, D>::type>::type type; }; + +typedef mpl::vector<Boolean, Char, Int32, Int64, Int8, Uint16, CharUtf32, Uint32, Uint64, Bin8, Uint8>::type IntegralTypes; +typedef mpl::vector<Bin1024, Bin128, Bin16, Bin256, Bin32, Bin40, Bin512, Bin64, Bin72>::type BinTypes; +typedef mpl::vector<Double, Float>::type FloatTypes; +typedef mpl::vector<SequenceNo, Uuid, Datetime, Dec32, Dec64> FixedSizeClassTypes; +typedef mpl::vector<Map, Vbin8, Str8Latin, Str8, Str8Utf16, Vbin16, Str16Latin, Str16, Str16Utf16, Vbin32> VariableSizeTypes; + +typedef concat4<IntegralTypes, BinTypes, FloatTypes, FixedSizeClassTypes>::type FixedSizeTypes; +typedef concat2<FixedSizeTypes, VariableSizeTypes>::type AllTypes; + +// TODO aconway 2008-02-20: should test 64 bit integrals for order also. +QPID_AUTO_TEST_CASE(testNetworkByteOrder) { + string data; + + uint32_t l = 0x11223344; + Codec::encode(std::back_inserter(data))(l); + uint32_t enc=reinterpret_cast<const uint32_t&>(*data.data()); + uint32_t l2 = ntohl(enc); + BOOST_CHECK_EQUAL(l, l2); + + data.clear(); + uint16_t s = 0x1122; + Codec::encode(std::back_inserter(data))(s); + uint32_t s2 = ntohs(*reinterpret_cast<const uint32_t*>(data.data())); + BOOST_CHECK_EQUAL(s, s2); +} + +QPID_AUTO_TEST_CASE(testSetLimit) { + typedef Codec::Encoder<back_insert_iterator<string> > Encoder; + string data; + Encoder encode(back_inserter(data), 3); + encode('1')('2')('3'); + try { + encode('4'); + BOOST_FAIL("Expected exception"); + } catch (...) {} // FIXME aconway 2008-04-03: catch proper exception + BOOST_CHECK_EQUAL(data, "123"); +} + +QPID_AUTO_TEST_CASE(testScopedLimit) { + typedef Codec::Encoder<back_insert_iterator<string> > Encoder; + string data; + Encoder encode(back_inserter(data), 10); + encode(Str8("123")); // 4 bytes + { + Encoder::ScopedLimit l(encode, 3); + encode('a')('b')('c'); + try { + encode('d'); + BOOST_FAIL("Expected exception"); + } catch(...) {} // FIXME aconway 2008-04-03: catch proper exception + } + BOOST_CHECK_EQUAL(data, "\003123abc"); + encode('x')('y')('z'); + try { + encode('!'); + BOOST_FAIL("Expected exception"); + } catch(...) {} // FIXME aconway 2008-04-03: catch proper exception + BOOST_CHECK_EQUAL(data.size(), 10u); +} + +// Assign test values to the various types. +void testValue(bool& b) { b = true; } +void testValue(Bit&) { } +template <class T> typename boost::enable_if<boost::is_arithmetic<T> >::type testValue(T& n) { n=42; } +void testValue(CharUtf32& c) { c = 43; } +void testValue(long long& l) { l = 0x012345; } +void testValue(Datetime& dt) { dt = qpid::sys::now(); } +void testValue(Uuid& uuid) { uuid=Uuid(true); } +template <class E, class M> void testValue(Decimal<E,M>& d) { d.exponent=2; d.mantissa=0x1122; } +void testValue(SequenceNo& s) { s = 42; } +template <size_t N> void testValue(Bin<N>& a) { a.assign(42); } +template <class T, class S, int Unique> void testValue(SerializableString<T, S, Unique>& s) { + char msg[]="foobar"; + s.assign(msg, msg+sizeof(msg)); +} +void testValue(Str16& s) { s = "the quick brown fox jumped over the lazy dog"; } +void testValue(Str8& s) { s = "foobar"; } +void testValue(Map& m) { m["s"] = Str8("foobar"); m["b"] = true; m["c"] = uint16_t(42); } + +//typedef mpl::vector<Str8, Str16>::type TestTypes; +/*BOOST_AUTO_TEST_CASE_TEMPLATE(testEncodeDecode, T, AllTypes) +{ + string data; + T t; + testValue(t); + Codec::encode(std::back_inserter(data))(t); + + BOOST_CHECK_EQUAL(Codec::size(t), data.size()); + + T t2; + Codec::decode(data.begin())(t2); + BOOST_CHECK_EQUAL(t,t2); +} +*/ + +struct TestMe { + bool encoded, decoded; + char value; + TestMe(char v) : encoded(), decoded(), value(v) {} + template <class S> void encode(S& s) const { + const_cast<TestMe*>(this)->encoded=true; s(value); + } + template <class S> void decode(S& s) { decoded=true; s(value); } + template <class S> void serialize(S& s) { s.split(*this); } +}; + +QPID_AUTO_TEST_CASE(testSplit) { + string data; + TestMe t1('x'); + Codec::encode(std::back_inserter(data))(t1); + BOOST_CHECK(t1.encoded); + BOOST_CHECK(!t1.decoded); + BOOST_CHECK_EQUAL(data, "x"); + + TestMe t2('y'); + Codec::decode(data.begin())(t2); + BOOST_CHECK(!t2.encoded); + BOOST_CHECK(t2.decoded); + BOOST_CHECK_EQUAL(t2.value, 'x'); +} + +QPID_AUTO_TEST_CASE(testControlEncodeDecode) { + string data; + Control::Holder h(in_place<connection::Tune>(1,2,3,4)); + Codec::encode(std::back_inserter(data))(h); + + BOOST_CHECK_EQUAL(data.size(), Codec::size(h)); + + Codec::Decoder<string::iterator> decode(data.begin()); + Control::Holder h2; + decode(h2); + + BOOST_REQUIRE(h2.get()); + BOOST_CHECK_EQUAL(h2.get()->getClassCode(), connection::CODE); + BOOST_CHECK_EQUAL(h2.get()->getCode(), uint8_t(connection::Tune::CODE)); + connection::Tune& tune=static_cast<connection::Tune&>(*h2.get()); + BOOST_CHECK_EQUAL(tune.channelMax, 1u); + BOOST_CHECK_EQUAL(tune.maxFrameSize, 2u); + BOOST_CHECK_EQUAL(tune.heartbeatMin, 3u); + BOOST_CHECK_EQUAL(tune.heartbeatMax, 4u); +} + +QPID_AUTO_TEST_CASE(testStruct32) { + message::DeliveryProperties dp; + dp.priority=message::MEDIUM; + dp.routingKey="foo"; + Struct32 s(dp); + string data; + Codec::encode(back_inserter(data))(s); + + uint32_t structSize; // Starts with size + Codec::decode(data.begin())(structSize); + BOOST_CHECK_EQUAL(structSize, Codec::size(dp) + 2); // +2 for code + BOOST_CHECK_EQUAL(structSize, data.size()-4); // encoded body + + BOOST_CHECK_EQUAL(data.size(), Codec::size(s)); + Struct32 s2; + Codec::decode(data.begin())(s2); + message::DeliveryProperties* dp2 = s2.getIf<message::DeliveryProperties>(); + BOOST_REQUIRE(dp2); + BOOST_CHECK_EQUAL(dp2->priority, message::MEDIUM); + BOOST_CHECK_EQUAL(dp2->routingKey, "foo"); +} + +QPID_AUTO_TEST_CASE(testStruct32Unknown) { + // Verify we can recode an unknown struct unchanged. + Struct32 s; + string data; + Codec::encode(back_inserter(data))(uint32_t(10)); + data.append(10, 'X'); + Codec::decode(data.begin())(s); + string data2; + Codec::encode(back_inserter(data2))(s); + BOOST_CHECK_EQUAL(data.size(), data2.size()); + BOOST_CHECK_EQUAL(data, data2); +} + +struct DummyPacked { + static const uint8_t PACK=1; + boost::optional<char> i, j; + char k; + Bit l,m; + DummyPacked(char a=0, char b=0, char c=0) : i(a), j(b), k(c), l(), m() {} + template <class S> void serialize(S& s) { s(i)(j)(k)(l)(m); } +}; + +Packer<DummyPacked> serializable(DummyPacked& d) { return Packer<DummyPacked>(d); } + +QPID_AUTO_TEST_CASE(testPackBits) { + DummyPacked d('a','b','c'); + BOOST_CHECK_EQUAL(packBits(d), 7u); + d.j = boost::none; + BOOST_CHECK_EQUAL(packBits(d), 5u); + d.m = true; + BOOST_CHECK_EQUAL(packBits(d), 0x15u); +} + + +QPID_AUTO_TEST_CASE(testPacked) { + string data; + + Codec::encode(back_inserter(data))('a')(boost::optional<char>('b'))(boost::optional<char>())('c'); + BOOST_CHECK_EQUAL(data, "abc"); + data.clear(); + + DummyPacked dummy('a','b','c'); + + Codec::encode(back_inserter(data))(dummy); + BOOST_CHECK_EQUAL(data.size(), 4u); + BOOST_CHECK_EQUAL(data, string("\007abc")); + data.clear(); + + dummy.i = boost::none; + Codec::encode(back_inserter(data))(dummy); + BOOST_CHECK_EQUAL(data, string("\6bc")); + data.clear(); + + const char* missing = "\5xy"; + Codec::decode(missing)(dummy); + BOOST_CHECK(dummy.i); + BOOST_CHECK_EQUAL(*dummy.i, 'x'); + BOOST_CHECK(!dummy.j); + BOOST_CHECK_EQUAL(dummy.k, 'y'); +} + +QPID_AUTO_TEST_CASE(testUnitControl) { + string data; + Control::Holder h(in_place<connection::Tune>(1,2,3,4)); + Codec::encode(std::back_inserter(data))(h); + + Unit unit(FrameHeader(FIRST_FRAME|LAST_FRAME, CONTROL)); + Codec::decode(data.begin())(unit); + + BOOST_REQUIRE(unit.get<ControlHolder>()); + + string data2; + Codec::encode(back_inserter(data2))(unit); + + BOOST_CHECK_EQUAL(data, data2); +} + +QPID_AUTO_TEST_CASE(testArray) { + ArrayDomain<char> a; + a.resize(3, 'x'); + string data; + Codec::encode(back_inserter(data))(a); + + ArrayDomain<char> b; + Codec::decode(data.begin())(b); + BOOST_CHECK_EQUAL(b.size(), 3u); + string data3; + Codec::encode(back_inserter(data3))(a); + BOOST_CHECK_EQUAL(data, data3); + + Array x; + Codec::decode(data.begin())(x); + BOOST_CHECK_EQUAL(x.size(), 3u); + BOOST_CHECK_EQUAL(x[0].size(), 1u); + BOOST_CHECK_EQUAL(*x[0].begin(), 'x'); + BOOST_CHECK_EQUAL(*x[2].begin(), 'x'); + + string data2; + Codec::encode(back_inserter(data2))(x); + BOOST_CHECK_EQUAL(data,data2); +} + +QPID_AUTO_TEST_CASE(testStruct) { + string data; + + message::DeliveryProperties dp; + BOOST_CHECK(!dp.discardUnroutable); + dp.immediate = true; + dp.redelivered = false; + dp.priority = message::MEDIUM; + dp.exchange = "foo"; + + Codec::encode(back_inserter(data))(dp); + // Skip 4 bytes size, little-endian decode for pack bits. + uint16_t encodedBits=uint8_t(data[5]); + encodedBits <<= 8; + encodedBits += uint8_t(data[4]); + BOOST_CHECK_EQUAL(encodedBits, packBits(dp)); + + data.clear(); + Struct32 h(dp); + Codec::encode(back_inserter(data))(h); + + Struct32 h2; + Codec::decode(data.begin())(h2); + BOOST_CHECK_EQUAL(h2.getClassCode(), Uint8(message::DeliveryProperties::CLASS_CODE)); + BOOST_CHECK_EQUAL(h2.getCode(), Uint8(message::DeliveryProperties::CODE)); + message::DeliveryProperties* dp2 = + dynamic_cast<message::DeliveryProperties*>(h2.get()); + BOOST_CHECK(dp2); + BOOST_CHECK(!dp2->discardUnroutable); + BOOST_CHECK(dp2->immediate); + BOOST_CHECK(!dp2->redelivered); + BOOST_CHECK_EQUAL(dp2->priority, message::MEDIUM); + BOOST_CHECK_EQUAL(dp2->exchange, "foo"); +} + +struct RecodeUnit { + template <class T> + void operator() (const T& t) { + BOOST_MESSAGE(BOOST_CURRENT_FUNCTION << " called with: " << t); + using qpid::framing::Buffer; + using qpid::framing::AMQFrame; + + session::Header sh; + BOOST_CHECK_EQUAL(Codec::size(sh), 2u); + + // Encode unit. + Unit u(t); + string data; + Codec::encode(back_inserter(data))(u.getHeader())(u); + data.push_back(char(0xCE)); // Preview end-of-frame + + // Decode AMQFrame + Buffer buf(&data[0], data.size()); + AMQFrame f; + f.decode(buf); + BOOST_MESSAGE("AMQFrame decoded: " << f); + // Encode AMQFrame + string data2(f.size(), ' '); + Buffer buf2(&data2[0], data.size()); + f.encode(buf2); + + // Verify encoded by unit == encoded by AMQFrame + BOOST_CHECK_MESSAGE(data == data2, BOOST_CURRENT_FUNCTION); + + // Decode unit + // FIXME aconway 2008-04-15: must set limit to decode a header. + Codec::Decoder<string::iterator> decode(data2.begin(), data2.size()-1); + + FrameHeader h; + decode(h); + BOOST_CHECK_EQUAL(u.getHeader(), h); + Unit u2(h); + decode(u2); + + // Re-encode unit + string data3; + Codec::encode(back_inserter(data3))(u2.getHeader())(u2); + data3.push_back(char(0xCE)); // Preview end-of-frame + + BOOST_CHECK_MESSAGE(data3 == data2, BOOST_CURRENT_FUNCTION); + } +}; + +QPID_AUTO_TEST_CASE(testSerializeAllSegmentTypes) { + RecodeUnit recode; + allSegmentTypes(recode); +} + +QPID_AUTO_TEST_SUITE_END() 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/benchmark b/qpid/cpp/src/tests/benchmark new file mode 100755 index 0000000000..c075837847 --- /dev/null +++ b/qpid/cpp/src/tests/benchmark @@ -0,0 +1,95 @@ +#!/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. +# +# A basic "benchmark" to generate performacne samples of throughput +# and latency against a single cluster member while they are replicating. +# +# Must be run in the qpid src/tests build directory. +# + +usage() { +cat <<EOF +Usage: $0 [options] -- client hosts --- broker hosts +Read the script for options. +EOF +} +# Defaults +TESTDIR=${TESTDIR:-$PWD} # Absolute path to test exes on all hosts. +SCRIPTDIR=${SCRIPTDIR:-`dirname $0`} # Path to local test scripts directory. +SAMPLES=10 # Runs of each test. +COUNT=${COUNT:-10000} # Count for pub/sub tests. +SIZE=${SIZE:-600} # Size of messages +ECHO=${ECHO:-1000} # Count for echo test. +NSUBS=${NSUBS:-4} +NPUBS=${NPUBS:-4} + +collect() { eval $COLLECT=\""\$$COLLECT $*"\"; } +COLLECT=ARGS +while test $# -gt 0; do + case $1 in + --testdir) TESTDIR=$2 ; shift 2 ;; + --samples) SAMPLES=$2 ; shift 2 ;; + --count) COUNT=$2 ; shift 2 ;; + --echos) ECHO=$2 ; shift 2 ;; + --size) SIZE=$2 ; shift 2 ;; + --nsubs) NSUBS=$2 ; shift 2 ;; + --npubs) NPUBS=$2 ; shift 2 ;; + --) COLLECT=CLIENTARG; shift ;; + ---) COLLECT=BROKERARG; shift;; + *) collect $1; shift ;; + esac +done + +CLIENTS=${CLIENTARG:-$CLIENTS} +BROKERS=${BROKERARG:-$BROKERS} +test -z "$CLIENTS" && { echo "Must specify at least one client host."; exit 1; } +test -z "$BROKERS" && { echo "Must specify at least one broker host."; exit 1; } + +export TESTDIR # For perfdist +CLIENTS=($CLIENTS) # Convert to array +BROKERS=($BROKERS) +trap "rm -f $FILES" EXIT + +dosamples() { + FILE=`mktemp` + FILES="$FILES $FILE" + TABS=`echo "$HEADING" | sed s'/[^ ]//g'` + { + echo "\"$*\"$TABS" + echo "$HEADING" + for (( i=0; i<$SAMPLES; ++i)) ; do echo "`$*`" ; done + echo + } | tee $FILE +} + +HEADING="pub sub total Mb" +dosamples $SCRIPTDIR/perfdist --size $SIZE --count $COUNT --nsubs $NSUBS --npubs $NPUBS -s -- ${CLIENTS[*]} --- ${BROKERS[*]} +HEADING="pub" +dosamples ssh -A ${CLIENTS[0]} $TESTDIR/publish --routing-key perftest0 --size $SIZE --count $COUNT -s -b ${BROKERS[0]} +HEADING="sub" +dosamples ssh -A ${CLIENTS[0]} $TESTDIR/consume --queue perftest0 -s --count $COUNT -b ${BROKERS[0]} +HEADING="min max avg" +dosamples ssh -A ${CLIENTS[0]} $TESTDIR/echotest --count $ECHO -s -b ${BROKERS[0]} + +echo +echo "Tab separated spreadsheet (also saved as benchmark.tab):" +echo + +echo "benchmark -- ${CLIENTS[*]} --- ${BROKERS[*]} " | tee benchmark.tab +paste $FILES | tee -a benchmark.tab diff --git a/qpid/cpp/src/tests/brokermgmt.mk b/qpid/cpp/src/tests/brokermgmt.mk new file mode 100644 index 0000000000..cf9a47200c --- /dev/null +++ b/qpid/cpp/src/tests/brokermgmt.mk @@ -0,0 +1,44 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Build a unit test for the broker's internal management agent. + +BROKERMGMT_GEN_SRC= \ + brokermgmt_gen/qmf/org/apache/qpid/broker/mgmt/test/Package.cpp \ + brokermgmt_gen/qmf/org/apache/qpid/broker/mgmt/test/Package.h \ + brokermgmt_gen/qmf/org/apache/qpid/broker/mgmt/test/TestObject.h \ + brokermgmt_gen/qmf/org/apache/qpid/broker/mgmt/test/TestObject.cpp + +$(BROKERMGMT_GEN_SRC): brokermgmt_gen.timestamp + +if GENERATE +BROKERMGMT_DEPS=../mgen.timestamp +endif # GENERATE +brokermgmt_gen.timestamp: BrokerMgmtAgent.xml ${BROKERMGMT_DEPS} + $(QMF_GEN) -b -o brokermgmt_gen/qmf $(srcdir)/BrokerMgmtAgent.xml + touch $@ + +BrokerMgmtAgent.$(OBJEXT): $(BROKERMGMT_GEN_SRC) + +CLEANFILES+=$(BROKERMGMT_GEN_SRC) brokermgmt_gen.timestamp + +unit_test_SOURCES+=BrokerMgmtAgent.cpp ${BROKERMGMT_GEN_SRC} +INCLUDES+= -Ibrokermgmt_gen + +EXTRA_DIST+=BrokerMgmtAgent.xml diff --git a/qpid/cpp/src/tests/brokertest.py b/qpid/cpp/src/tests/brokertest.py new file mode 100644 index 0000000000..a19dd305e5 --- /dev/null +++ b/qpid/cpp/src/tests/brokertest.py @@ -0,0 +1,671 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT 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. cluster +# or federation + +import os, signal, string, tempfile, subprocess, socket, threading, time, imp, re +import qpid, traceback, signal +from qpid import connection, messaging, util +from qpid.compat import format_exc +from qpid.harness import Skipped +from unittest import TestCase +from copy import copy +from threading import Thread, Lock, Condition +from logging import getLogger +import qmf.console + +log = getLogger("qpid.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=.01): + """Call function until it returns True or timeout expires. + Double the delay for each retry. Return True if function + returns true, False if timeout expires.""" + deadline = time.time() + timeout + while not function(): + remaining = deadline - time.time() + if remaining <= 0: return False + delay = min(delay, remaining) + time.sleep(delay) + delay *= 2 + return True + +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 +PIPE = subprocess.PIPE + +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") + try: + subprocess.Popen.__init__(self, self.cmd, bufsize=0, executable=None, + stdin=stdin, stdout=stdout, stderr=stderr, + close_fds=True) + except ValueError: # Windows can't do close_fds + 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 __str__(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 stop(self): # Clean up at end of test. + try: + if self.expect == EXPECT_UNKNOWN: + try: self.kill() # Just make sure its dead + except: pass + elif self.expect == EXPECT_RUNNING: + try: self.kill() + except: self.unexpected("expected running, exit code %d" % self.wait()) + 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") + finally: + self.wait() # Clean up the process. + + 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 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._cleanup() + + def kill(self): + 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._cleanup() + + 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 + + def __str__(self): return "Broker<%s %s>"%(self.name, self.pname) + + def find_log(self): + self.log = "%s.log" % self.name + i = 1 + while (os.path.exists(self.log)): + self.log = "%s-%d.log" % (self.name, i) + i += 1 + + def get_log(self): + return os.path.abspath(self.log) + + def __init__(self, test, args=[], name=None, expect=EXPECT_RUNNING, port=0, log_level=None, wait=None): + """Start a broker daemon. name determines the data-dir and log + file names.""" + + self.test = test + self._port=port + if BrokerTest.store_lib: + args = args + ['--load-module', BrokerTest.store_lib] + if BrokerTest.sql_store_lib: + args = args + ['--load-module', BrokerTest.sql_store_lib] + args = args + ['--catalog', BrokerTest.sql_catalog] + if BrokerTest.sql_clfs_store_lib: + args = args + ['--load-module', BrokerTest.sql_clfs_store_lib] + args = args + ['--catalog', BrokerTest.sql_catalog] + cmd = [BrokerTest.qpidd_exec, "--port", port, "--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.find_log() + cmd += ["--log-to-file", self.log] + cmd += ["--log-to-stderr=no"] + if log_level != None: + cmd += ["--log-enable=%s" % log_level] + self.datadir = self.name + cmd += ["--data-dir", self.datadir] + Popen.__init__(self, cmd, expect, stdout=PIPE) + test.cleanup_stop(self) + self._host = "127.0.0.1" + log.debug("Started broker %s (%s, %s)" % (self.name, self.pname, self.log)) + self._log_ready = False + + def startQmf(self, handler=None): + self.qmf_session = qmf.console.Session(handler) + self.qmf_broker = self.qmf_session.addBroker("%s:%s" % (self.host(), self.port())) + + 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: + raise Exception("Can't get port for broker %s (%s)%s" % + (self.name, self.pname, error_line(self.log,5))) + return self._port + + def unexpected(self,msg): + raise BadProcessStatus("%s: %s (%s)" % (msg, self.name, self.pname)) + + def connect(self, **kwargs): + """New API connection to the broker.""" + return messaging.Connection.establish(self.host_port(), **kwargs) + + def connect_old(self): + """Old API connection to the broker.""" + socket = qpid.util.connect(self.host(),self.port()) + connection = qpid.connection.Connection (sock=socket) + connection.start() + return connection; + + def declare_queue(self, queue): + c = self.connect_old() + s = c.session(str(qpid.datatypes.uuid4())) + s.queue_declare(queue=queue) + c.close() + + 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 log_ready(self): + """Return true if the log file exists and contains a broker ready message""" + if not self._log_ready: + self._log_ready = find_in_file("notice Broker running", self.log) + return self._log_ready + + def ready(self, **kwargs): + """Wait till broker is ready to serve clients""" + # First make sure the broker is listening by checking the log. + if not retry(self.log_ready, timeout=60): + raise Exception( + "Timed out waiting for broker %s%s"%(self.name, error_line(self.log,5))) + # Create a connection and a session. For a cluster broker this will + # return after cluster init has finished. + try: + c = self.connect(**kwargs) + try: c.session() + finally: c.close() + except Exception,e: raise RethrownException( + "Broker %s not responding: (%s)%s"%(self.name,e,error_line(self.log, 5))) + + def store_state(self): + f = open(os.path.join(self.datadir, "cluster", "store.status")) + try: uuids = f.readlines() + finally: f.close() + null_uuid="00000000-0000-0000-0000-000000000000\n" + if len(uuids) < 2: return "unknown" # we looked while the file was being updated. + if uuids[0] == null_uuid: return "empty" + if uuids[1] == null_uuid: return "dirty" + return "clean" + +class Cluster: + """A cluster of brokers in a test.""" + + _cluster_count = 0 + + def __init__(self, test, count=0, args=[], expect=EXPECT_RUNNING, wait=True): + self.test = test + self._brokers=[] + self.name = "cluster%d" % Cluster._cluster_count + Cluster._cluster_count += 1 + # Use unique cluster name + self.args = copy(args) + self.args += [ "--cluster-name", "%s-%s:%d" % (self.name, socket.gethostname(), os.getpid()) ] + self.args += [ "--log-enable=info+", "--log-enable=debug+:cluster"] + assert BrokerTest.cluster_lib, "Cannot locate cluster plug-in" + self.args += [ "--load-module", BrokerTest.cluster_lib ] + self.start_n(count, expect=expect, wait=wait) + + def start(self, name=None, expect=EXPECT_RUNNING, wait=True, args=[], port=0): + """Add a broker to the cluster. Returns the index of the new broker.""" + if not name: name="%s-%d" % (self.name, len(self._brokers)) + self._brokers.append(self.test.broker(self.args+args, name, expect, wait, port=port)) + return self._brokers[-1] + + def start_n(self, count, expect=EXPECT_RUNNING, wait=True, args=[]): + for i in range(count): self.start(expect=expect, wait=wait, args=args) + + # 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__() + +class BrokerTest(TestCase): + """ + Tracks processes started by test and kills at end of test. + Provides a well-known working directory for each test. + """ + + # Environment settings. + qpidd_exec = os.path.abspath(checkenv("QPIDD_EXEC")) + cluster_lib = os.getenv("CLUSTER_LIB") + xml_lib = os.getenv("XML_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() + + def configure(self, config): self.config=config + + def setUp(self): + outdir = self.config.defines.get("OUTDIR") or "brokertest.tmp" + self.dir = os.path.join(self.rootdir, outdir, self.id()) + os.makedirs(self.dir) + os.chdir(self.dir) + self.stopem = [] # things to stop at end of test + + def tearDown(self): + err = [] + for p in self.stopem: + try: p.stop() + except Exception, e: err.append(str(e)) + self.stopem = [] # reset in case more processes start + os.chdir(self.rootdir) + if err: raise Exception("Unexpected process status:\n "+"\n ".join(err)) + + def cleanup_stop(self, stopable): + """Call thing.stop at end of test""" + self.stopem.append(stopable) + + 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.cleanup_stop(p) + return p + + def broker(self, args=[], name=None, expect=EXPECT_RUNNING, wait=True, port=0, log_level=None): + """Create and return a broker ready for use""" + b = Broker(self, args=args, name=name, expect=expect, port=port, log_level=log_level) + 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 cluster(self, count=0, args=[], expect=EXPECT_RUNNING, wait=True): + """Create and return a cluster ready for use""" + cluster = Cluster(self, count, args, expect=expect, wait=wait) + return cluster + + def browse(self, session, queue, timeout=0): + """Assert that the contents of messages on queue (as retrieved + using session and timeout) exactly match the strings in + expect_contents""" + r = session.receiver("%s;{mode:browse}"%(queue)) + try: + contents = [] + try: + while True: contents.append(r.fetch(timeout=timeout).content) + except messaging.Empty: pass + finally: pass #FIXME aconway 2011-04-14: r.close() + return contents + + def assert_browse(self, session, queue, expect_contents, timeout=0): + """Assert that the contents of messages on queue (as retrieved + using session and timeout) exactly match the strings in + expect_contents""" + actual_contents = self.browse(session, queue, timeout) + self.assertEqual(expect_contents, actual_contents) + +def join(thread, timeout=10): + 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 + +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"): + """ + 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) + self.sender = broker.test.popen( + ["qpid-send", + "--broker", "localhost:%s"%broker.port(), + "--address", "%s;{create:always}"%queue, + "--failover-updates", + "--content-stdin" + ], + expect=EXPECT_RUNNING, + stdin=PIPE) + self.condition = Condition() + self.max = max_depth + self.received = 0 + self.stopped = False + self.error = None + + 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: + 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: self.error = RethrownException(self.sender.pname) + + 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"): + """ + sender: enable flow control. Call sender.received(n) for each message received. + """ + Thread.__init__(self) + self.test = broker.test + self.receiver = self.test.popen( + ["qpid-receive", + "--broker", "localhost:%s"%broker.port(), + "--address", "%s;{create:always}"%queue, + "--failover-updates", + "--forever" + ], + expect=EXPECT_RUNNING, + stdout=PIPE) + self.lock = Lock() + self.error = None + self.sender = sender + + def read_message(self): + return int(self.receiver.stdout.readline()) + + def run(self): + try: + self.received = 0 + m = self.read_message() + while m != -1: + assert(m <= self.received) # Check for missing messages + if (m == self.received): # Ignore duplicates + self.received += 1 + if self.sender: + self.sender.notify_received(self.received) + m = self.read_message() + except Exception: + self.error = RethrownException(self.receiver.pname) + + def stop(self): + """Returns when termination message is received""" + join(self) + if self.error: raise self.error + +class ErrorGenerator(StoppableThread): + """ + Thread that continuously generates errors by trying to consume from + a non-existent queue. For cluster regression tests, error handling + caused issues in the past. + """ + + def __init__(self, broker): + StoppableThread.__init__(self) + self.broker=broker + broker.test.cleanup_stop(self) + self.start() + + def run(self): + c = self.broker.connect_old() + try: + while not self.stopped: + try: + c.session(str(qpid.datatypes.uuid4())).message_subscribe( + queue="non-existent-queue") + assert(False) + except qpid.session.SessionException: pass + time.sleep(0.01) + except: pass # Normal if broker is killed. + +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..6c75927461 --- /dev/null +++ b/qpid/cpp/src/tests/cli_tests.py @@ -0,0 +1,475 @@ +#!/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 brokertest import import_script, checkenv +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) + queues = self.qmf.getObjects(_class="queue") + for queue in queues: + if queue.name == qname: + return queue + assert False + + def test_queue_params(self): + self.startQmf() + 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 flow-to-disk") + queue4 = self.makeQueue("test_queue_params4", "--limit-policy ring") + queue5 = self.makeQueue("test_queue_params5", "--limit-policy ring-strict") + + LIMIT = "qpid.policy_type" + assert LIMIT not in queue1.arguments + self.assertEqual(queue2.arguments[LIMIT], "reject") + self.assertEqual(queue3.arguments[LIMIT], "flow_to_disk") + self.assertEqual(queue4.arguments[LIMIT], "ring") + self.assertEqual(queue5.arguments[LIMIT], "ring_strict") + + queue6 = self.makeQueue("test_queue_params6", "--order fifo") + queue7 = self.makeQueue("test_queue_params7", "--order lvq") + queue8 = self.makeQueue("test_queue_params8", "--order lvq-no-browse") + + LVQ = "qpid.last_value_queue" + LVQNB = "qpid.last_value_queue_no_browse" + + assert LVQ not in queue6.arguments + assert LVQ in queue7.arguments + assert LVQ not in queue8.arguments + + assert LVQNB not in queue6.arguments + assert LVQNB not in queue7.arguments + assert LVQNB in queue8.arguments + + + def test_queue_params_api(self): + self.startQmf() + queue1 = self.makeQueue("test_queue_params1", "--limit-policy none", True) + queue2 = self.makeQueue("test_queue_params2", "--limit-policy reject", True) + queue3 = self.makeQueue("test_queue_params3", "--limit-policy flow-to-disk", True) + queue4 = self.makeQueue("test_queue_params4", "--limit-policy ring", True) + queue5 = self.makeQueue("test_queue_params5", "--limit-policy ring-strict", True) + + LIMIT = "qpid.policy_type" + assert LIMIT not in queue1.arguments + self.assertEqual(queue2.arguments[LIMIT], "reject") + self.assertEqual(queue3.arguments[LIMIT], "flow_to_disk") + self.assertEqual(queue4.arguments[LIMIT], "ring") + self.assertEqual(queue5.arguments[LIMIT], "ring_strict") + + queue6 = self.makeQueue("test_queue_params6", "--order fifo", True) + queue7 = self.makeQueue("test_queue_params7", "--order lvq", True) + queue8 = self.makeQueue("test_queue_params8", "--order lvq-no-browse", True) + + LVQ = "qpid.last_value_queue" + LVQNB = "qpid.last_value_queue_no_browse" + + assert LVQ not in queue6.arguments + assert LVQ in queue7.arguments + assert LVQ not in queue8.arguments + + assert LVQNB not in queue6.arguments + assert LVQNB not in queue7.arguments + assert LVQNB in queue8.arguments + + + def test_qpid_config(self): + self.startQmf(); + qmf = self.qmf + qname = "test_qpid_config" + + ret = os.system(self.qpid_config_command(" add queue " + qname)) + self.assertEqual(ret, 0) + queues = qmf.getObjects(_class="queue") + 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 = qmf.getObjects(_class="queue") + found = False + for queue in queues: + if queue.name == qname: + found = True + self.assertEqual(found, False) + + def test_qpid_config_api(self): + self.startQmf(); + qmf = self.qmf + qname = "test_qpid_config_api" + + ret = self.qpid_config_api(" add queue " + qname) + self.assertEqual(ret, 0) + queues = qmf.getObjects(_class="queue") + 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 = qmf.getObjects(_class="queue") + 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.startQmf(); + qmf = self.qmf + qname = "test_qpid_config_sasl_plain_expect_succeed" + cmd = " --sasl-mechanism PLAIN -a 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.startQmf(); + qmf = self.qmf + qname = "test_qpid_config_sasl_plain_expect_succeed" + cmd = " --sasl-mechanism PLAIN -a 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.qmf.getObjects(_class = "exchange") + 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.qmf.getObjects(_class="queue") + 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.startQmf(); + qmf = self.qmf + 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.startQmf(); + qmf = self.qmf + 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.startQmf(); + qmf = self.qmf + qname = "test_qpid_config" + + ret = os.system(self.qpid_config_command(" add queue --durable " + qname)) + self.assertEqual(ret, 0) + queues = qmf.getObjects(_class="queue") + 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 = qmf.getObjects(_class="queue") + found = False + for queue in queues: + if queue.name == qname: + found = True + self.assertEqual(found, False) + + def test_qpid_config_altex(self): + self.startQmf(); + qmf = self.qmf + 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 = qmf.getObjects(_class="exchange") + 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_.name, altName) + self.assertEqual(found, True) + + ret = os.system(self.qpid_config_command(" add queue %s --alternate-exchange=%s" % (qName, altName))) + self.assertEqual(ret, 0) + + queues = qmf.getObjects(_class="queue") + 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_.name, 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.startQmf(); + qmf = self.qmf + + 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.startQmf(); + qmf = self.qmf + + 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 = qmf.getObjects(_class="link") + 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.startQmf(); + qmf = self.qmf + + 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 = qmf.getObjects(_class="link") + 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.startQmf(); + qmf = self.qmf + + 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 = qmf.getObjects(_class="link") + 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.startQmf(); + qmf = self.qmf + + 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 -a localhost:%d" % self.broker.port + " " + arg + + def qpid_config_api(self, arg = ""): + script = import_script(checkenv("QPID_CONFIG_EXEC")) + broker = ["-a", "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/cluster.cmake b/qpid/cpp/src/tests/cluster.cmake new file mode 100644 index 0000000000..3471173e97 --- /dev/null +++ b/qpid/cpp/src/tests/cluster.cmake @@ -0,0 +1,90 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# +# Cluster tests cmake fragment, to be included in CMakeLists.txt +# + +add_executable (failover_soak failover_soak.cpp ForkedBroker.cpp ${platform_test_additions}) +target_link_libraries (failover_soak qpidclient) +remember_location(failover_soak) + +add_executable (cluster_authentication_soak cluster_authentication_soak.cpp ForkedBroker.cpp ${platform_test_additions}) +target_link_libraries (cluster_authentication_soak qpidclient) +remember_location(cluster_authentication_soak) + +set (cluster_test_SOURCES + cluster_test + unit_test + ClusterFixture + ForkedBroker + PartialFailure + ClusterFailover + InitialStatusMap + StoreStatus + ) +add_executable (cluster_test ${cluster_test_SOURCES} ${platform_test_additions}) +target_link_libraries (cluster_test ${qpid_test_boost_libs} qpidclient qpidbroker cluster_shared) +remember_location(cluster_test) + +add_test (cluster_test ${CMAKE_CURRENT_SOURCE_DIR}/run_cluster_test${test_script_suffix}) +add_test (cluster_tests ${CMAKE_CURRENT_SOURCE_DIR}/run_cluster_tests${test_script_suffix}) +add_test (cluster_read_credit ${CMAKE_CURRENT_SOURCE_DIR}/cluster_read_credit${test_script_suffix}) +add_test (cluster_test_watchdog ${CMAKE_CURRENT_SOURCE_DIR}/test_watchdog${test_script_suffix}) +add_test (federated_cluster_test ${CMAKE_CURRENT_SOURCE_DIR}/federated_cluster_test${test_script_suffix}) +add_test (clustered_replication_test ${CMAKE_CURRENT_SOURCE_DIR}/clustered_replication_test${test_script_suffix}) + +# FIXME aconway 2009-12-01: translate to cmake +# # Clean up after cluster_test and start_cluster +# CLEANFILES += cluster_test.acl cluster.ports + +# EXTRA_DIST += \ +# ais_check \ +# run_cluster_test \ +# cluster_read_credit \ +# test_watchdog \ +# start_cluster \ +# stop_cluster \ +# restart_cluster \ +# cluster_python_tests \ +# cluster_python_tests_failing.txt \ +# federated_cluster_test \ +# clustered_replication_test \ +# run_cluster_tests \ +# run_long_cluster_tests \ +# testlib.py \ +# cluster_tests.py \ +# long_cluster_tests.py \ +# cluster_tests.fail + +# LONG_TESTS += \ +# run_long_cluster_tests \ +# start_cluster \ +# cluster_python_tests \ +# stop_cluster + +# qpidtest_PROGRAMS += cluster_test + +# cluster_test_SOURCES = \ + +# cluster_test_LDADD=$(lib_client) $(lib_broker) ../cluster.la -lboost_unit_test_framework + +# qpidtest_SCRIPTS += run_cluster_tests cluster_tests.py run_long_cluster_tests long_cluster_tests.py testlib.py cluster_tests.fail + +# endif diff --git a/qpid/cpp/src/tests/cluster.mk b/qpid/cpp/src/tests/cluster.mk new file mode 100644 index 0000000000..7d17dd7bde --- /dev/null +++ b/qpid/cpp/src/tests/cluster.mk @@ -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 cluster scripts and extra files in distribution even if +# we're not configured for cluster. + +# Useful scripts for doing cluster testing. +CLUSTER_TEST_SCRIPTS_LIST= \ + allhosts rsynchosts \ + qpid-build-rinstall qpid-src-rinstall \ + qpid-test-cluster \ + qpid-cluster-benchmark + +EXTRA_DIST += \ + $(CLUSTER_TEST_SCRIPTS_LIST) \ + ais_check \ + run_cluster_test \ + cluster_read_credit \ + test_watchdog \ + start_cluster \ + stop_cluster \ + restart_cluster \ + cluster_python_tests \ + cluster_python_tests_failing.txt \ + federated_cluster_test \ + clustered_replication_test \ + run_cluster_tests \ + run_long_cluster_tests \ + testlib.py \ + brokertest.py \ + cluster_tests.py \ + cluster_test_logs.py \ + long_cluster_tests.py \ + cluster_tests.fail + + +if HAVE_LIBCPG + +# +# Cluster tests makefile fragment, to be included in Makefile.am +# + +# NOTE: Programs using the openais library must be run with gid=ais +# You should do "newgrp ais" before running the tests to run these. +# + + +# ais_check checks pre-requisites for cluster tests and runs them if ok. +TESTS += \ + run_cluster_test \ + cluster_read_credit \ + test_watchdog \ + run_cluster_tests \ + federated_cluster_test \ + clustered_replication_test + +# Clean up after cluster_test and start_cluster +CLEANFILES += cluster_test.acl cluster.ports + +LONG_TESTS += \ + run_long_cluster_tests \ + start_cluster \ + cluster_python_tests \ + stop_cluster + +qpidtest_PROGRAMS += cluster_test + +cluster_test_SOURCES = \ + cluster_test.cpp \ + unit_test.cpp \ + ClusterFixture.cpp \ + ClusterFixture.h \ + ForkedBroker.h \ + ForkedBroker.cpp \ + PartialFailure.cpp \ + ClusterFailover.cpp + +cluster_test_LDADD=$(lib_client) $(lib_broker) ../cluster.la -lboost_unit_test_framework + +qpidtest_SCRIPTS += run_cluster_tests brokertest.py cluster_tests.py cluster_test_logs.py run_long_cluster_tests long_cluster_tests.py testlib.py cluster_tests.fail +qpidtest_SCRIPTS += $(CLUSTER_TEST_SCRIPTS_LIST) + +endif diff --git a/qpid/cpp/src/tests/cluster_authentication_soak.cpp b/qpid/cpp/src/tests/cluster_authentication_soak.cpp new file mode 100644 index 0000000000..b8e8a22693 --- /dev/null +++ b/qpid/cpp/src/tests/cluster_authentication_soak.cpp @@ -0,0 +1,310 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <signal.h> +#include <fcntl.h> + +#include <sys/wait.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> + +#include <string> +#include <iostream> +#include <sstream> +#include <vector> + +#include <boost/assign.hpp> + +#include "qpid/framing/Uuid.h" + +#include <ForkedBroker.h> +#include <qpid/client/Connection.h> + +#include <sasl/sasl.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + + + + +using namespace std; +using boost::assign::list_of; +using namespace qpid::framing; +using namespace qpid::client; + + +namespace qpid { +namespace tests { + +vector<pid_t> brokerPids; + +typedef vector<ForkedBroker *> brokerVector; + + + + + +int runSilent = 1; +int newbiePort = 0; + + +void +makeClusterName ( string & s ) { + stringstream ss; + ss << "authenticationSoakCluster_" << Uuid(true).str(); + s = ss.str(); +} + + + +void +startBroker ( brokerVector & brokers , int brokerNumber, string const & clusterName ) { + stringstream prefix, clusterArg; + prefix << "soak-" << brokerNumber; + clusterArg << "--cluster-name=" << clusterName; + + std::vector<std::string> argv; + + argv.push_back ("../qpidd"); + argv.push_back ("--no-module-dir"); + argv.push_back ("--load-module=../.libs/cluster.so"); + argv.push_back (clusterArg.str()); + argv.push_back ("--cluster-username=zig"); + argv.push_back ("--cluster-password=zig"); + argv.push_back ("--cluster-mechanism=ANONYMOUS"); + argv.push_back ("--sasl-config=./sasl_config"); + argv.push_back ("--auth=yes"); + argv.push_back ("--mgmt-enable=yes"); + argv.push_back ("--log-prefix"); + argv.push_back (prefix.str()); + argv.push_back ("--log-to-file"); + argv.push_back (prefix.str()+".log"); + argv.push_back ("TMP_DATA_DIR"); + + ForkedBroker * newbie = new ForkedBroker (argv); + newbiePort = newbie->getPort(); + brokers.push_back ( newbie ); +} + + + + +bool +runPerftest ( bool hangTest ) { + stringstream portSs; + portSs << newbiePort; + string portStr = portSs.str(); + char const * path = "./qpid-perftest"; + + vector<char const *> argv; + argv.push_back ( "./qpid-perftest" ); + argv.push_back ( "-p" ); + argv.push_back ( portStr.c_str() ); + argv.push_back ( "--username" ); + argv.push_back ( "zig" ); + argv.push_back ( "--password" ); + argv.push_back ( "zig" ); + argv.push_back ( "--mechanism" ); + argv.push_back ( "DIGEST-MD5" ); + argv.push_back ( "--count" ); + argv.push_back ( "20000" ); + argv.push_back ( 0 ); + + pid_t pid = fork(); + + if ( ! pid ) { + int i=open("/dev/null",O_RDWR); + dup2 ( i, fileno(stdout) ); + dup2 ( i, fileno(stderr) ); + + execv ( path, const_cast<char * const *>(&argv[0]) ); + // The exec failed: we are still in parent process. + perror ( "error running qpid-perftest: " ); + return false; + } + else { + if ( hangTest ) { + if ( ! runSilent ) + cerr << "Pausing perftest " << pid << endl; + kill ( pid, 19 ); + } + + struct timeval startTime, + currentTime, + duration; + + gettimeofday ( & startTime, 0 ); + + while ( 1 ) { + sleep ( 2 ); + int status; + int returned_pid = waitpid ( pid, &status, WNOHANG ); + if ( returned_pid == pid ) { + int exit_status = WEXITSTATUS(status); + if ( exit_status ) { + cerr << "qpid-perftest failed. exit_status was: " << exit_status << endl; + return false; + } + else { + return true; // qpid-perftest succeeded. + } + } + else { // qpid-perftest has not yet completed. + gettimeofday ( & currentTime, 0 ); + timersub ( & currentTime, & startTime, & duration ); + if ( duration.tv_sec > 60 ) { + kill ( pid, 9 ); + cerr << "qpid-perftest pid " << pid << " hanging: killed.\n"; + return false; + } + } + } + + } +} + + + +bool +allBrokersAreAlive ( brokerVector & brokers ) { + for ( unsigned int i = 0; i < brokers.size(); ++ i ) + if ( ! brokers[i]->isRunning() ) + return false; + + return true; +} + + + + + +void +killAllBrokers ( brokerVector & brokers ) { + for ( unsigned int i = 0; i < brokers.size(); ++ i ) { + brokers[i]->kill ( 9 ); + } +} + + + + +void +killOneBroker ( brokerVector & brokers ) { + int doomedBroker = getpid() % brokers.size(); + cout << "Killing broker " << brokers[doomedBroker]->getPID() << endl; + brokers[doomedBroker]->kill ( 9 ); + sleep ( 2 ); +} + + + + +}} // namespace qpid::tests + +using namespace qpid::tests; + + + +/* + * Please note that this test has self-test capability. + * It is intended to detect + * 1. perftest hangs. + * 2. broker deaths + * Both of these condtions can be forced when running manually + * to ensure that the test really does detect them. + * See command-line arguments 3 and 4. + */ +int +main ( int argc, char ** argv ) +{ + // I need the SASL_PATH_TYPE_CONFIG feature, which did not appear until SASL 2.1.22 +#if (SASL_VERSION_FULL < ((2<<16)|(1<<8)|22)) + cout << "Skipping SASL test, SASL version too low." << endl; + return 0; +#endif + + int n_iterations = argc > 1 ? atoi(argv[1]) : 1; + runSilent = argc > 2 ? atoi(argv[2]) : 1; // default to silent + int killBroker = argc > 3 ? atoi(argv[3]) : 0; // Force the kill of one broker. + int hangTest = argc > 4 ? atoi(argv[4]) : 0; // Force the first perftest to hang. + int n_brokers = 3; + brokerVector brokers; + + srand ( getpid() ); + string clusterName; + makeClusterName ( clusterName ); + for ( int i = 0; i < n_brokers; ++ i ) { + startBroker ( brokers, i, clusterName ); + } + + sleep ( 3 ); + + /* Run all qpid-perftest iterations, and only then check for brokers + * still being up. If you just want a quick check for the failure + * mode in which a single iteration would kill all brokers except + * the client-connected one, just run it with the iterations arg + * set to 1. + */ + for ( int iteration = 0; iteration < n_iterations; ++ iteration ) { + if ( ! runPerftest ( hangTest ) ) { + if ( ! runSilent ) + cerr << "qpid-perftest " << iteration << " failed.\n"; + return 1; + } + if ( ! ( iteration % 10 ) ) { + if ( ! runSilent ) + cerr << "qpid-perftest " << iteration << " complete. -------------- \n"; + } + } + if ( ! runSilent ) + cerr << "\nqpid-perftest " << n_iterations << " iterations complete. -------------- \n\n"; + + /* If the command-line tells us to kill a broker, do + * it now. Use this option to prove that this test + * really can detect broker-deaths. + */ + if ( killBroker ) { + killOneBroker ( brokers ); + } + + if ( ! allBrokersAreAlive ( brokers ) ) { + if ( ! runSilent ) + cerr << "not all brokers are alive.\n"; + killAllBrokers ( brokers ); + return 2; + } + + killAllBrokers ( brokers ); + if ( ! runSilent ) + cout << "success.\n"; + + return 0; +} + + + diff --git a/qpid/cpp/src/tests/cluster_python_tests b/qpid/cpp/src/tests/cluster_python_tests new file mode 100755 index 0000000000..9d9137ed57 --- /dev/null +++ b/qpid/cpp/src/tests/cluster_python_tests @@ -0,0 +1,27 @@ +#!/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. +# + +# Skip if cluster services not running. +. `dirname $0`/ais_check + +FAILING=`dirname $0`/cluster_python_tests_failing.txt +source `dirname $0`/python_tests + diff --git a/qpid/cpp/src/tests/cluster_python_tests_failing.txt b/qpid/cpp/src/tests/cluster_python_tests_failing.txt new file mode 100644 index 0000000000..7ba8089946 --- /dev/null +++ b/qpid/cpp/src/tests/cluster_python_tests_failing.txt @@ -0,0 +1,32 @@ +qpid_tests.broker_0_10.management.ManagementTest.test_purge_queue +qpid_tests.broker_0_10.management.ManagementTest.test_connection_close +qpid_tests.broker_0_10.dtx.DtxTests.test_bad_resume +qpid_tests.broker_0_10.dtx.DtxTests.test_commit_unknown +qpid_tests.broker_0_10.dtx.DtxTests.test_end +qpid_tests.broker_0_10.dtx.DtxTests.test_end_suspend_and_fail +qpid_tests.broker_0_10.dtx.DtxTests.test_end_unknown_xid +qpid_tests.broker_0_10.dtx.DtxTests.test_forget_xid_on_completion +qpid_tests.broker_0_10.dtx.DtxTests.test_get_timeout +qpid_tests.broker_0_10.dtx.DtxTests.test_get_timeout_unknown +qpid_tests.broker_0_10.dtx.DtxTests.test_implicit_end +qpid_tests.broker_0_10.dtx.DtxTests.test_invalid_commit_not_ended +qpid_tests.broker_0_10.dtx.DtxTests.test_invalid_commit_one_phase_false +qpid_tests.broker_0_10.dtx.DtxTests.test_invalid_commit_one_phase_true +qpid_tests.broker_0_10.dtx.DtxTests.test_invalid_prepare_not_ended +qpid_tests.broker_0_10.dtx.DtxTests.test_invalid_rollback_not_ended +qpid_tests.broker_0_10.dtx.DtxTests.test_prepare_unknown +qpid_tests.broker_0_10.dtx.DtxTests.test_recover +qpid_tests.broker_0_10.dtx.DtxTests.test_rollback_unknown +qpid_tests.broker_0_10.dtx.DtxTests.test_select_required +qpid_tests.broker_0_10.dtx.DtxTests.test_set_timeout +qpid_tests.broker_0_10.dtx.DtxTests.test_simple_commit +qpid_tests.broker_0_10.dtx.DtxTests.test_simple_prepare_commit +qpid_tests.broker_0_10.dtx.DtxTests.test_simple_prepare_rollback +qpid_tests.broker_0_10.dtx.DtxTests.test_simple_rollback +qpid_tests.broker_0_10.dtx.DtxTests.test_start_already_known +qpid_tests.broker_0_10.dtx.DtxTests.test_start_join +qpid_tests.broker_0_10.dtx.DtxTests.test_start_join_and_resume +qpid_tests.broker_0_10.dtx.DtxTests.test_suspend_resume +qpid_tests.broker_0_10.dtx.DtxTests.test_suspend_start_end_resume +qpid_tests.broker_0_10.message.MessageTests.test_ttl +qpid_tests.broker_0_10.management.ManagementTest.test_broker_connectivity_oldAPI diff --git a/qpid/cpp/src/tests/cluster_read_credit b/qpid/cpp/src/tests/cluster_read_credit new file mode 100755 index 0000000000..370d4098c5 --- /dev/null +++ b/qpid/cpp/src/tests/cluster_read_credit @@ -0,0 +1,27 @@ +#!/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. +# + +# Regression test for http://issues.apache.org/jira/browse/QPID-2086 + +srcdir=`dirname $0` +. $srcdir/ais_check +$srcdir/start_cluster 1 --cluster-read-max=2 || exit 1 +trap $srcdir/stop_cluster EXIT +seq 1 10000 | ./sender --port `cat cluster.ports` --routing-key no-such-queue diff --git a/qpid/cpp/src/tests/cluster_test.cpp b/qpid/cpp/src/tests/cluster_test.cpp new file mode 100644 index 0000000000..f2ccd0ba84 --- /dev/null +++ b/qpid/cpp/src/tests/cluster_test.cpp @@ -0,0 +1,1231 @@ +/* + * + * 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 "unit_test.h" +#include "ForkedBroker.h" +#include "BrokerFixture.h" +#include "ClusterFixture.h" + +#include "qpid/client/Connection.h" +#include "qpid/client/ConnectionSettings.h" +#include "qpid/client/ConnectionAccess.h" +#include "qpid/client/Session.h" +#include "qpid/client/FailoverListener.h" +#include "qpid/client/FailoverManager.h" +#include "qpid/client/QueueOptions.h" +#include "qpid/cluster/Cluster.h" +#include "qpid/cluster/Cpg.h" +#include "qpid/cluster/UpdateClient.h" +#include "qpid/framing/AMQBody.h" +#include "qpid/framing/Uuid.h" +#include "qpid/framing/reply_exceptions.h" +#include "qpid/framing/enum.h" +#include "qpid/framing/MessageTransferBody.h" +#include "qpid/log/Logger.h" +#include "qpid/sys/Monitor.h" +#include "qpid/sys/Thread.h" + +#include <boost/bind.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/assign.hpp> + +#include <string> +#include <iostream> +#include <fstream> +#include <iterator> +#include <vector> +#include <set> +#include <algorithm> +#include <iterator> + + +using namespace std; +using namespace qpid; +using namespace qpid::cluster; +using namespace qpid::framing; +using namespace qpid::client; +using namespace boost::assign; +using broker::Broker; +using boost::shared_ptr; + +namespace qpid { +namespace tests { + +QPID_AUTO_TEST_SUITE(cluster_test) + +bool durableFlag = std::getenv("STORE_LIB") != 0; + +void prepareArgs(ClusterFixture::Args& args, const bool durableFlag = false) { + ostringstream clusterLib; + clusterLib << getLibPath("CLUSTER_LIB"); + args += "--auth", "no", "--no-module-dir", "--load-module", clusterLib.str(); + if (durableFlag) + args += "--load-module", getLibPath("STORE_LIB"), "TMP_DATA_DIR"; + else + args += "--no-data-dir"; +} + +ClusterFixture::Args prepareArgs(const bool durableFlag = false) { + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + return args; +} + +// Timeout for tests that wait for messages +const sys::Duration TIMEOUT=2*sys::TIME_SEC; + + +ostream& operator<<(ostream& o, const cpg_name* n) { + return o << Cpg::str(*n); +} + +ostream& operator<<(ostream& o, const cpg_address& a) { + return o << "(" << a.nodeid <<","<<a.pid<<","<<a.reason<<")"; +} + +template <class T> +ostream& operator<<(ostream& o, const pair<T*, int>& array) { + o << "{ "; + ostream_iterator<cpg_address> i(o, " "); + copy(array.first, array.first+array.second, i); + o << "}"; + return o; +} + +template <class C> set<int> makeSet(const C& c) { + set<int> s; + copy(c.begin(), c.end(), inserter(s, s.begin())); + return s; +} + +class Sender { + public: + Sender(boost::shared_ptr<ConnectionImpl> ci, uint16_t ch) : connection(ci), channel(ch) {} + void send(const AMQBody& body, bool firstSeg, bool lastSeg, bool firstFrame, bool lastFrame) { + AMQFrame f(body); + f.setChannel(channel); + f.setFirstSegment(firstSeg); + f.setLastSegment(lastSeg); + f.setFirstFrame(firstFrame); + f.setLastFrame(lastFrame); + connection->expand(f.encodedSize(), false); + connection->handle(f); + } + + private: + boost::shared_ptr<ConnectionImpl> connection; + uint16_t channel; +}; + +int64_t getMsgSequence(const Message& m) { + return m.getMessageProperties().getApplicationHeaders().getAsInt64("qpid.msg_sequence"); +} + +Message ttlMessage(const string& data, const string& key, uint64_t ttl, bool durable = false) { + Message m(data, key); + m.getDeliveryProperties().setTtl(ttl); + if (durable) m.getDeliveryProperties().setDeliveryMode(framing::PERSISTENT); + return m; +} + +Message makeMessage(const string& data, const string& key, bool durable = false) { + Message m(data, key); + if (durable) m.getDeliveryProperties().setDeliveryMode(framing::PERSISTENT); + return m; +} + +vector<string> browse(Client& c, const string& q, int n) { + SubscriptionSettings browseSettings( + FlowControl::messageCredit(n), + ACCEPT_MODE_NONE, + ACQUIRE_MODE_NOT_ACQUIRED, + 0 // No auto-ack. + ); + LocalQueue lq; + c.subs.subscribe(lq, q, browseSettings); + c.session.messageFlush(q); + vector<string> result; + for (int i = 0; i < n; ++i) { + Message m; + if (!lq.get(m, TIMEOUT)) + break; + result.push_back(m.getData()); + } + c.subs.getSubscription(q).cancel(); + return result; +} + +ConnectionSettings aclSettings(int port, const std::string& id) { + ConnectionSettings settings; + settings.port = port; + settings.mechanism = "PLAIN"; + settings.username = id; + settings.password = id; + return settings; +} + +// An illegal frame body +struct PoisonPill : public AMQBody { + virtual uint8_t type() const { return 0xFF; } + virtual void encode(Buffer& ) const {} + virtual void decode(Buffer& , uint32_t=0) {} + virtual uint32_t encodedSize() const { return 0; } + + virtual void print(std::ostream&) const {}; + virtual void accept(AMQBodyConstVisitor&) const {}; + + virtual AMQMethodBody* getMethod() { return 0; } + virtual const AMQMethodBody* getMethod() const { return 0; } + + /** Match if same type and same class/method ID for methods */ + static bool match(const AMQBody& , const AMQBody& ) { return false; } + virtual boost::intrusive_ptr<AMQBody> clone() const { return new PoisonPill; } +}; + +QPID_AUTO_TEST_CASE(testBadClientData) { + // Ensure that bad data on a client connection closes the + // connection but does not stop the broker. + ClusterFixture::Args args; + prepareArgs(args, false); + args += "--log-enable=critical"; // Supress expected errors + ClusterFixture cluster(2, args, -1); + Client c0(cluster[0]); + Client c1(cluster[1]); + boost::shared_ptr<client::ConnectionImpl> ci = + client::ConnectionAccess::getImpl(c0.connection); + AMQFrame poison(boost::intrusive_ptr<AMQBody>(new PoisonPill)); + ci->expand(poison.encodedSize(), false); + ci->handle(poison); + { + ScopedSuppressLogging sl; + BOOST_CHECK_THROW(c0.session.queueQuery("q0"), Exception); + } + Client c00(cluster[0]); + BOOST_CHECK_EQUAL(c00.session.queueQuery("q00").getQueue(), ""); + BOOST_CHECK_EQUAL(c1.session.queueQuery("q1").getQueue(), ""); +} + +QPID_AUTO_TEST_CASE(testAcl) { + ofstream policyFile("cluster_test.acl"); + policyFile << "acl allow foo@QPID create queue name=foo" << endl + << "acl allow foo@QPID create queue name=foo2" << endl + << "acl deny foo@QPID create queue name=bar" << endl + << "acl allow all all" << endl; + policyFile.close(); + char cwd[1024]; + BOOST_CHECK(::getcwd(cwd, sizeof(cwd))); + ostringstream aclLib; + aclLib << getLibPath("ACL_LIB"); + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + args += "--log-enable=critical"; // Supress expected errors + args += "--acl-file", string(cwd) + "/cluster_test.acl", + "--cluster-mechanism", "PLAIN", + "--cluster-username", "cluster", + "--cluster-password", "cluster", + "--load-module", aclLib.str(); + ClusterFixture cluster(2, args, -1); + + Client c0(aclSettings(cluster[0], "c0"), "c0"); + Client c1(aclSettings(cluster[1], "c1"), "c1"); + Client foo(aclSettings(cluster[1], "foo"), "foo"); + + foo.session.queueDeclare("foo", arg::durable=durableFlag); + BOOST_CHECK_EQUAL(c0.session.queueQuery("foo").getQueue(), "foo"); + + { + ScopedSuppressLogging sl; + BOOST_CHECK_THROW(foo.session.queueDeclare("bar", arg::durable=durableFlag), framing::UnauthorizedAccessException); + } + BOOST_CHECK(c0.session.queueQuery("bar").getQueue().empty()); + BOOST_CHECK(c1.session.queueQuery("bar").getQueue().empty()); + + cluster.add(); + Client c2(aclSettings(cluster[2], "c2"), "c2"); + { + ScopedSuppressLogging sl; + BOOST_CHECK_THROW(foo.session.queueDeclare("bar", arg::durable=durableFlag), framing::UnauthorizedAccessException); + } + BOOST_CHECK(c2.session.queueQuery("bar").getQueue().empty()); +} + +QPID_AUTO_TEST_CASE(testMessageTimeToLive) { + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(2, args, -1); + Client c0(cluster[0], "c0"); + Client c1(cluster[1], "c1"); + c0.session.queueDeclare("p", arg::durable=durableFlag); + c0.session.queueDeclare("q", arg::durable=durableFlag); + c0.session.messageTransfer(arg::content=ttlMessage("a", "q", 200, durableFlag)); + c0.session.messageTransfer(arg::content=makeMessage("b", "q", durableFlag)); + c0.session.messageTransfer(arg::content=ttlMessage("x", "p", 100000, durableFlag)); + c0.session.messageTransfer(arg::content=makeMessage("y", "p", durableFlag)); + cluster.add(); + Client c2(cluster[1], "c2"); + + BOOST_CHECK_EQUAL(browse(c0, "p", 1), list_of<string>("x")); + BOOST_CHECK_EQUAL(browse(c1, "p", 1), list_of<string>("x")); + BOOST_CHECK_EQUAL(browse(c2, "p", 1), list_of<string>("x")); + + sys::usleep(200*1000); + BOOST_CHECK_EQUAL(browse(c0, "q", 1), list_of<string>("b")); + BOOST_CHECK_EQUAL(browse(c1, "q", 1), list_of<string>("b")); + BOOST_CHECK_EQUAL(browse(c2, "q", 1), list_of<string>("b")); +} + +QPID_AUTO_TEST_CASE(testSequenceOptions) { + // Make sure the exchange qpid.msg_sequence property is properly replicated. + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c0(cluster[0], "c0"); + FieldTable ftargs; + ftargs.setInt("qpid.msg_sequence", 1); + c0.session.queueDeclare(arg::queue="q", arg::durable=durableFlag); + c0.session.exchangeDeclare(arg::exchange="ex", arg::type="direct", arg::arguments=ftargs); + c0.session.exchangeBind(arg::exchange="ex", arg::queue="q", arg::bindingKey="k"); + c0.session.messageTransfer(arg::content=makeMessage("1", "k", durableFlag), arg::destination="ex"); + c0.session.messageTransfer(arg::content=makeMessage("2", "k", durableFlag), arg::destination="ex"); + BOOST_CHECK_EQUAL(1, getMsgSequence(c0.subs.get("q", TIMEOUT))); + BOOST_CHECK_EQUAL(2, getMsgSequence(c0.subs.get("q", TIMEOUT))); + + cluster.add(); + Client c1(cluster[1]); + c1.session.messageTransfer(arg::content=makeMessage("3", "k", durableFlag), arg::destination="ex"); + BOOST_CHECK_EQUAL(3, getMsgSequence(c1.subs.get("q", TIMEOUT))); +} + +QPID_AUTO_TEST_CASE(testTxTransaction) { + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c0(cluster[0], "c0"); + c0.session.queueDeclare(arg::queue="q", arg::durable=durableFlag); + c0.session.messageTransfer(arg::content=makeMessage("A", "q", durableFlag)); + c0.session.messageTransfer(arg::content=makeMessage("B", "q", durableFlag)); + + // Start a transaction that will commit. + Session commitSession = c0.connection.newSession("commit"); + SubscriptionManager commitSubs(commitSession); + commitSession.txSelect(); + commitSession.messageTransfer(arg::content=makeMessage("a", "q", durableFlag)); + commitSession.messageTransfer(arg::content=makeMessage("b", "q", durableFlag)); + BOOST_CHECK_EQUAL(commitSubs.get("q", TIMEOUT).getData(), "A"); + + // Start a transaction that will roll back. + Session rollbackSession = c0.connection.newSession("rollback"); + SubscriptionManager rollbackSubs(rollbackSession); + rollbackSession.txSelect(); + rollbackSession.messageTransfer(arg::content=makeMessage("1", "q", durableFlag)); + Message rollbackMessage = rollbackSubs.get("q", TIMEOUT); + BOOST_CHECK_EQUAL(rollbackMessage.getData(), "B"); + + BOOST_CHECK_EQUAL(c0.session.queueQuery("q").getMessageCount(), 0u); + // Add new member mid transaction. + cluster.add(); + Client c1(cluster[1], "c1"); + + // More transactional work + BOOST_CHECK_EQUAL(c1.session.queueQuery("q").getMessageCount(), 0u); + rollbackSession.messageTransfer(arg::content=makeMessage("2", "q", durableFlag)); + commitSession.messageTransfer(arg::content=makeMessage("c", "q", durableFlag)); + rollbackSession.messageTransfer(arg::content=makeMessage("3", "q", durableFlag)); + + BOOST_CHECK_EQUAL(c1.session.queueQuery("q").getMessageCount(), 0u); + + // Commit/roll back. + commitSession.txCommit(); + rollbackSession.txRollback(); + rollbackSession.messageRelease(rollbackMessage.getId()); + + // Verify queue status: just the comitted messages and dequeues should remain. + BOOST_CHECK_EQUAL(c1.session.queueQuery("q").getMessageCount(), 4u); + BOOST_CHECK_EQUAL(c1.subs.get("q", TIMEOUT).getData(), "B"); + BOOST_CHECK_EQUAL(c1.subs.get("q", TIMEOUT).getData(), "a"); + BOOST_CHECK_EQUAL(c1.subs.get("q", TIMEOUT).getData(), "b"); + BOOST_CHECK_EQUAL(c1.subs.get("q", TIMEOUT).getData(), "c"); + + commitSession.close(); + rollbackSession.close(); +} + +QPID_AUTO_TEST_CASE(testUnacked) { + // Verify replication of unacknowledged messages. + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c0(cluster[0], "c0"); + + Message m; + + // Create unacked message: acquired but not accepted. + SubscriptionSettings manualAccept(FlowControl::unlimited(), ACCEPT_MODE_EXPLICIT, ACQUIRE_MODE_PRE_ACQUIRED, 0); + c0.session.queueDeclare("q1", arg::durable=durableFlag); + c0.session.messageTransfer(arg::content=makeMessage("11","q1", durableFlag)); + LocalQueue q1; + c0.subs.subscribe(q1, "q1", manualAccept); + BOOST_CHECK_EQUAL(q1.get(TIMEOUT).getData(), "11"); // Acquired but not accepted + BOOST_CHECK_EQUAL(c0.session.queueQuery("q1").getMessageCount(), 0u); // Gone from queue + + // Create unacked message: not acquired, accepted or completeed. + SubscriptionSettings manualAcquire(FlowControl::unlimited(), ACCEPT_MODE_EXPLICIT, ACQUIRE_MODE_NOT_ACQUIRED, 0); + c0.session.queueDeclare("q2", arg::durable=durableFlag); + c0.session.messageTransfer(arg::content=makeMessage("21","q2", durableFlag)); + c0.session.messageTransfer(arg::content=makeMessage("22","q2", durableFlag)); + LocalQueue q2; + c0.subs.subscribe(q2, "q2", manualAcquire); + m = q2.get(TIMEOUT); // Not acquired or accepted, still on queue + BOOST_CHECK_EQUAL(m.getData(), "21"); + BOOST_CHECK_EQUAL(c0.session.queueQuery("q2").getMessageCount(), 2u); // Not removed + c0.subs.getSubscription("q2").acquire(m); // Acquire manually + BOOST_CHECK_EQUAL(c0.session.queueQuery("q2").getMessageCount(), 1u); // Removed + BOOST_CHECK_EQUAL(q2.get(TIMEOUT).getData(), "22"); // Not acquired or accepted, still on queue + BOOST_CHECK_EQUAL(c0.session.queueQuery("q2").getMessageCount(), 1u); // 1 not acquired. + + // Create empty credit record: acquire and accept but don't complete. + SubscriptionSettings manualComplete(FlowControl::messageWindow(1), ACCEPT_MODE_EXPLICIT, ACQUIRE_MODE_PRE_ACQUIRED, 1, MANUAL_COMPLETION); + c0.session.queueDeclare("q3", arg::durable=durableFlag); + c0.session.messageTransfer(arg::content=makeMessage("31", "q3", durableFlag)); + c0.session.messageTransfer(arg::content=makeMessage("32", "q3", durableFlag)); + LocalQueue q3; + c0.subs.subscribe(q3, "q3", manualComplete); + Message m31=q3.get(TIMEOUT); + BOOST_CHECK_EQUAL(m31.getData(), "31"); // Automatically acquired & accepted but not completed. + BOOST_CHECK_EQUAL(c0.session.queueQuery("q3").getMessageCount(), 1u); + + // Add new member while there are unacked messages. + cluster.add(); + Client c1(cluster[1], "c1"); + + // Check queue counts + BOOST_CHECK_EQUAL(c1.session.queueQuery("q1").getMessageCount(), 0u); + BOOST_CHECK_EQUAL(c1.session.queueQuery("q2").getMessageCount(), 1u); + BOOST_CHECK_EQUAL(c1.session.queueQuery("q3").getMessageCount(), 1u); + + // Complete the empty credit message, should unblock the message behind it. + BOOST_CHECK_THROW(q3.get(0), Exception); + c0.session.markCompleted(SequenceSet(m31.getId()), true); + BOOST_CHECK_EQUAL(q3.get(TIMEOUT).getData(), "32"); + BOOST_CHECK_EQUAL(c0.session.queueQuery("q3").getMessageCount(), 0u); + BOOST_CHECK_EQUAL(c1.session.queueQuery("q3").getMessageCount(), 0u); + + // Close the original session - unacked messages should be requeued. + c0.session.close(); + BOOST_CHECK_EQUAL(c1.session.queueQuery("q1").getMessageCount(), 1u); + BOOST_CHECK_EQUAL(c1.session.queueQuery("q2").getMessageCount(), 2u); + + BOOST_CHECK_EQUAL(c1.subs.get("q1", TIMEOUT).getData(), "11"); + BOOST_CHECK_EQUAL(c1.subs.get("q2", TIMEOUT).getData(), "21"); + BOOST_CHECK_EQUAL(c1.subs.get("q2", TIMEOUT).getData(), "22"); +} + +// FIXME aconway 2009-06-17: test for unimplemented feature, enable when implemented. +void testUpdateTxState() { + // Verify that we update transaction state correctly to new members. + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c0(cluster[0], "c0"); + + // Do work in a transaction. + c0.session.txSelect(); + c0.session.queueDeclare("q", arg::durable=durableFlag); + c0.session.messageTransfer(arg::content=makeMessage("1","q", durableFlag)); + c0.session.messageTransfer(arg::content=makeMessage("2","q", durableFlag)); + Message m; + BOOST_CHECK(c0.subs.get(m, "q", TIMEOUT)); + BOOST_CHECK_EQUAL(m.getData(), "1"); + + // New member, TX not comitted, c1 should see nothing. + cluster.add(); + Client c1(cluster[1], "c1"); + BOOST_CHECK_EQUAL(c1.session.queueQuery(arg::queue="q").getMessageCount(), 0u); + + // After commit c1 shoudl see results of tx. + c0.session.txCommit(); + BOOST_CHECK_EQUAL(c1.session.queueQuery(arg::queue="q").getMessageCount(), 1u); + BOOST_CHECK(c1.subs.get(m, "q", TIMEOUT)); + BOOST_CHECK_EQUAL(m.getData(), "2"); + + // Another transaction with both members active. + c0.session.messageTransfer(arg::content=makeMessage("3","q", durableFlag)); + BOOST_CHECK_EQUAL(c1.session.queueQuery(arg::queue="q").getMessageCount(), 0u); + c0.session.txCommit(); + BOOST_CHECK_EQUAL(c1.session.queueQuery(arg::queue="q").getMessageCount(), 1u); + BOOST_CHECK(c1.subs.get(m, "q", TIMEOUT)); + BOOST_CHECK_EQUAL(m.getData(), "3"); +} + +QPID_AUTO_TEST_CASE(testUpdateMessageBuilder) { + // Verify that we update a partially recieved message to a new member. + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c0(cluster[0], "c0"); + c0.session.queueDeclare("q", arg::durable=durableFlag); + Sender sender(ConnectionAccess::getImpl(c0.connection), c0.session.getChannel()); + + // Send first 2 frames of message. + MessageTransferBody transfer( + ProtocolVersion(), string(), // default exchange. + framing::message::ACCEPT_MODE_NONE, + framing::message::ACQUIRE_MODE_PRE_ACQUIRED); + sender.send(transfer, true, false, true, true); + AMQHeaderBody header; + header.get<DeliveryProperties>(true)->setRoutingKey("q"); + if (durableFlag) + header.get<DeliveryProperties>(true)->setDeliveryMode(DELIVERY_MODE_PERSISTENT); + else + header.get<DeliveryProperties>(true)->setDeliveryMode(DELIVERY_MODE_NON_PERSISTENT); + sender.send(header, false, false, true, true); + + // No reliable way to ensure the partial message has arrived + // before we start the new broker, so we sleep. + sys::usleep(2500); + cluster.add(); + + // Send final 2 frames of message. + sender.send(AMQContentBody("ab"), false, true, true, false); + sender.send(AMQContentBody("cd"), false, true, false, true); + + // Verify message is enqued correctly on second member. + Message m; + Client c1(cluster[1], "c1"); + BOOST_CHECK(c1.subs.get(m, "q", TIMEOUT)); + BOOST_CHECK_EQUAL(m.getData(), "abcd"); + BOOST_CHECK_EQUAL(2u, knownBrokerPorts(c1.connection, 2).size()); +} + +QPID_AUTO_TEST_CASE(testConnectionKnownHosts) { + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c0(cluster[0], "c0"); + set<int> kb0 = knownBrokerPorts(c0.connection, 1); + BOOST_CHECK_EQUAL(kb0.size(), 1u); + BOOST_CHECK_EQUAL(kb0, makeSet(cluster)); + + cluster.add(); + Client c1(cluster[1], "c1"); + set<int> kb1 = knownBrokerPorts(c1.connection, 2); + kb0 = knownBrokerPorts(c0.connection, 2); + BOOST_CHECK_EQUAL(kb1.size(), 2u); + BOOST_CHECK_EQUAL(kb1, makeSet(cluster)); + BOOST_CHECK_EQUAL(kb1,kb0); + + cluster.add(); + Client c2(cluster[2], "c2"); + set<int> kb2 = knownBrokerPorts(c2.connection, 3); + kb1 = knownBrokerPorts(c1.connection, 3); + kb0 = knownBrokerPorts(c0.connection, 3); + BOOST_CHECK_EQUAL(kb2.size(), 3u); + BOOST_CHECK_EQUAL(kb2, makeSet(cluster)); + BOOST_CHECK_EQUAL(kb2,kb0); + BOOST_CHECK_EQUAL(kb2,kb1); + + cluster.killWithSilencer(1,c1.connection,9); + kb0 = knownBrokerPorts(c0.connection, 2); + kb2 = knownBrokerPorts(c2.connection, 2); + BOOST_CHECK_EQUAL(kb0.size(), 2u); + BOOST_CHECK_EQUAL(kb0, kb2); +} + +QPID_AUTO_TEST_CASE(testUpdateConsumers) { + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + + Client c0(cluster[0], "c0"); + c0.session.queueDeclare("p", arg::durable=durableFlag); + c0.session.queueDeclare("q", arg::durable=durableFlag); + c0.subs.subscribe(c0.lq, "q", FlowControl::zero()); + LocalQueue lp; + c0.subs.subscribe(lp, "p", FlowControl::messageCredit(1)); + c0.session.sync(); + + // Start new members + cluster.add(); // Local + Client c1(cluster[1], "c1"); + cluster.add(); + Client c2(cluster[2], "c2"); + + // Transfer messages + c0.session.messageTransfer(arg::content=makeMessage("aaa", "q", durableFlag)); + + c0.session.messageTransfer(arg::content=makeMessage("bbb", "p", durableFlag)); + c0.session.messageTransfer(arg::content=makeMessage("ccc", "p", durableFlag)); + + // Activate the subscription, ensure message removed on all queues. + c0.subs.setFlowControl("q", FlowControl::unlimited()); + Message m; + BOOST_CHECK(c0.lq.get(m, TIMEOUT)); + BOOST_CHECK_EQUAL(m.getData(), "aaa"); + BOOST_CHECK_EQUAL(c0.session.queueQuery("q").getMessageCount(), 0u); + BOOST_CHECK_EQUAL(c1.session.queueQuery("q").getMessageCount(), 0u); + BOOST_CHECK_EQUAL(c2.session.queueQuery("q").getMessageCount(), 0u); + + // Check second subscription's flow control: gets first message, not second. + BOOST_CHECK(lp.get(m, TIMEOUT)); + BOOST_CHECK_EQUAL(m.getData(), "bbb"); + BOOST_CHECK_EQUAL(c0.session.queueQuery("p").getMessageCount(), 1u); + BOOST_CHECK_EQUAL(c1.session.queueQuery("p").getMessageCount(), 1u); + BOOST_CHECK_EQUAL(c2.session.queueQuery("p").getMessageCount(), 1u); + + BOOST_CHECK(c0.subs.get(m, "p", TIMEOUT)); + BOOST_CHECK_EQUAL(m.getData(), "ccc"); + + // Kill the subscribing member, ensure further messages are not removed. + cluster.killWithSilencer(0,c0.connection,9); + BOOST_REQUIRE_EQUAL(knownBrokerPorts(c1.connection, 2).size(), 2u); + for (int i = 0; i < 10; ++i) { + c1.session.messageTransfer(arg::content=makeMessage("xxx", "q", durableFlag)); + BOOST_REQUIRE(c1.subs.get(m, "q", TIMEOUT)); + BOOST_REQUIRE_EQUAL(m.getData(), "xxx"); + } +} + +// Test that message data and delivery properties are updated properly. +QPID_AUTO_TEST_CASE(testUpdateMessages) { + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c0(cluster[0], "c0"); + + // Create messages with different delivery properties + c0.session.queueDeclare("q", arg::durable=durableFlag); + c0.session.exchangeBind(arg::exchange="amq.fanout", arg::queue="q"); + c0.session.messageTransfer(arg::content=makeMessage("foo","q", durableFlag)); + c0.session.messageTransfer(arg::content=makeMessage("bar","q", durableFlag), + arg::destination="amq.fanout"); + + while (c0.session.queueQuery("q").getMessageCount() != 2) + sys::usleep(1000); // Wait for message to show up on broker 0. + + // Add a new broker, it will catch up. + cluster.add(); + + // Do some work post-add + c0.session.queueDeclare("p", arg::durable=durableFlag); + c0.session.messageTransfer(arg::content=makeMessage("pfoo","p", durableFlag)); + + // Do some work post-join + BOOST_REQUIRE_EQUAL(knownBrokerPorts(c0.connection, 2).size(), 2u); + c0.session.messageTransfer(arg::content=makeMessage("pbar","p", durableFlag)); + + // Verify new brokers have state. + Message m; + + Client c1(cluster[1], "c1"); + + BOOST_CHECK(c1.subs.get(m, "q", TIMEOUT)); + BOOST_CHECK_EQUAL(m.getData(), "foo"); + BOOST_CHECK(m.getDeliveryProperties().hasExchange()); + BOOST_CHECK_EQUAL(m.getDeliveryProperties().getExchange(), ""); + BOOST_CHECK(c1.subs.get(m, "q", TIMEOUT)); + BOOST_CHECK_EQUAL(m.getData(), "bar"); + BOOST_CHECK(m.getDeliveryProperties().hasExchange()); + BOOST_CHECK_EQUAL(m.getDeliveryProperties().getExchange(), "amq.fanout"); + BOOST_CHECK_EQUAL(c1.session.queueQuery("q").getMessageCount(), 0u); + + // Add another broker, don't wait for join - should be stalled till ready. + cluster.add(); + Client c2(cluster[2], "c2"); + BOOST_CHECK(c2.subs.get(m, "p", TIMEOUT)); + BOOST_CHECK_EQUAL(m.getData(), "pfoo"); + BOOST_CHECK(c2.subs.get(m, "p", TIMEOUT)); + BOOST_CHECK_EQUAL(m.getData(), "pbar"); + BOOST_CHECK_EQUAL(c2.session.queueQuery("p").getMessageCount(), 0u); +} + +QPID_AUTO_TEST_CASE(testWiringReplication) { + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(3, args, -1); + Client c0(cluster[0]); + BOOST_CHECK(c0.session.queueQuery("q").getQueue().empty()); + BOOST_CHECK(c0.session.exchangeQuery("ex").getType().empty()); + c0.session.queueDeclare("q", arg::durable=durableFlag); + c0.session.exchangeDeclare("ex", arg::type="direct"); + c0.session.close(); + c0.connection.close(); + // Verify all brokers get wiring update. + for (size_t i = 0; i < cluster.size(); ++i) { + BOOST_MESSAGE("i == "<< i); + Client c(cluster[i]); + BOOST_CHECK_EQUAL("q", c.session.queueQuery("q").getQueue()); + BOOST_CHECK_EQUAL("direct", c.session.exchangeQuery("ex").getType()); + } +} + +QPID_AUTO_TEST_CASE(testMessageEnqueue) { + // Enqueue on one broker, dequeue on another. + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(2, args, -1); + Client c0(cluster[0]); + c0.session.queueDeclare("q", arg::durable=durableFlag); + c0.session.messageTransfer(arg::content=makeMessage("foo", "q", durableFlag)); + c0.session.messageTransfer(arg::content=makeMessage("bar", "q", durableFlag)); + c0.session.close(); + Client c1(cluster[1]); + Message msg; + BOOST_CHECK(c1.subs.get(msg, "q", TIMEOUT)); + BOOST_CHECK_EQUAL(string("foo"), msg.getData()); + BOOST_CHECK(c1.subs.get(msg, "q", TIMEOUT)); + BOOST_CHECK_EQUAL(string("bar"), msg.getData()); +} + +QPID_AUTO_TEST_CASE(testMessageDequeue) { + // Enqueue on one broker, dequeue on two others. + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(3, args, -1); + Client c0(cluster[0], "c0"); + c0.session.queueDeclare("q", arg::durable=durableFlag); + c0.session.messageTransfer(arg::content=makeMessage("foo", "q", durableFlag)); + c0.session.messageTransfer(arg::content=makeMessage("bar", "q", durableFlag)); + + Message msg; + + // Dequeue on 2 others, ensure correct order. + Client c1(cluster[1], "c1"); + BOOST_CHECK(c1.subs.get(msg, "q")); + BOOST_CHECK_EQUAL("foo", msg.getData()); + + Client c2(cluster[2], "c2"); + BOOST_CHECK(c1.subs.get(msg, "q")); + BOOST_CHECK_EQUAL("bar", msg.getData()); + + // Queue should be empty on all cluster members. + BOOST_CHECK_EQUAL(0u, c0.session.queueQuery("q").getMessageCount()); + BOOST_CHECK_EQUAL(0u, c1.session.queueQuery("q").getMessageCount()); + BOOST_CHECK_EQUAL(0u, c2.session.queueQuery("q").getMessageCount()); +} + +QPID_AUTO_TEST_CASE(testDequeueWaitingSubscription) { + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(3, args, -1); + Client c0(cluster[0]); + BOOST_REQUIRE_EQUAL(knownBrokerPorts(c0.connection, 3).size(), 3u); // Wait for brokers. + + // First start a subscription. + c0.session.queueDeclare("q", arg::durable=durableFlag); + c0.subs.subscribe(c0.lq, "q", FlowControl::messageCredit(2)); + + // Now send messages + Client c1(cluster[1]); + c1.session.messageTransfer(arg::content=makeMessage("foo", "q", durableFlag)); + c1.session.messageTransfer(arg::content=makeMessage("bar", "q", durableFlag)); + + // Check they arrived + Message m; + BOOST_CHECK(c0.lq.get(m, TIMEOUT)); + BOOST_CHECK_EQUAL("foo", m.getData()); + BOOST_CHECK(c0.lq.get(m, TIMEOUT)); + BOOST_CHECK_EQUAL("bar", m.getData()); + + // Queue should be empty on all cluster members. + Client c2(cluster[2]); + BOOST_CHECK_EQUAL(0u, c0.session.queueQuery("q").getMessageCount()); + BOOST_CHECK_EQUAL(0u, c1.session.queueQuery("q").getMessageCount()); + BOOST_CHECK_EQUAL(0u, c2.session.queueQuery("q").getMessageCount()); +} + +QPID_AUTO_TEST_CASE(queueDurabilityPropagationToNewbie) +{ + /* + Start with a single broker. + Set up two queues: one durable, and one not. + Add a new broker to the cluster. + Make sure it has one durable and one non-durable queue. + */ + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c0(cluster[0]); + c0.session.queueDeclare("durable_queue", arg::durable=true); + c0.session.queueDeclare("non_durable_queue", arg::durable=false); + cluster.add(); + Client c1(cluster[1]); + QueueQueryResult durable_query = c1.session.queueQuery ( "durable_queue" ); + QueueQueryResult non_durable_query = c1.session.queueQuery ( "non_durable_queue" ); + BOOST_CHECK_EQUAL(durable_query.getQueue(), std::string("durable_queue")); + BOOST_CHECK_EQUAL(non_durable_query.getQueue(), std::string("non_durable_queue")); + + BOOST_CHECK_EQUAL ( durable_query.getDurable(), true ); + BOOST_CHECK_EQUAL ( non_durable_query.getDurable(), false ); +} + + +QPID_AUTO_TEST_CASE(testHeartbeatCancelledOnFailover) +{ + + struct Sender : FailoverManager::Command + { + std::string queue; + std::string content; + + Sender(const std::string& q, const std::string& c) : queue(q), content(c) {} + + void execute(AsyncSession& session, bool) + { + session.messageTransfer(arg::content=makeMessage(content, queue, durableFlag)); + } + }; + + struct Receiver : FailoverManager::Command, MessageListener, qpid::sys::Runnable + { + FailoverManager& mgr; + std::string queue; + std::string expectedContent; + qpid::client::Subscription subscription; + qpid::sys::Monitor lock; + bool ready, failed; + + Receiver(FailoverManager& m, const std::string& q, const std::string& c) : mgr(m), queue(q), expectedContent(c), ready(false), failed(false) {} + + void received(Message& message) + { + BOOST_CHECK_EQUAL(expectedContent, message.getData()); + subscription.cancel(); + } + + void execute(AsyncSession& session, bool) + { + session.queueDeclare(arg::queue=queue, arg::durable=durableFlag); + SubscriptionManager subs(session); + subscription = subs.subscribe(*this, queue); + session.sync(); + setReady(); + subs.run(); + //cleanup: + session.queueDelete(arg::queue=queue); + } + + void run() + { + try { + mgr.execute(*this); + } + catch (const std::exception& e) { + BOOST_MESSAGE("Exception in mgr.execute: " << e.what()); + failed = true; + } + } + + void waitForReady() + { + qpid::sys::Monitor::ScopedLock l(lock); + while (!ready) { + lock.wait(); + } + } + + void setReady() + { + qpid::sys::Monitor::ScopedLock l(lock); + ready = true; + lock.notify(); + } + }; + + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(2, args, -1); + ConnectionSettings settings; + settings.port = cluster[1]; + settings.heartbeat = 1; + FailoverManager fmgr(settings); + Sender sender("my-queue", "my-data"); + Receiver receiver(fmgr, "my-queue", "my-data"); + qpid::sys::Thread runner(receiver); + receiver.waitForReady(); + { + ScopedSuppressLogging allQuiet; // suppress connection closed messages + cluster.kill(1); + //sleep for 2 secs to allow the heartbeat task to fire on the now dead connection: + ::usleep(2*1000*1000); + } + fmgr.execute(sender); + runner.join(); + BOOST_CHECK(!receiver.failed); + fmgr.close(); +} + +QPID_AUTO_TEST_CASE(testPolicyUpdate) { + //tests that the policys internal state is accurate on newly + //joined nodes + ClusterFixture::Args args; + args += "--log-enable", "critical"; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c1(cluster[0], "c1"); + { + ScopedSuppressLogging allQuiet; + QueueOptions options; + options.setSizePolicy(REJECT, 0, 2); + c1.session.queueDeclare("q", arg::arguments=options, arg::durable=durableFlag); + c1.session.messageTransfer(arg::content=makeMessage("one", "q", durableFlag)); + cluster.add(); + Client c2(cluster[1], "c2"); + c2.session.messageTransfer(arg::content=makeMessage("two", "q", durableFlag)); + + BOOST_CHECK_THROW(c2.session.messageTransfer(arg::content=makeMessage("three", "q", durableFlag)), framing::ResourceLimitExceededException); + + Message received; + BOOST_CHECK(c1.subs.get(received, "q")); + BOOST_CHECK_EQUAL(received.getData(), std::string("one")); + BOOST_CHECK(c1.subs.get(received, "q")); + BOOST_CHECK_EQUAL(received.getData(), std::string("two")); + BOOST_CHECK(!c1.subs.get(received, "q")); + } +} + +QPID_AUTO_TEST_CASE(testExclusiveQueueUpdate) { + //tests that exclusive queues are accurately replicated on newly + //joined nodes + ClusterFixture::Args args; + args += "--log-enable", "critical"; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c1(cluster[0], "c1"); + { + ScopedSuppressLogging allQuiet; + c1.session.queueDeclare("q", arg::exclusive=true, arg::autoDelete=true, arg::alternateExchange="amq.fanout"); + cluster.add(); + Client c2(cluster[1], "c2"); + QueueQueryResult result = c2.session.queueQuery("q"); + BOOST_CHECK_EQUAL(result.getQueue(), std::string("q")); + BOOST_CHECK(result.getExclusive()); + BOOST_CHECK(result.getAutoDelete()); + BOOST_CHECK(!result.getDurable()); + BOOST_CHECK_EQUAL(result.getAlternateExchange(), std::string("amq.fanout")); + BOOST_CHECK_THROW(c2.session.queueDeclare(arg::queue="q", arg::exclusive=true, arg::passive=true), framing::ResourceLockedException); + c1.session.close(); + c1.connection.close(); + c2.session = c2.connection.newSession(); + BOOST_CHECK_THROW(c2.session.queueDeclare(arg::queue="q", arg::passive=true), framing::NotFoundException); + } +} + +/** + * Subscribes to specified queue and acquires up to the specified + * number of message but does not accept or release them. These + * message are therefore 'locked' by the clients session. + */ +Subscription lockMessages(Client& client, const std::string& queue, int count) +{ + LocalQueue q; + SubscriptionSettings settings(FlowControl::messageCredit(count)); + settings.autoAck = 0; + Subscription sub = client.subs.subscribe(q, queue, settings); + client.session.messageFlush(sub.getName()); + return sub; +} + +/** + * check that the specified queue contains the expected set of + * messages (matched on content) for all nodes in the cluster + */ +void checkQueue(ClusterFixture& cluster, const std::string& queue, const std::vector<std::string>& messages) +{ + for (size_t i = 0; i < cluster.size(); i++) { + Client client(cluster[i], (boost::format("%1%_%2%") % "c" % (i+1)).str()); + BOOST_CHECK_EQUAL(browse(client, queue, messages.size()), messages); + client.close(); + } +} + +void send(Client& client, const std::string& queue, int count, int start=1, const std::string& base="m", + const std::string& lvqKey="") +{ + for (int i = 0; i < count; i++) { + Message message = makeMessage((boost::format("%1%_%2%") % base % (i+start)).str(), queue, durableFlag); + if (!lvqKey.empty()) message.getHeaders().setString(QueueOptions::strLVQMatchProperty, lvqKey); + client.session.messageTransfer(arg::content=message); + } +} + +QPID_AUTO_TEST_CASE(testRingQueueUpdate) { + //tests that ring queues are accurately replicated on newly + //joined nodes + ClusterFixture::Args args; + args += "--log-enable", "critical"; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c1(cluster[0], "c1"); + { + ScopedSuppressLogging allQuiet; + QueueOptions options; + options.setSizePolicy(RING, 0, 5); + c1.session.queueDeclare("q", arg::arguments=options, arg::durable=durableFlag); + send(c1, "q", 5); + lockMessages(c1, "q", 1); + //add new node + cluster.add(); + BOOST_CHECK_EQUAL(2u, knownBrokerPorts(c1.connection, 2).size());//wait till joined + //send one more message + send(c1, "q", 1, 6); + //release locked message + c1.close(); + //check state of queue on both nodes + checkQueue(cluster, "q", list_of<string>("m_2")("m_3")("m_4")("m_5")("m_6")); + } +} + +QPID_AUTO_TEST_CASE(testRingQueueUpdate2) { + //tests that ring queues are accurately replicated on newly joined + //nodes; just like testRingQueueUpdate, but new node joins after + //the sixth message has been sent. + ClusterFixture::Args args; + args += "--log-enable", "critical"; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c1(cluster[0], "c1"); + { + ScopedSuppressLogging allQuiet; + QueueOptions options; + options.setSizePolicy(RING, 0, 5); + c1.session.queueDeclare("q", arg::arguments=options, arg::durable=durableFlag); + send(c1, "q", 5); + lockMessages(c1, "q", 1); + //send sixth message + send(c1, "q", 1, 6); + //add new node + cluster.add(); + BOOST_CHECK_EQUAL(2u, knownBrokerPorts(c1.connection, 2).size());//wait till joined + //release locked message + c1.close(); + //check state of queue on both nodes + checkQueue(cluster, "q", list_of<string>("m_2")("m_3")("m_4")("m_5")("m_6")); + } +} + +QPID_AUTO_TEST_CASE(testLvqUpdate) { + //tests that lvqs are accurately replicated on newly joined nodes + ClusterFixture::Args args; + args += "--log-enable", "critical"; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c1(cluster[0], "c1"); + { + ScopedSuppressLogging allQuiet; + QueueOptions options; + options.setOrdering(LVQ); + c1.session.queueDeclare("q", arg::arguments=options, arg::durable=durableFlag); + + send(c1, "q", 5, 1, "a", "a"); + send(c1, "q", 2, 1, "b", "b"); + send(c1, "q", 1, 1, "c", "c"); + send(c1, "q", 1, 3, "b", "b"); + + //add new node + cluster.add(); + BOOST_CHECK_EQUAL(2u, knownBrokerPorts(c1.connection, 2).size());//wait till joined + + //check state of queue on both nodes + checkQueue(cluster, "q", list_of<string>("a_5")("b_3")("c_1")); + } +} + + +QPID_AUTO_TEST_CASE(testBrowsedLvqUpdate) { + //tests that lvqs are accurately replicated on newly joined nodes + //if the lvq state has been affected by browsers + ClusterFixture::Args args; + args += "--log-enable", "critical"; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c1(cluster[0], "c1"); + { + ScopedSuppressLogging allQuiet; + QueueOptions options; + options.setOrdering(LVQ); + c1.session.queueDeclare("q", arg::arguments=options, arg::durable=durableFlag); + + send(c1, "q", 1, 1, "a", "a"); + send(c1, "q", 2, 1, "b", "b"); + send(c1, "q", 1, 1, "c", "c"); + checkQueue(cluster, "q", list_of<string>("a_1")("b_2")("c_1")); + send(c1, "q", 4, 2, "a", "a"); + send(c1, "q", 1, 3, "b", "b"); + + //add new node + cluster.add(); + BOOST_CHECK_EQUAL(2u, knownBrokerPorts(c1.connection, 2).size());//wait till joined + + //check state of queue on both nodes + checkQueue(cluster, "q", list_of<string>("a_1")("b_2")("c_1")("a_5")("b_3")); + } +} + +QPID_AUTO_TEST_CASE(testRelease) { + //tests that releasing a messages that was unacked when one node + //joined works correctly + ClusterFixture::Args args; + args += "--log-enable", "critical"; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c1(cluster[0], "c1"); + { + ScopedSuppressLogging allQuiet; + c1.session.queueDeclare("q", arg::durable=durableFlag); + for (int i = 0; i < 5; i++) { + c1.session.messageTransfer(arg::content=makeMessage((boost::format("%1%_%2%") % "m" % (i+1)).str(), "q", durableFlag)); + } + //receive but don't ack a message + LocalQueue lq; + SubscriptionSettings lqSettings(FlowControl::messageCredit(1)); + lqSettings.autoAck = 0; + Subscription lqSub = c1.subs.subscribe(lq, "q", lqSettings); + c1.session.messageFlush("q"); + Message received; + BOOST_CHECK(lq.get(received)); + BOOST_CHECK_EQUAL(received.getData(), std::string("m_1")); + + //add new node + cluster.add(); + + lqSub.release(lqSub.getUnaccepted()); + + //check state of queue on both nodes + vector<string> expected = list_of<string>("m_1")("m_2")("m_3")("m_4")("m_5"); + Client c3(cluster[0], "c3"); + BOOST_CHECK_EQUAL(browse(c3, "q", 5), expected); + Client c2(cluster[1], "c2"); + BOOST_CHECK_EQUAL(browse(c2, "q", 5), expected); + } +} + + +// Browse for 1 message with byte credit, return true if a message was +// received false if not. +bool browseByteCredit(Client& c, const string& q, int n, Message& m) { + SubscriptionSettings browseSettings( + FlowControl(1, n, false), // 1 message, n bytes credit, no window + ACCEPT_MODE_NONE, + ACQUIRE_MODE_NOT_ACQUIRED, + 0 // No auto-ack. + ); + LocalQueue lq; + Subscription s = c.subs.subscribe(lq, q, browseSettings); + c.session.messageFlush(arg::destination=q, arg::sync=true); + c.session.sync(); + c.subs.getSubscription(q).cancel(); + return lq.get(m, 0); // No timeout, flush should push message thru. +} + +// Ensure cluster update preserves exact message size, use byte credt as test. +QPID_AUTO_TEST_CASE(testExactByteCredit) { + ClusterFixture cluster(1, prepareArgs(), -1); + Client c0(cluster[0], "c0"); + c0.session.queueDeclare("q"); + c0.session.messageTransfer(arg::content=Message("MyMessage", "q")); + cluster.add(); + + int size=36; // Size of message on broker: headers+body + Client c1(cluster[1], "c1"); + Message m; + + // Ensure we get the message with exact credit. + BOOST_CHECK(browseByteCredit(c0, "q", size, m)); + BOOST_CHECK(browseByteCredit(c1, "q", size, m)); + // and not with one byte less. + BOOST_CHECK(!browseByteCredit(c0, "q", size-1, m)); + BOOST_CHECK(!browseByteCredit(c1, "q", size-1, m)); +} + +// Test that consumer positions are updated correctly. +// Regression test for https://bugzilla.redhat.com/show_bug.cgi?id=541927 +// +QPID_AUTO_TEST_CASE(testUpdateConsumerPosition) { + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c0(cluster[0], "c0"); + + c0.session.queueDeclare("q", arg::durable=durableFlag); + SubscriptionSettings settings; + settings.autoAck = 0; + // Set the acquire mode to 'not-acquired' the consumer moves along the queue + // but does not acquire (remove) messages. + settings.acquireMode = ACQUIRE_MODE_NOT_ACQUIRED; + Subscription s = c0.subs.subscribe(c0.lq, "q", settings); + c0.session.messageTransfer(arg::content=makeMessage("1", "q", durableFlag)); + BOOST_CHECK_EQUAL("1", c0.lq.get(TIMEOUT).getData()); + + // Add another member, send/receive another message and acquire + // the messages. With the bug, this creates an inconsistency + // because the browse position was not updated to the new member. + cluster.add(); + c0.session.messageTransfer(arg::content=makeMessage("2", "q", durableFlag)); + BOOST_CHECK_EQUAL("2", c0.lq.get(TIMEOUT).getData()); + s.acquire(s.getUnacquired()); + s.accept(s.getUnaccepted()); + + // In the bug we now have 0 messages on cluster[0] and 1 message on cluster[1] + // Subscribing on cluster[1] provokes an error that shuts down cluster[0] + Client c1(cluster[1], "c1"); + Subscription s1 = c1.subs.subscribe(c1.lq, "q"); // Default auto-ack=1 + Message m; + BOOST_CHECK(!c1.lq.get(m, TIMEOUT/10)); + BOOST_CHECK_EQUAL(c1.session.queueQuery("q").getMessageCount(), 0u); + BOOST_CHECK_EQUAL(c0.session.queueQuery("q").getMessageCount(), 0u); +} + +QPID_AUTO_TEST_CASE(testFairsharePriorityDelivery) { + ClusterFixture::Args args; + prepareArgs(args, durableFlag); + ClusterFixture cluster(1, args, -1); + Client c0(cluster[0], "c0"); + + FieldTable arguments; + arguments.setInt("x-qpid-priorities", 10); + arguments.setInt("x-qpid-fairshare", 5); + c0.session.queueDeclare("q", arg::durable=durableFlag, arg::arguments=arguments); + + //send messages of different priorities + for (int i = 0; i < 20; i++) { + Message msg = makeMessage((boost::format("msg-%1%") % i).str(), "q", durableFlag); + msg.getDeliveryProperties().setPriority(i % 2 ? 9 : 5); + c0.session.messageTransfer(arg::content=msg); + } + + //pull off a couple of the messages (first four should be the top priority messages + for (int i = 0; i < 4; i++) { + BOOST_CHECK_EQUAL((boost::format("msg-%1%") % ((i*2)+1)).str(), c0.subs.get("q", TIMEOUT).getData()); + } + + // Add another member + cluster.add(); + Client c1(cluster[1], "c1"); + + //pull off some more messages + BOOST_CHECK_EQUAL((boost::format("msg-%1%") % 9).str(), c0.subs.get("q", TIMEOUT).getData()); + BOOST_CHECK_EQUAL((boost::format("msg-%1%") % 0).str(), c1.subs.get("q", TIMEOUT).getData()); + BOOST_CHECK_EQUAL((boost::format("msg-%1%") % 2).str(), c0.subs.get("q", TIMEOUT).getData()); + + //check queue has same content on both nodes + BOOST_CHECK_EQUAL(browse(c0, "q", 12), browse(c1, "q", 12)); +} + +QPID_AUTO_TEST_SUITE_END() +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/cluster_test_logs.py b/qpid/cpp/src/tests/cluster_test_logs.py new file mode 100755 index 0000000000..9f7d1e2f6c --- /dev/null +++ b/qpid/cpp/src/tests/cluster_test_logs.py @@ -0,0 +1,119 @@ +#!/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. +# + +# Functions for comparing broker log files, used by cluster_tests.py. + +import os, os.path, re, glob +from itertools import izip + +def split_log(log): + """Split a broker log at checkpoints where a member joins. + Return the set of checkpoints discovered.""" + checkpoint_re = re.compile("Member joined, frameSeq=([0-9]+), queue snapshot:") + outfile = None + checkpoints = [] + for l in open(log): + match = checkpoint_re.search(l) + if match: + checkpoint = match.groups()[0] + checkpoints.append(checkpoint) + if outfile: outfile.close() + outfile = open("%s.%s"%(log, checkpoint), 'w') + + if outfile: outfile.write(l) + if outfile: outfile.close() + return checkpoints + +def filter_log(log): + """Filter the contents of a log file to remove data that is expected + to differ between brokers in a cluster. Filtered log contents between + the same checkpoints should match across the cluster.""" + out = open("%s.filter"%(log), 'w') + # Lines to skip entirely, expected differences + skip = "|".join([ + 'local connection', # Only on local broker + 'UPDATER|UPDATEE', # Ignore update process + 'stall for update|unstall, ignore update|cancelled offer .* unstall', + 'caught up', + 'active for links|Passivating links|Activating links', + 'info Connection.* connected to', # UpdateClient connection + 'warning Connection [\d+ [0-9.:]+] closed', # UpdateClient connection + 'warning Broker closed connection: 200, OK', + 'task late', + 'task overran', + 'warning CLOSING .* unsent data', + 'Inter-broker link ', + 'Running in a cluster, marking store', + 'debug Sending keepalive signal to watchdog', # Watchdog timer thread + 'last broker standing joined by 1 replicas, updating queue policies.', + 'Connection .* timed out: closing' # heartbeat connection close + ]) + # Regex to match a UUID + uuid='\w\w\w\w\w\w\w\w-\w\w\w\w-\w\w\w\w-\w\w\w\w-\w\w\w\w\w\w\w\w\w\w\w\w' + # Substitutions to remove expected differences + subs = [ + (r'\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d ', ''), # Remove timestamp + (r'cluster\([0-9.: ]*', 'cluster('), # Remove cluster node id + (r' local\)| shadow\)', ')'), # Remove local/shadow indication + (r'CATCHUP', 'READY'), # Treat catchup as equivalent to ready. + (r'OFFER', 'READY'), # Treat offer as equivalent to ready. + # System UUID expected to be different + (r'(org.apache.qpid.broker:system[:(])%s(\)?)'%(uuid), r'\1UUID\2'), + + # TODO aconway 2010-12-20: review if these should be expected: + (r' len=\d+', ' len=NN'), # buffer lengths + (r' map={.*_object_name:([^,}]*)[,}].*', r' \1'), # V2 map - just keep name + (r'\d+-\d+-\d+--\d+', 'X-X-X--X'), # V1 Object IDs + ] + # Substitutions to mask known issue: durable test shows inconsistent "changed stats for com.redhat.rhm.store:journal" messages. + skip += '|Changed V[12] statistics com.redhat.rhm.store:journal' + subs += [(r'to=console.obj.1.0.com.redhat.rhm.store.journal props=\d+ stats=\d+', + 'to=console.obj.1.0.com.redhat.rhm.store.journal props=NN stats=NN')] + + skip_re = re.compile(skip) + subs = [(re.compile(pattern), subst) for pattern, subst in subs] + for l in open(log): + if skip_re.search(l): continue + for pattern,subst in subs: l = re.sub(pattern,subst,l) + out.write(l) + out.close() + +def verify_logs(): + """Compare log files from cluster brokers, verify that they correspond correctly.""" + for l in glob.glob("*.log"): filter_log(l) + checkpoints = set() + for l in glob.glob("*.filter"): checkpoints = checkpoints.union(set(split_log(l))) + errors=[] + for c in checkpoints: + fragments = glob.glob("*.filter.%s"%(c)) + fragments.sort(reverse=True, key=os.path.getsize) + while len(fragments) >= 2: + a = fragments.pop(0) + b = fragments[0] + for ab in izip(open(a), open(b)): + if ab[0] != ab[1]: + errors.append("\n %s %s"%(a, b)) + break + if errors: + raise Exception("Files differ in %s"%(os.getcwd())+"".join(errors)) + +# Can be run as a script. +if __name__ == "__main__": + verify_logs() diff --git a/qpid/cpp/src/tests/cluster_test_scripts/README.txt b/qpid/cpp/src/tests/cluster_test_scripts/README.txt new file mode 100644 index 0000000000..e861a2f397 --- /dev/null +++ b/qpid/cpp/src/tests/cluster_test_scripts/README.txt @@ -0,0 +1,20 @@ +Cluster test scripts. + +A set of scripts to start and stop cluster and test clients on +multiple hosts using ssh. + +Pre-requisites: You must be + - set up for password-free ssh access to the test hosts. + - a member of the ais group on all the test hosts. + +Configuration: + +Copy defaults.sh to config.sh and edit the values as necessary. + +Test scripts: + +Test scripts use the functions in functions.sh to start & monitor +cluster and clients. +A test script can collect other scripts. + + diff --git a/qpid/cpp/src/tests/cluster_test_scripts/cluster_check b/qpid/cpp/src/tests/cluster_test_scripts/cluster_check new file mode 100755 index 0000000000..05fcc1bcd2 --- /dev/null +++ b/qpid/cpp/src/tests/cluster_test_scripts/cluster_check @@ -0,0 +1,37 @@ +#!/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. +# + +# Check that all members of a cluster are running + +source config.sh + +HOSTS=(`cat $CLUSTER_HOME/hosts`) +PORTS=(`cat $CLUSTER_HOME/ports`) + +for ((i=0; i<${#HOSTS[*]}; ++i)); do + host=${HOSTS[$i]} + port=${PORTS[$i]} + ssh $host "$QPIDD -cp $port" > /dev/null || { + ret=1 + echo "ERROR: broker not running $host:$port" + } +done +exit $ret diff --git a/qpid/cpp/src/tests/cluster_test_scripts/cluster_start b/qpid/cpp/src/tests/cluster_test_scripts/cluster_start new file mode 100755 index 0000000000..8911358f7e --- /dev/null +++ b/qpid/cpp/src/tests/cluster_test_scripts/cluster_start @@ -0,0 +1,56 @@ +#!/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. +# + +# Start a cluster +# +# Arguments: NAME HOST [host...] +# Start a cluster called NAME with N nodes running on the given HOSTs +# repeat the host name to run multiple brokers on one host. Use dynamic +# ports. +# +# Log files, data directories and hosts/ports files are all stored under +# $HOME/cluster_test/$NAME +# + +source config.sh + +CLUSTER_NAME=`date +"${USER}_%F_%T"` +HOSTS=($BROKER_HOSTS) +for ((i = 0; i < ${#HOSTS[*]}; ++i)) ; do + host=${HOSTS[$i]} + datadir=$CLUSTER_HOME/broker$i + log=$datadir/qpidd.log + ssh $host "rm -rf $datadir; mkdir -p $datadir" || { + echo "ERROR: can't make data dir $datadir"; exit 1 + } + port=`ssh $host "echo $QPIDD -dp0 --cluster-name=$CLUSTER_NAME \ + --data-dir=$datadir \ + --log-to-file=$log --log-prefix=broker$i \ + $QPIDD_OPTS | newgrp ais"` || { + error "ERROR: can't start broker $i on $host"; exit 1; + } + PORTS="$PORTS $port" +done + +echo "$BROKER_HOSTS" > $CLUSTER_HOME/hosts +echo "$PORTS" > $CLUSTER_HOME/ports + +`dirname $0`/cluster_check $NAME diff --git a/qpid/cpp/src/tests/cluster_test_scripts/cluster_stop b/qpid/cpp/src/tests/cluster_test_scripts/cluster_stop new file mode 100755 index 0000000000..09aa8f3b21 --- /dev/null +++ b/qpid/cpp/src/tests/cluster_test_scripts/cluster_stop @@ -0,0 +1,38 @@ +#!/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. +# + +# Stop the cluster. + +source config.sh + +HOSTS=(`cat $CLUSTER_HOME/hosts`) +PORTS=(`cat $CLUSTER_HOME/ports`) + +for ((i=0; i<${#HOSTS[*]}; ++i)); do + host=${HOSTS[$i]} + port=${PORTS[$i]} + ssh $host "$QPIDD -qp $port" > /dev/null || { + ret=1 + echo "ERROR: stopping broker at $host:$port" + } +done + +exit $ret diff --git a/qpid/cpp/src/tests/cluster_test_scripts/config_example.sh b/qpid/cpp/src/tests/cluster_test_scripts/config_example.sh new file mode 100755 index 0000000000..d47c9a9c77 --- /dev/null +++ b/qpid/cpp/src/tests/cluster_test_scripts/config_example.sh @@ -0,0 +1,44 @@ +# Cluster configuration. + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# All output stored under $HOME/$CLUSTER_HOME. +CLUSTER_HOME=$HOME/cluster_test + +# Hosts where brokers will be run. Repeat hostname to run multiple brokers on 1 host. +BROKER_HOSTS="mrg22 mrg23 mrg24 mrg25 mrg26" + +# Hosts where clients will be run. +CLIENT_HOSTS="$BROKER_HOSTS" + +# Paths to executables +QPIDD=qpidd +PERFTEST=perftest + +# Directory containing tests +TESTDIR=/usr/bin + +# Options for qpidd, must be sufficient to load the cluster plugin. +# Scripts will add --cluster-name, --daemon, --port and --log-to-file options here. +QPIDD_OPTS=" \ +--auth=no \ +--log-enable=notice+ \ +--log-enable=debug+:cluster \ +" diff --git a/qpid/cpp/src/tests/cluster_test_scripts/perftest b/qpid/cpp/src/tests/cluster_test_scripts/perftest new file mode 100755 index 0000000000..984761eb5f --- /dev/null +++ b/qpid/cpp/src/tests/cluster_test_scripts/perftest @@ -0,0 +1,54 @@ +#!/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 a distributed perftest against a cluster. +# Args: npubs nsubs [perftest-options] + +source config.sh + +NPUBS=${1:-4} ; shift +NSUBS=${1:-4} ; shift +OPTS="--npubs $NPUBS --nsubs $NSUBS $*" + +CLIENTS=($CLIENT_HOSTS) +BROKERS=(`cat $CLUSTER_HOME/hosts`) +PORTS=(`cat $CLUSTER_HOME/ports`) + +start() { + client=${CLIENTS[i % ${#CLIENTS[*]}]} + broker=${BROKERS[i % ${#BROKERS[*]}]} + port=${PORTS[i % ${#PORTS[*]}]} + ssh -n $client $PERFTEST $OPTS $* -b $broker -p $port & + PIDS="$PIDS $!" +} + +ssh ${CLIENTS[0]} $PERFTEST $OPTS --setup -b ${BROKERS[0]} -p${PORTS[0]} +for (( i=0 ; i < $NPUBS ; ++i)); do start --publish; done +for (( ; i < $NPUBS+$NSUBS ; ++i)); do start --subscribe; done +ssh ${CLIENTS[0]} $PERFTEST $OPTS --control -b ${BROKERS[0]} -p${PORTS[0]} + +for pid in $PIDS; do + wait $pid || echo "ERROR: client process $pid failed" +done + +`dirname $0`/cluster_check + + diff --git a/qpid/cpp/src/tests/cluster_tests.fail b/qpid/cpp/src/tests/cluster_tests.fail new file mode 100644 index 0000000000..b28b04f643 --- /dev/null +++ b/qpid/cpp/src/tests/cluster_tests.fail @@ -0,0 +1,3 @@ + + + diff --git a/qpid/cpp/src/tests/cluster_tests.py b/qpid/cpp/src/tests/cluster_tests.py new file mode 100755 index 0000000000..593791297a --- /dev/null +++ b/qpid/cpp/src/tests/cluster_tests.py @@ -0,0 +1,1057 @@ +#!/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, cluster_test_logs +from qpid import datatypes, messaging +from brokertest import * +from qpid.harness import Skipped +from qpid.messaging import Message, Empty, Disposition, REJECTED +from threading import Thread, Lock, Condition +from logging import getLogger +from itertools import chain +from tempfile import NamedTemporaryFile + +log = getLogger("qpid.cluster_tests") + +# Note: brokers that shut themselves down due to critical error during +# normal operation will still have an exit code of 0. Brokers that +# shut down because of an error found during initialize will exit with +# a non-0 code. Hence the apparently inconsistent use of EXPECT_EXIT_OK +# and EXPECT_EXIT_FAIL in some of the tests below. + +# TODO aconway 2010-03-11: resolve this - ideally any exit due to an error +# should give non-0 exit status. + +# Import scripts as modules +qpid_cluster=import_script(checkenv("QPID_CLUSTER_EXEC")) + +def readfile(filename): + """Returns te content of file named filename as a string""" + f = file(filename) + try: return f.read() + finally: f.close() + +class ShortTests(BrokerTest): + """Short cluster functionality tests.""" + + def test_message_replication(self): + """Test basic cluster message replication.""" + # Start a cluster, send some messages to member 0. + cluster = self.cluster(2) + s0 = cluster[0].connect().session() + s0.sender("q; {create:always}").send(Message("x")) + s0.sender("q; {create:always}").send(Message("y")) + s0.connection.close() + + # Verify messages available on member 1. + s1 = cluster[1].connect().session() + m = s1.receiver("q", capacity=1).fetch(timeout=1) + s1.acknowledge() + self.assertEqual("x", m.content) + s1.connection.close() + + # Start member 2 and verify messages available. + s2 = cluster.start().connect().session() + m = s2.receiver("q", capacity=1).fetch(timeout=1) + s2.acknowledge() + self.assertEqual("y", m.content) + s2.connection.close() + + def test_store_direct_update_match(self): + """Verify that brokers stores an identical message whether they receive it + direct from clients or during an update, no header or other differences""" + cluster = self.cluster(0, args=["--load-module", self.test_store_lib]) + cluster.start(args=["--test-store-dump", "direct.dump"]) + # Try messages with various headers + cluster[0].send_message("q", Message(durable=True, content="foobar", + subject="subject", + reply_to="reply_to", + properties={"n":10})) + # Try messages of different sizes + for size in range(0,10000,100): + cluster[0].send_message("q", Message(content="x"*size, durable=True)) + # Try sending via named exchange + c = cluster[0].connect_old() + s = c.session(str(qpid.datatypes.uuid4())) + s.exchange_bind(exchange="amq.direct", binding_key="foo", queue="q") + props = s.delivery_properties(routing_key="foo", delivery_mode=2) + s.message_transfer( + destination="amq.direct", + message=qpid.datatypes.Message(props, "content")) + + # Now update a new member and compare their dumps. + cluster.start(args=["--test-store-dump", "updatee.dump"]) + assert readfile("direct.dump") == readfile("updatee.dump") + os.remove("direct.dump") + os.remove("updatee.dump") + + def test_sasl(self): + """Test SASL authentication and encryption in a cluster""" + sasl_config=os.path.join(self.rootdir, "sasl_config") + acl=os.path.join(os.getcwd(), "policy.acl") + aclf=file(acl,"w") + aclf.write(""" +acl deny zag@QPID create queue +acl allow all all +""") + aclf.close() + cluster = self.cluster(2, args=["--auth", "yes", + "--sasl-config", sasl_config, + "--load-module", os.getenv("ACL_LIB"), + "--acl-file", acl]) + + # Valid user/password, ensure queue is created. + c = cluster[0].connect(username="zig", password="zig") + c.session().sender("ziggy;{create:always}") + c.close() + c = cluster[1].connect(username="zig", password="zig") + c.session().receiver("ziggy;{assert:always}") + c.close() + for b in cluster: b.ready() # Make sure all brokers still running. + + # Valid user, bad password + try: + cluster[0].connect(username="zig", password="foo").close() + self.fail("Expected exception") + except messaging.exceptions.ConnectionError: pass + for b in cluster: b.ready() # Make sure all brokers still running. + + # Bad user ID + try: + cluster[0].connect(username="foo", password="bar").close() + self.fail("Expected exception") + except messaging.exceptions.ConnectionError: pass + for b in cluster: b.ready() # Make sure all brokers still running. + + # Action disallowed by ACL + c = cluster[0].connect(username="zag", password="zag") + try: + s = c.session() + s.sender("zaggy;{create:always}") + s.close() + self.fail("Expected exception") + except messaging.exceptions.UnauthorizedAccess: pass + # make sure the queue was not created at the other node. + c = cluster[0].connect(username="zag", password="zag") + try: + s = c.session() + s.sender("zaggy;{assert:always}") + s.close() + self.fail("Expected exception") + except messaging.exceptions.NotFound: pass + + def test_user_id_update(self): + """Ensure that user-id of an open session is updated to new cluster members""" + sasl_config=os.path.join(self.rootdir, "sasl_config") + cluster = self.cluster(1, args=["--auth", "yes", "--sasl-config", sasl_config,]) + c = cluster[0].connect(username="zig", password="zig") + s = c.session().sender("q;{create:always}") + s.send(Message("x", user_id="zig")) # Message sent before start new broker + cluster.start() + s.send(Message("y", user_id="zig")) # Messsage sent after start of new broker + # Verify brokers are healthy and messages are on the queue. + self.assertEqual("x", cluster[0].get_message("q").content) + self.assertEqual("y", cluster[1].get_message("q").content) + + def test_link_events(self): + """Regression test for https://bugzilla.redhat.com/show_bug.cgi?id=611543""" + args = ["--mgmt-pub-interval", 1] # Publish management information every second. + broker1 = self.cluster(1, args)[0] + broker2 = self.cluster(1, args)[0] + qp = self.popen(["qpid-printevents", broker1.host_port()], EXPECT_RUNNING) + qr = self.popen(["qpid-route", "route", "add", + broker1.host_port(), broker2.host_port(), + "amq.fanout", "key" + ], EXPECT_EXIT_OK) + # Look for link event in printevents output. + retry(lambda: find_in_file("brokerLinkUp", qp.outfile("out"))) + broker1.ready() + broker2.ready() + + def test_queue_cleaner(self): + """ Regression test to ensure that cleanup of expired messages works correctly """ + cluster = self.cluster(2, args=["--queue-purge-interval", 3]) + + s0 = cluster[0].connect().session() + sender = s0.sender("my-lvq; {create: always, node:{x-declare:{arguments:{'qpid.last_value_queue':1}}}}") + #send 10 messages that will all expire and be cleaned up + for i in range(1, 10): + msg = Message("message-%s" % i) + msg.properties["qpid.LVQ_key"] = "a" + msg.ttl = 0.1 + sender.send(msg) + #wait for queue cleaner to run + time.sleep(3) + + #test all is ok by sending and receiving a message + msg = Message("non-expiring") + msg.properties["qpid.LVQ_key"] = "b" + sender.send(msg) + s0.connection.close() + s1 = cluster[1].connect().session() + m = s1.receiver("my-lvq", capacity=1).fetch(timeout=1) + s1.acknowledge() + self.assertEqual("non-expiring", m.content) + s1.connection.close() + + for b in cluster: b.ready() # Make sure all brokers still running. + + + def test_amqfailover_visible(self): + """Verify that the amq.failover exchange can be seen by + QMF-based tools - regression test for BZ615300.""" + broker1 = self.cluster(1)[0] + broker2 = self.cluster(1)[0] + qs = subprocess.Popen(["qpid-stat", "-e", broker1.host_port()], stdout=subprocess.PIPE) + out = qs.communicate()[0] + assert out.find("amq.failover") > 0 + + def evaluate_address(self, session, address): + """Create a receiver just to evaluate an address for its side effects""" + r = session.receiver(address) + r.close() + + def test_expire_fanout(self): + """Regression test for QPID-2874: Clustered broker crashes in assertion in + cluster/ExpiryPolicy.cpp. + Caused by a fan-out message being updated as separate messages""" + cluster = self.cluster(1) + session0 = cluster[0].connect().session() + # Create 2 queues bound to fanout exchange. + self.evaluate_address(session0, "q1;{create:always,node:{x-bindings:[{exchange:'amq.fanout',queue:q1}]}}") + self.evaluate_address(session0, "q2;{create:always,node:{x-bindings:[{exchange:'amq.fanout',queue:q2}]}}") + queues = ["q1", "q2"] + # Send a fanout message with a long timeout + s = session0.sender("amq.fanout") + s.send(Message("foo", ttl=100), sync=False) + # Start a new member, check the messages + cluster.start() + session1 = cluster[1].connect().session() + for q in queues: self.assert_browse(session1, "q1", ["foo"]) + + def test_route_update(self): + """Regression test for https://issues.apache.org/jira/browse/QPID-2982 + Links and bridges associated with routes were not replicated on update. + This meant extra management objects and caused an exit if a management + client was attached. + """ + args=["--mgmt-pub-interval=1","--log-enable=trace+:management"] + cluster0 = self.cluster(1, args=args) + cluster1 = self.cluster(1, args=args) + assert 0 == subprocess.call( + ["qpid-route", "route", "add", cluster0[0].host_port(), + cluster1[0].host_port(), "dummy-exchange", "dummy-key", "-d"]) + cluster0.start() + + # Wait for qpid-tool:list on cluster0[0] to generate expected output. + pattern = re.compile("org.apache.qpid.broker.*link") + qpid_tool = subprocess.Popen(["qpid-tool", cluster0[0].host_port()], + stdin=subprocess.PIPE, stdout=subprocess.PIPE) + class Scanner(Thread): + def __init__(self): self.found = False; Thread.__init__(self) + def run(self): + for l in qpid_tool.stdout: + if pattern.search(l): self.found = True; return + scanner = Scanner() + scanner.start() + start = time.time() + try: + # Wait up to 5 second timeout for scanner to find expected output + while not scanner.found and time.time() < start + 5: + qpid_tool.stdin.write("list\n") # Ask qpid-tool to list + for b in cluster0: b.ready() # Raise if any brokers are down + finally: + qpid_tool.stdin.write("quit\n") + qpid_tool.wait() + scanner.join() + assert scanner.found + # Regression test for https://issues.apache.org/jira/browse/QPID-3235 + # Inconsistent stats when changing elder. + + # Force a change of elder + cluster0.start() + cluster0[0].kill() + time.sleep(2) # Allow a management interval to pass. + # Verify logs are consistent + cluster_test_logs.verify_logs() + + def test_redelivered(self): + """Verify that redelivered flag is set correctly on replayed messages""" + cluster = self.cluster(2, expect=EXPECT_EXIT_FAIL) + url = "amqp:tcp:%s,tcp:%s" % (cluster[0].host_port(), cluster[1].host_port()) + queue = "my-queue" + cluster[0].declare_queue(queue) + self.sender = self.popen( + ["qpid-send", + "--broker", url, + "--address", queue, + "--sequence=true", + "--send-eos=1", + "--messages=100000", + "--connection-options={reconnect:true}" + ]) + self.receiver = self.popen( + ["qpid-receive", + "--broker", url, + "--address", queue, + "--ignore-duplicates", + "--check-redelivered", + "--connection-options={reconnect:true}", + "--forever" + ]) + time.sleep(1)#give sender enough time to have some messages to replay + cluster[0].kill() + self.sender.wait() + self.receiver.wait() + cluster[1].kill() + + class BlockedSend(Thread): + """Send a message, send is expected to block. + Verify that it does block (for a given timeout), then allow + waiting till it unblocks when it is expected to do so.""" + def __init__(self, sender, msg): + self.sender, self.msg = sender, msg + self.blocked = True + self.condition = Condition() + self.timeout = 0.1 # Time to wait for expected results. + Thread.__init__(self) + def run(self): + try: + self.sender.send(self.msg, sync=True) + self.condition.acquire() + try: + self.blocked = False + self.condition.notify() + finally: self.condition.release() + except Exception,e: print "BlockedSend exception: %s"%e + def start(self): + Thread.start(self) + time.sleep(self.timeout) + assert self.blocked # Expected to block + def assert_blocked(self): assert self.blocked + def wait(self): # Now expecting to unblock + self.condition.acquire() + try: + while self.blocked: + self.condition.wait(self.timeout) + if self.blocked: raise Exception("Timed out waiting for send to unblock") + finally: self.condition.release() + self.join() + + def queue_flowlimit_test(self, brokers): + """Verify that the queue's flowlimit configuration and state are + correctly replicated. + The brokers argument allows this test to run on single broker, + cluster of 2 pre-startd brokers or cluster where second broker + starts after queue is in flow control. + """ + # configure a queue with a specific flow limit on first broker + ssn0 = brokers.first().connect().session() + s0 = ssn0.sender("flq; {create:always, node:{type:queue, x-declare:{arguments:{'qpid.flow_stop_count':5, 'qpid.flow_resume_count':3}}}}") + brokers.first().startQmf() + q1 = [q for q in brokers.first().qmf_session.getObjects(_class="queue") if q.name == "flq"][0] + oid = q1.getObjectId() + self.assertEqual(q1.name, "flq") + self.assertEqual(q1.arguments, {u'qpid.flow_stop_count': 5L, u'qpid.flow_resume_count': 3L}) + assert not q1.flowStopped + self.assertEqual(q1.flowStoppedCount, 0) + + # fill the queue on one broker until flow control is active + for x in range(5): s0.send(Message(str(x))) + sender = ShortTests.BlockedSend(s0, Message(str(6))) + sender.start() # Tests that sender does block + # Verify the broker queue goes into a flowStopped state + deadline = time.time() + 1 + while not q1.flowStopped and time.time() < deadline: q1.update() + assert q1.flowStopped + self.assertEqual(q1.flowStoppedCount, 1) + sender.assert_blocked() # Still blocked + + # Now verify the both brokers in cluster have same configuration + brokers.second().startQmf() + qs = brokers.second().qmf_session.getObjects(_objectId=oid) + self.assertEqual(len(qs), 1) + q2 = qs[0] + self.assertEqual(q2.name, "flq") + self.assertEqual(q2.arguments, {u'qpid.flow_stop_count': 5L, u'qpid.flow_resume_count': 3L}) + assert q2.flowStopped + self.assertEqual(q2.flowStoppedCount, 1) + + # now drain the queue using a session to the other broker + ssn1 = brokers.second().connect().session() + r1 = ssn1.receiver("flq", capacity=6) + for x in range(4): + r1.fetch(timeout=0) + ssn1.acknowledge() + sender.wait() # Verify no longer blocked. + + # and re-verify state of queue on both brokers + q1.update() + assert not q1.flowStopped + q2.update() + assert not q2.flowStopped + + ssn0.connection.close() + ssn1.connection.close() + cluster_test_logs.verify_logs() + + def test_queue_flowlimit(self): + """Test flow limits on a standalone broker""" + broker = self.broker() + class Brokers: + def first(self): return broker + def second(self): return broker + self.queue_flowlimit_test(Brokers()) + + def test_queue_flowlimit_cluster(self): + cluster = self.cluster(2) + class Brokers: + def first(self): return cluster[0] + def second(self): return cluster[1] + self.queue_flowlimit_test(Brokers()) + + def test_queue_flowlimit_cluster_join(self): + cluster = self.cluster(1) + class Brokers: + def first(self): return cluster[0] + def second(self): + if len(cluster) == 1: cluster.start() + return cluster[1] + self.queue_flowlimit_test(Brokers()) + + def test_queue_flowlimit_replicate(self): + """ Verify that a queue which is in flow control BUT has drained BELOW + the flow control 'stop' threshold, is correctly replicated when a new + broker is added to the cluster. + """ + + class AsyncSender(Thread): + """Send a fixed number of msgs from a sender in a separate thread + so it may block without blocking the test. + """ + def __init__(self, broker, address, count=1, size=4): + Thread.__init__(self) + self.daemon = True + self.broker = broker + self.queue = address + self.count = count + self.size = size + self.done = False + + def run(self): + self.sender = subprocess.Popen(["qpid-send", + "--capacity=1", + "--content-size=%s" % self.size, + "--messages=%s" % self.count, + "--failover-updates", + "--connection-options={reconnect:true}", + "--address=%s" % self.queue, + "--broker=%s" % self.broker.host_port()]) + self.sender.wait() + self.done = True + + cluster = self.cluster(2) + # create a queue with rather draconian flow control settings + ssn0 = cluster[0].connect().session() + s0 = ssn0.sender("flq; {create:always, node:{type:queue, x-declare:{arguments:{'qpid.flow_stop_count':100, 'qpid.flow_resume_count':20}}}}") + + # fire off the sending thread to broker[0], and wait until the queue + # hits flow control on broker[1] + sender = AsyncSender(cluster[0], "flq", count=110); + sender.start(); + + cluster[1].startQmf() + q_obj = [q for q in cluster[1].qmf_session.getObjects(_class="queue") if q.name == "flq"][0] + deadline = time.time() + 10 + while not q_obj.flowStopped and time.time() < deadline: + q_obj.update() + assert q_obj.flowStopped + assert not sender.done + assert q_obj.msgDepth < 110 + + # Now drain enough messages on broker[1] to drop below the flow stop + # threshold, but not relieve flow control... + receiver = subprocess.Popen(["qpid-receive", + "--messages=15", + "--timeout=1", + "--print-content=no", + "--failover-updates", + "--connection-options={reconnect:true}", + "--ack-frequency=1", + "--address=flq", + "--broker=%s" % cluster[1].host_port()]) + receiver.wait() + q_obj.update() + assert q_obj.flowStopped + assert not sender.done + current_depth = q_obj.msgDepth + + # add a new broker to the cluster, and verify that the queue is in flow + # control on that broker + cluster.start() + cluster[2].startQmf() + q_obj = [q for q in cluster[2].qmf_session.getObjects(_class="queue") if q.name == "flq"][0] + assert q_obj.flowStopped + assert q_obj.msgDepth == current_depth + + # now drain the queue on broker[2], and verify that the sender becomes + # unblocked + receiver = subprocess.Popen(["qpid-receive", + "--messages=95", + "--timeout=1", + "--print-content=no", + "--failover-updates", + "--connection-options={reconnect:true}", + "--ack-frequency=1", + "--address=flq", + "--broker=%s" % cluster[2].host_port()]) + receiver.wait() + q_obj.update() + assert not q_obj.flowStopped + assert q_obj.msgDepth == 0 + + # verify that the sender has become unblocked + sender.join(timeout=5) + assert not sender.isAlive() + assert sender.done + + def test_blocked_queue_delete(self): + """Verify that producers which are blocked on a queue due to flow + control are unblocked when that queue is deleted. + """ + + cluster = self.cluster(2) + cluster[0].startQmf() + cluster[1].startQmf() + + # configure a queue with a specific flow limit on first broker + ssn0 = cluster[0].connect().session() + s0 = ssn0.sender("flq; {create:always, node:{type:queue, x-declare:{arguments:{'qpid.flow_stop_count':5, 'qpid.flow_resume_count':3}}}}") + q1 = [q for q in cluster[0].qmf_session.getObjects(_class="queue") if q.name == "flq"][0] + oid = q1.getObjectId() + self.assertEqual(q1.name, "flq") + self.assertEqual(q1.arguments, {u'qpid.flow_stop_count': 5L, u'qpid.flow_resume_count': 3L}) + assert not q1.flowStopped + self.assertEqual(q1.flowStoppedCount, 0) + + # fill the queue on one broker until flow control is active + for x in range(5): s0.send(Message(str(x))) + sender = ShortTests.BlockedSend(s0, Message(str(6))) + sender.start() # Tests that sender does block + # Verify the broker queue goes into a flowStopped state + deadline = time.time() + 1 + while not q1.flowStopped and time.time() < deadline: q1.update() + assert q1.flowStopped + self.assertEqual(q1.flowStoppedCount, 1) + sender.assert_blocked() # Still blocked + + # Now verify the both brokers in cluster have same configuration + qs = cluster[1].qmf_session.getObjects(_objectId=oid) + self.assertEqual(len(qs), 1) + q2 = qs[0] + self.assertEqual(q2.name, "flq") + self.assertEqual(q2.arguments, {u'qpid.flow_stop_count': 5L, u'qpid.flow_resume_count': 3L}) + assert q2.flowStopped + self.assertEqual(q2.flowStoppedCount, 1) + + # now delete the blocked queue from other broker + ssn1 = cluster[1].connect().session() + self.evaluate_address(ssn1, "flq;{delete:always}") + sender.wait() # Verify no longer blocked. + + ssn0.connection.close() + ssn1.connection.close() + cluster_test_logs.verify_logs() + + + def test_alternate_exchange_update(self): + """Verify that alternate-exchange on exchanges and queues is propagated to new members of a cluster. """ + cluster = self.cluster(1) + s0 = cluster[0].connect().session() + # create alt queue bound to amq.fanout exchange, will be destination for alternate exchanges + self.evaluate_address(s0, "alt;{create:always,node:{x-bindings:[{exchange:'amq.fanout',queue:alt}]}}") + # create direct exchange ex with alternate-exchange amq.fanout and no queues bound + self.evaluate_address(s0, "ex;{create:always,node:{type:topic, x-declare:{type:'direct', alternate-exchange:'amq.fanout'}}}") + # create queue q with alternate-exchange amq.fanout + self.evaluate_address(s0, "q;{create:always,node:{type:queue, x-declare:{alternate-exchange:'amq.fanout'}}}") + + def verify(broker): + s = broker.connect().session() + # Verify unmatched message goes to ex's alternate. + s.sender("ex").send("foo") + self.assertEqual("foo", s.receiver("alt").fetch(timeout=0).content) + # 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, Disposition(REJECTED)) # Reject the message + self.assertEqual("bar", s.receiver("alt").fetch(timeout=0).content) + + verify(cluster[0]) + cluster.start() + verify(cluster[1]) + + def test_binding_order(self): + """Regression test for binding order inconsistency in cluster""" + cluster = self.cluster(1) + c0 = cluster[0].connect() + s0 = c0.session() + # Declare multiple queues bound to same key on amq.topic + def declare(q,max=0): + if max: declare = 'x-declare:{arguments:{"qpid.max_count":%d, "qpid.flow_stop_count":0}}'%max + else: declare = 'x-declare:{}' + bind='x-bindings:[{queue:%s,key:key,exchange:"amq.topic"}]'%(q) + s0.sender("%s;{create:always,node:{%s,%s}}" % (q,declare,bind)) + declare('d',max=4) # Only one with a limit + for q in ['c', 'b','a']: declare(q) + # Add a cluster member, send enough messages to exceed the max count + cluster.start() + try: + s = s0.sender('amq.topic/key') + for m in xrange(1,6): s.send(Message(str(m))) + self.fail("Expected capacity exceeded exception") + except messaging.exceptions.TargetCapacityExceeded: pass + c1 = cluster[1].connect() + s1 = c1.session() + s0 = c0.session() # Old session s0 is broken by exception. + # Verify queue contents are consistent. + for q in ['a','b','c','d']: + self.assertEqual(self.browse(s0, q), self.browse(s1, q)) + # Verify queue contents are "best effort" + for q in ['a','b','c']: self.assert_browse(s1,q,[str(n) for n in xrange(1,6)]) + self.assert_browse(s1,'d',[str(n) for n in xrange(1,5)]) + + def test_deleted_exchange(self): + """QPID-3215: cached exchange reference can cause cluster inconsistencies + if exchange is deleted/recreated + Verify stand-alone case + """ + cluster = self.cluster() + # Verify we do not route message via an exchange that has been destroyed. + cluster.start() + s0 = cluster[0].connect().session() + self.evaluate_address(s0, "ex;{create:always,node:{type:topic}}") + self.evaluate_address(s0, "q;{create:always,node:{x-bindings:[{exchange:'ex',queue:q,key:foo}]}}") + send0 = s0.sender("ex/foo") + send0.send("foo") + self.assert_browse(s0, "q", ["foo"]) + self.evaluate_address(s0, "ex;{delete:always}") + try: + send0.send("bar") # Should fail, exchange is deleted. + self.fail("Expected not-found exception") + except qpid.messaging.NotFound: pass + self.assert_browse(cluster[0].connect().session(), "q", ["foo"]) + + def test_deleted_exchange_inconsistent(self): + """QPID-3215: cached exchange reference can cause cluster inconsistencies + if exchange is deleted/recreated + + Verify cluster inconsistency. + """ + cluster = self.cluster() + cluster.start() + s0 = cluster[0].connect().session() + self.evaluate_address(s0, "ex;{create:always,node:{type:topic}}") + self.evaluate_address(s0, "q;{create:always,node:{x-bindings:[{exchange:'ex',queue:q,key:foo}]}}") + send0 = s0.sender("ex/foo") + send0.send("foo") + self.assert_browse(s0, "q", ["foo"]) + + cluster.start() + s1 = cluster[1].connect().session() + self.evaluate_address(s0, "ex;{delete:always}") + try: + send0.send("bar") + self.fail("Expected not-found exception") + except qpid.messaging.NotFound: pass + + self.assert_browse(s1, "q", ["foo"]) + + +class LongTests(BrokerTest): + """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(self): + """Test fail-over during continuous send-receive with errors""" + + # Original cluster will all be killed so expect exit with failure + cluster = self.cluster(3, expect=EXPECT_EXIT_FAIL) + for b in cluster: ErrorGenerator(b) + + # Start sender and receiver threads + cluster[0].declare_queue("test-queue") + sender = NumberedSender(cluster[1], 1000) # Max queue depth + receiver = NumberedReceiver(cluster[2], sender) + receiver.start() + sender.start() + + # Kill original brokers, start new ones for the duration. + endtime = time.time() + self.duration() + i = 0 + while time.time() < endtime: + cluster[i].kill() + i += 1 + b = cluster.start(expect=EXPECT_EXIT_FAIL) + ErrorGenerator(b) + time.sleep(5) + sender.stop() + receiver.stop() + for i in range(i, len(cluster)): cluster[i].kill() + + def test_management(self, args=[]): + """ + Stress test: Run management clients and other clients concurrently + while killing and restarting brokers. + """ + + class ClientLoop(StoppableThread): + """Run a client executable in a loop.""" + def __init__(self, broker, cmd): + StoppableThread.__init__(self) + self.broker=broker + self.cmd = cmd # Client command. + self.lock = Lock() + self.process = None # Client process. + self.start() + + def run(self): + try: + while True: + self.lock.acquire() + try: + if self.stopped: break + self.process = self.broker.test.popen( + self.cmd, expect=EXPECT_UNKNOWN) + finally: + self.lock.release() + try: + exit = self.process.wait() + except OSError, e: + # Process may already have been killed by self.stop() + break + except Exception, e: + self.process.unexpected( + "client of %s: %s"%(self.broker.name, e)) + self.lock.acquire() + try: + if self.stopped: break + if exit != 0: + self.process.unexpected( + "client of %s exit code %s"%(self.broker.name, exit)) + finally: + self.lock.release() + except Exception, e: + self.error = RethrownException("Error in ClientLoop.run") + + def stop(self): + """Stop the running client and wait for it to exit""" + self.lock.acquire() + try: + if self.stopped: return + self.stopped = True + if self.process: + try: self.process.kill() # Kill the client. + except OSError: pass # The client might not be running. + finally: self.lock.release() + StoppableThread.stop(self) + + # body of test_management() + + args += ["--mgmt-pub-interval", 1] + args += ["--log-enable=trace+:management"] + # Use store if present. + if BrokerTest.store_lib: args +=["--load-module", BrokerTest.store_lib] + cluster = self.cluster(3, args) + + clients = [] # Per-broker list of clients that only connect to one broker. + mclients = [] # Management clients that connect to every broker in the cluster. + + def start_clients(broker): + """Start ordinary clients for a broker.""" + cmds=[ + ["qpid-tool", "localhost:%s"%(broker.port())], + ["qpid-perftest", "--count=5000", "--durable=yes", + "--base-name", str(qpid.datatypes.uuid4()), "--port", broker.port()], + ["qpid-txtest", "--queue-base-name", "tx-%s"%str(qpid.datatypes.uuid4()), + "--port", broker.port()], + ["qpid-queue-stats", "-a", "localhost:%s" %(broker.port())], + ["testagent", "localhost", str(broker.port())] ] + clients.append([ClientLoop(broker, cmd) for cmd in cmds]) + + def start_mclients(broker): + """Start management clients that make multiple connections.""" + cmd = ["qpid-stat", "-b", "localhost:%s" %(broker.port())] + mclients.append(ClientLoop(broker, cmd)) + + endtime = time.time() + self.duration() + # For long duration, first run is a quarter of the duration. + runtime = max(5, self.duration() / 4.0) + alive = 0 # First live cluster member + for i in range(len(cluster)): start_clients(cluster[i]) + start_mclients(cluster[alive]) + + while time.time() < endtime: + time.sleep(runtime) + runtime = 5 # Remaining runs 5 seconds, frequent broker kills + for b in cluster[alive:]: b.ready() # Check if a broker crashed. + # Kill the first broker, expect the clients to fail. + b = cluster[alive] + b.expect = EXPECT_EXIT_FAIL + b.kill() + # Stop the brokers clients and all the mclients. + for c in clients[alive] + mclients: + try: c.stop() + except: pass # Ignore expected errors due to broker shutdown. + clients[alive] = [] + mclients = [] + # Start another broker and clients + alive += 1 + cluster.start() + start_clients(cluster[-1]) + start_mclients(cluster[alive]) + for c in chain(mclients, *clients): + c.stop() + # Verify that logs are consistent + cluster_test_logs.verify_logs() + + def test_management_qmf2(self): + self.test_management(args=["--mgmt-qmf2=yes"]) + + def test_connect_consistent(self): + args=["--mgmt-pub-interval=1","--log-enable=trace+:management"] + cluster = self.cluster(2, args=args) + end = time.time() + self.duration() + while (time.time() < end): # Get a management interval + for i in xrange(1000): cluster[0].connect().close() + cluster_test_logs.verify_logs() + + def test_flowlimit_failover(self): + """Test fail-over during continuous send-receive with flow control + active. + """ + + # Original cluster will all be killed so expect exit with failure + cluster = self.cluster(3, expect=EXPECT_EXIT_FAIL) + #for b in cluster: ErrorGenerator(b) + + # create a queue with rather draconian flow control settings + ssn0 = cluster[0].connect().session() + s0 = ssn0.sender("test-queue; {create:always, node:{type:queue, x-declare:{arguments:{'qpid.flow_stop_count':2000, 'qpid.flow_resume_count':100}}}}") + + receiver = NumberedReceiver(cluster[2]) + receiver.start() + senders = [NumberedSender(cluster[i]) for i in range(1,3)] + for s in senders: + s.start() + + # Kill original brokers, start new ones for the duration. + endtime = time.time() + self.duration(); + i = 0 + while time.time() < endtime: + cluster[i].kill() + i += 1 + b = cluster.start(expect=EXPECT_EXIT_FAIL) + #ErrorGenerator(b) + time.sleep(5) + #b = cluster[0] + #b.startQmf() + for s in senders: + s.stop() + receiver.stop() + for i in range(i, len(cluster)): cluster[i].kill() + + +class StoreTests(BrokerTest): + """ + Cluster tests that can only be run if there is a store available. + """ + def args(self): + assert BrokerTest.store_lib + return ["--load-module", BrokerTest.store_lib] + + def test_store_loaded(self): + """Ensure we are indeed loading a working store""" + broker = self.broker(self.args(), name="recoverme", expect=EXPECT_EXIT_FAIL) + m = Message("x", durable=True) + broker.send_message("q", m) + broker.kill() + broker = self.broker(self.args(), name="recoverme") + self.assertEqual("x", broker.get_message("q").content) + + def test_kill_restart(self): + """Verify we can kill/resetart a broker with store in a cluster""" + cluster = self.cluster(1, self.args()) + cluster.start("restartme", expect=EXPECT_EXIT_FAIL).kill() + + # Send a message, retrieve from the restarted broker + cluster[0].send_message("q", "x") + m = cluster.start("restartme").get_message("q") + self.assertEqual("x", m.content) + + def stop_cluster(self,broker): + """Clean shut-down of a cluster""" + self.assertEqual(0, qpid_cluster.main( + ["-kf", broker.host_port()])) + + def test_persistent_restart(self): + """Verify persistent cluster shutdown/restart scenarios""" + cluster = self.cluster(0, args=self.args() + ["--cluster-size=3"]) + a = cluster.start("a", expect=EXPECT_EXIT_OK, wait=False) + b = cluster.start("b", expect=EXPECT_EXIT_OK, wait=False) + c = cluster.start("c", expect=EXPECT_EXIT_FAIL, wait=True) + a.send_message("q", Message("1", durable=True)) + # Kill & restart one member. + c.kill() + self.assertEqual(a.get_message("q").content, "1") + a.send_message("q", Message("2", durable=True)) + c = cluster.start("c", expect=EXPECT_EXIT_OK) + self.assertEqual(c.get_message("q").content, "2") + # Shut down the entire cluster cleanly and bring it back up + a.send_message("q", Message("3", durable=True)) + self.stop_cluster(a) + a = cluster.start("a", wait=False) + b = cluster.start("b", wait=False) + c = cluster.start("c", wait=True) + self.assertEqual(a.get_message("q").content, "3") + + def test_persistent_partial_failure(self): + # Kill 2 members, shut down the last cleanly then restart + # Ensure we use the clean database + cluster = self.cluster(0, args=self.args() + ["--cluster-size=3"]) + a = cluster.start("a", expect=EXPECT_EXIT_FAIL, wait=False) + b = cluster.start("b", expect=EXPECT_EXIT_FAIL, wait=False) + c = cluster.start("c", expect=EXPECT_EXIT_OK, wait=True) + a.send_message("q", Message("4", durable=True)) + a.kill() + b.kill() + self.assertEqual(c.get_message("q").content, "4") + c.send_message("q", Message("clean", durable=True)) + self.stop_cluster(c) + a = cluster.start("a", wait=False) + b = cluster.start("b", wait=False) + c = cluster.start("c", wait=True) + self.assertEqual(a.get_message("q").content, "clean") + + def test_wrong_cluster_id(self): + # Start a cluster1 broker, then try to restart in cluster2 + cluster1 = self.cluster(0, args=self.args()) + a = cluster1.start("a", expect=EXPECT_EXIT_OK) + a.terminate() + cluster2 = self.cluster(1, args=self.args()) + try: + a = cluster2.start("a", expect=EXPECT_EXIT_FAIL) + a.ready() + self.fail("Expected exception") + except: pass + + def test_wrong_shutdown_id(self): + # Start 2 members and shut down. + cluster = self.cluster(0, args=self.args()+["--cluster-size=2"]) + a = cluster.start("a", expect=EXPECT_EXIT_OK, wait=False) + b = cluster.start("b", expect=EXPECT_EXIT_OK, wait=False) + self.stop_cluster(a) + self.assertEqual(a.wait(), 0) + self.assertEqual(b.wait(), 0) + + # Restart with a different member and shut down. + a = cluster.start("a", expect=EXPECT_EXIT_OK, wait=False) + c = cluster.start("c", expect=EXPECT_EXIT_OK, wait=False) + self.stop_cluster(a) + self.assertEqual(a.wait(), 0) + self.assertEqual(c.wait(), 0) + # Mix members from both shutdown events, they should fail + # TODO aconway 2010-03-11: can't predict the exit status of these + # as it depends on the order of delivery of initial-status messages. + # See comment at top of this file. + a = cluster.start("a", expect=EXPECT_UNKNOWN, wait=False) + b = cluster.start("b", expect=EXPECT_UNKNOWN, wait=False) + self.assertRaises(Exception, lambda: a.ready()) + self.assertRaises(Exception, lambda: b.ready()) + + def test_solo_store_clean(self): + # A single node cluster should always leave a clean store. + cluster = self.cluster(0, self.args()) + a = cluster.start("a", expect=EXPECT_EXIT_FAIL) + a.send_message("q", Message("x", durable=True)) + a.kill() + a = cluster.start("a") + self.assertEqual(a.get_message("q").content, "x") + + def test_last_store_clean(self): + # Verify that only the last node in a cluster to shut down has + # a clean store. Start with cluster of 3, reduce to 1 then + # increase again to ensure that a node that was once alone but + # finally did not finish as the last node does not get a clean + # store. + cluster = self.cluster(0, self.args()) + a = cluster.start("a", expect=EXPECT_EXIT_FAIL) + self.assertEqual(a.store_state(), "clean") + b = cluster.start("b", expect=EXPECT_EXIT_FAIL) + c = cluster.start("c", expect=EXPECT_EXIT_FAIL) + self.assertEqual(b.store_state(), "dirty") + self.assertEqual(c.store_state(), "dirty") + retry(lambda: a.store_state() == "dirty") + + a.send_message("q", Message("x", durable=True)) + a.kill() + b.kill() # c is last man, will mark store clean + retry(lambda: c.store_state() == "clean") + a = cluster.start("a", expect=EXPECT_EXIT_FAIL) # c no longer last man + retry(lambda: c.store_state() == "dirty") + c.kill() # a is now last man + retry(lambda: a.store_state() == "clean") + a.kill() + self.assertEqual(a.store_state(), "clean") + self.assertEqual(b.store_state(), "dirty") + self.assertEqual(c.store_state(), "dirty") + + def test_restart_clean(self): + """Verify that we can re-start brokers one by one in a + persistent cluster after a clean oshutdown""" + cluster = self.cluster(0, self.args()) + a = cluster.start("a", expect=EXPECT_EXIT_OK) + b = cluster.start("b", expect=EXPECT_EXIT_OK) + c = cluster.start("c", expect=EXPECT_EXIT_OK) + a.send_message("q", Message("x", durable=True)) + self.stop_cluster(a) + a = cluster.start("a") + b = cluster.start("b") + c = cluster.start("c") + self.assertEqual(c.get_message("q").content, "x") + + def test_join_sub_size(self): + """Verify that after starting a cluster with cluster-size=N, + we can join new members even if size < N-1""" + cluster = self.cluster(0, self.args()+["--cluster-size=3"]) + a = cluster.start("a", wait=False, expect=EXPECT_EXIT_FAIL) + b = cluster.start("b", wait=False, expect=EXPECT_EXIT_FAIL) + c = cluster.start("c") + a.send_message("q", Message("x", durable=True)) + a.send_message("q", Message("y", durable=True)) + a.kill() + b.kill() + a = cluster.start("a") + self.assertEqual(c.get_message("q").content, "x") + b = cluster.start("b") + self.assertEqual(c.get_message("q").content, "y") diff --git a/qpid/cpp/src/tests/clustered_replication_test b/qpid/cpp/src/tests/clustered_replication_test new file mode 100755 index 0000000000..d6c72d9d1b --- /dev/null +++ b/qpid/cpp/src/tests/clustered_replication_test @@ -0,0 +1,110 @@ +#!/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. +# + +# Test reliability of the replication feature in the face of link +# failures: +source ./test_env.sh + +trap stop_brokers INT EXIT + +fail() { + echo $1 + exit 1 +} + +stop_brokers() { + if [[ $PRIMARY1 ]] ; then + $QPIDD_EXEC --no-module-dir -q --port $PRIMARY1 + unset PRIMARY1 + fi + if [[ $PRIMARY2 ]] ; then + $QPIDD_EXEC --no-module-dir -q --port $PRIMARY2 + unset PRIMARY2 + fi + if [[ $DR1 ]] ; then + $QPIDD_EXEC --no-module-dir -q --port $DR1 + unset DR1 + fi + if [[ $DR2 ]] ; then + $QPIDD_EXEC --no-module-dir -q --port $DR2 + unset DR2 + fi +} + +if test -d $PYTHON_DIR; then + . $srcdir/ais_check + + #todo: these cluster names need to be unique to prevent clashes + PRIMARY_CLUSTER=PRIMARY_$(hostname)_$$ + DR_CLUSTER=DR_$(hostname)_$$ + + GENERAL_OPTS="--auth no --no-module-dir --no-data-dir --daemon --port 0 --log-to-stderr false" + PRIMARY_OPTS="--load-module $REPLICATING_LISTENER_LIB --create-replication-queue true --replication-queue REPLICATION_QUEUE --load-module $CLUSTER_LIB --cluster-name $PRIMARY_CLUSTER" + DR_OPTS="--load-module $REPLICATION_EXCHANGE_LIB --load-module $CLUSTER_LIB --cluster-name $DR_CLUSTER" + + rm -f repl*.tmp #cleanup any files left from previous run + + #start first node of primary cluster and set up test queue + echo Starting primary cluster + PRIMARY1=$(with_ais_group $QPIDD_EXEC $GENERAL_OPTS $PRIMARY_OPTS --log-to-file repl.primary.1.tmp) || fail "Could not start PRIMARY1" + $PYTHON_COMMANDS/qpid-config -a "localhost:$PRIMARY1" add queue test-queue --generate-queue-events 2 + $PYTHON_COMMANDS/qpid-config -a "localhost:$PRIMARY1" add queue control-queue --generate-queue-events 1 + + #send 10 messages, consume 5 of them + for i in `seq 1 10`; do echo Message$i; done | ./sender --port $PRIMARY1 + ./receiver --port $PRIMARY1 --messages 5 > /dev/null + + #add new node to primary cluster, testing correct transfer of state: + echo Adding node to primary cluster + PRIMARY2=$(with_ais_group $QPIDD_EXEC $GENERAL_OPTS $PRIMARY_OPTS --log-to-file repl.primary.2.tmp) || fail "Could not start PRIMARY2 " + + #start DR cluster, set up test queue there and establish replication bridge + echo Starting DR cluster + DR1=$(with_ais_group $QPIDD_EXEC $GENERAL_OPTS $DR_OPTS --log-to-file repl.dr.1.tmp) || fail "Could not start DR1" + DR2=$(with_ais_group $QPIDD_EXEC $GENERAL_OPTS $DR_OPTS --log-to-file repl.dr.2.tmp) || fail "Could not start DR2" + + $PYTHON_COMMANDS/qpid-config -a "localhost:$DR1" add queue test-queue + $PYTHON_COMMANDS/qpid-config -a "localhost:$DR1" add queue control-queue + $PYTHON_COMMANDS/qpid-config -a "localhost:$DR1" add exchange replication REPLICATION_EXCHANGE + $PYTHON_COMMANDS/qpid-route queue add localhost:$DR2 localhost:$PRIMARY2 REPLICATION_EXCHANGE REPLICATION_QUEUE || fail "Could not add route." + + #send more messages to primary + for i in `seq 11 20`; do echo Message$i; done | ./sender --port $PRIMARY1 --send-eos 1 + + #wait for replication events to all be processed: + echo Waiting for replication to complete + echo Done | ./sender --port $PRIMARY1 --routing-key control-queue --send-eos 1 + ./receiver --queue control-queue --port $DR1 > /dev/null + + #verify contents of test queue on dr cluster: + echo Verifying... + ./receiver --port $DR2 > repl.out.tmp + for i in `seq 6 20`; do echo Message$i; done | diff repl.out.tmp - || FAIL=1 + + if [[ $FAIL ]]; then + echo Clustered replication test failed: expectations not met! + exit 1 + else + echo Clustered replication test passed + rm -f repl*.tmp + fi + +fi diff --git a/qpid/cpp/src/tests/config.null b/qpid/cpp/src/tests/config.null new file mode 100644 index 0000000000..565c7da435 --- /dev/null +++ b/qpid/cpp/src/tests/config.null @@ -0,0 +1 @@ +# empty config 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_level_test b/qpid/cpp/src/tests/dynamic_log_level_test new file mode 100755 index 0000000000..990e56b1b1 --- /dev/null +++ b/qpid/cpp/src/tests/dynamic_log_level_test @@ -0,0 +1,57 @@ +#!/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 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; +} + +rm -rf $LOG_FILE +PORT=$($QPIDD_EXEC --auth=no --no-module-dir --daemon --port=0 --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) + +$srcdir/qpid-ctrl -b localhost:$PORT setLogLevel level='notice+' > /dev/null +$srcdir/qpid-ctrl -b localhost:$PORT echo sequence=1 body=HIDDEN > /dev/null +$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 +$srcdir/qpid-ctrl -b localhost:$PORT setLogLevel level='notice+' > /dev/null + +#check log includes debug statement for last echo, but not the first +if [[ $(grep echo $LOG_FILE | wc -l) -ne 1 ]]; then + cat $LOG_FILE + error "Log contents not as expected" +else + rm -rf $LOG_FILE + echo OK +fi + diff --git a/qpid/cpp/src/tests/echotest.cpp b/qpid/cpp/src/tests/echotest.cpp new file mode 100644 index 0000000000..5114ab883d --- /dev/null +++ b/qpid/cpp/src/tests/echotest.cpp @@ -0,0 +1,158 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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() +{ + Duration t(EPOCH, now()); + return t; +} + +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..3536ffddbe --- /dev/null +++ b/qpid/cpp/src/tests/exception_test.cpp @@ -0,0 +1,127 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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) { + ProxySessionFixture fix; + ProxyConnection c(fix.broker->getPort(Broker::TCP_TRANSPORT)); + fix.session.queueDeclare(arg::queue="q"); + fix.subs.subscribe(fix.lq, "q"); + Catcher<TransportFailure> pop(bind(&LocalQueue::pop, &fix.lq, sys::TIME_SEC)); + fix.connection.proxy.close(); + BOOST_CHECK(pop.join()); +} + +QPID_AUTO_TEST_CASE(DisconnectedListen) { + ProxySessionFixture fix; + struct NullListener : public MessageListener { + void received(Message&) { BOOST_FAIL("Unexpected message"); } + } l; + ProxyConnection c(fix.broker->getPort(Broker::TCP_TRANSPORT)); + fix.session.queueDeclare(arg::queue="q"); + fix.subs.subscribe(l, "q"); + + Catcher<TransportFailure> runner(bind(&SubscriptionManager::run, boost::ref(fix.subs))); + fix.connection.proxy.close(); + runner.join(); + BOOST_CHECK_THROW(fix.session.queueDeclare(arg::queue="x"), TransportFailure); +} + +QPID_AUTO_TEST_CASE(NoSuchQueueTest) { + ProxySessionFixture 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/failover_soak.cpp b/qpid/cpp/src/tests/failover_soak.cpp new file mode 100644 index 0000000000..c2ac36a757 --- /dev/null +++ b/qpid/cpp/src/tests/failover_soak.cpp @@ -0,0 +1,827 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/wait.h> +#include <sys/time.h> +#include <string.h> +#include <sys/types.h> +#include <signal.h> + +#include <string> +#include <iostream> +#include <sstream> +#include <vector> + +#include <boost/assign.hpp> + +#include "qpid/framing/Uuid.h" + +#include <ForkedBroker.h> +#include <qpid/client/Connection.h> + + + + + +using namespace std; +using boost::assign::list_of; +using namespace qpid::framing; +using namespace qpid::client; + + +namespace qpid { +namespace tests { + +vector<pid_t> pids; + +typedef vector<ForkedBroker *> brokerVector; + +typedef enum +{ + NO_STATUS, + RUNNING, + COMPLETED +} +childStatus; + + +typedef enum +{ + NO_TYPE, + DECLARING_CLIENT, + SENDING_CLIENT, + RECEIVING_CLIENT +} +childType; + + +ostream& operator<< ( ostream& os, const childType& ct ) { + switch ( ct ) { + case DECLARING_CLIENT: os << "Declaring Client"; break; + case SENDING_CLIENT: os << "Sending Client"; break; + case RECEIVING_CLIENT: os << "Receiving Client"; break; + default: os << "No Client"; break; + } + + return os; +} + + + + +struct child +{ + child ( string & name, pid_t pid, childType type ) + : name(name), pid(pid), retval(-999), status(RUNNING), type(type) + { + gettimeofday ( & startTime, 0 ); + } + + + void + done ( int _retval ) + { + retval = _retval; + status = COMPLETED; + gettimeofday ( & stopTime, 0 ); + } + + + void + setType ( childType t ) + { + type = t; + } + + + string name; + pid_t pid; + int retval; + childStatus status; + childType type; + struct timeval startTime, + stopTime; +}; + + + + +struct children : public vector<child *> +{ + + void + add ( string & name, pid_t pid, childType type ) + { + push_back ( new child ( name, pid, type ) ); + } + + + child * + get ( pid_t pid ) + { + vector<child *>::iterator i; + for ( i = begin(); i != end(); ++ i ) + if ( pid == (*i)->pid ) + return *i; + + return 0; + } + + + void + exited ( pid_t pid, int retval ) + { + child * kid = get ( pid ); + if(! kid) + { + if ( verbosity > 1 ) + { + cerr << "children::exited warning: Can't find child with pid " + << pid + << endl; + } + return; + } + + kid->done ( retval ); + } + + + int + unfinished ( ) + { + int count = 0; + + vector<child *>::iterator i; + for ( i = begin(); i != end(); ++ i ) + if ( COMPLETED != (*i)->status ) + ++ count; + + return count; + } + + + int + checkChildren ( ) + { + for ( unsigned int i = 0; i < pids.size(); ++ i ) + { + int pid = pids[i]; + int returned_pid; + int status; + + child * kid = get ( pid ); + + if ( kid->status != COMPLETED ) + { + returned_pid = waitpid ( pid, &status, WNOHANG ); + + if ( returned_pid == pid ) + { + int exit_status = WEXITSTATUS(status); + exited ( pid, exit_status ); + if ( exit_status ) // this is a child error. + return exit_status; + } + } + } + + return 0; + } + + + void + killEverybody ( ) + { + vector<child *>::iterator i; + for ( i = begin(); i != end(); ++ i ) + kill ( (*i)->pid, 9 ); + } + + + + void + print ( ) + { + cout << "--- status of all children --------------\n"; + vector<child *>::iterator i; + for ( i = begin(); i != end(); ++ i ) + cout << "child: " << (*i)->name + << " status: " << (*i)->status + << endl; + cout << "\n\n\n\n"; + } + + int verbosity; +}; + + +children allMyChildren; + + +void +childExit ( int ) +{ + int childReturnCode; + pid_t pid = waitpid ( 0, & childReturnCode, WNOHANG); + + if ( pid > 0 ) + allMyChildren.exited ( pid, childReturnCode ); +} + + + +int +mrand ( int maxDesiredVal ) { + double zeroToOne = (double) rand() / (double) RAND_MAX; + return (int) (zeroToOne * (double) maxDesiredVal); +} + + + +int +mrand ( int minDesiredVal, int maxDesiredVal ) { + int interval = maxDesiredVal - minDesiredVal; + return minDesiredVal + mrand ( interval ); +} + + + +void +makeClusterName ( string & s ) { + stringstream ss; + ss << "soakTestCluster_" << Uuid(true).str(); + s = ss.str(); +} + + + + + +void +printBrokers ( brokerVector & brokers ) +{ + cout << "Broker List ------------ size: " << brokers.size() << "\n"; + for ( brokerVector::iterator i = brokers.begin(); i != brokers.end(); ++ i) { + cout << "pid: " + << (*i)->getPID() + << " port: " + << (*i)->getPort() + << endl; + } + cout << "end Broker List ------------\n"; +} + + + + +ForkedBroker * newbie = 0; +int newbie_port = 0; + + + +bool +wait_for_newbie ( ) +{ + if ( ! newbie ) + return true; + + try + { + Connection connection; + connection.open ( "127.0.0.1", newbie_port ); + connection.close(); + newbie = 0; // He's no newbie anymore! + return true; + } + catch ( const std::exception& error ) + { + std::cerr << "wait_for_newbie error: " + << error.what() + << endl; + return false; + } +} + +bool endsWith(const char* str, const char* suffix) { + return (strlen(suffix) < strlen(str) && 0 == strcmp(str+strlen(str)-strlen(suffix), suffix)); +} + + +void +startNewBroker ( brokerVector & brokers, + char const * moduleOrDir, + string const clusterName, + int verbosity, + int durable ) +{ + static int brokerId = 0; + stringstream path, prefix; + prefix << "soak-" << brokerId; + std::vector<std::string> argv = list_of<string> + ("qpidd") + ("--cluster-name")(clusterName) + ("--auth=no") + ("--mgmt-enable=no") + ("--log-prefix")(prefix.str()) + ("--log-to-file")(prefix.str()+".log") + ("--log-enable=info+") + ("--log-enable=debug+:cluster") + ("TMP_DATA_DIR"); + + if (endsWith(moduleOrDir, "cluster.so")) { + // Module path specified, load only that module. + argv.push_back(string("--load-module=")+moduleOrDir); + argv.push_back("--no-module-dir"); + if ( durable ) { + std::cerr << "failover_soak warning: durable arg hass no effect. Use \"dir\" option of \"moduleOrDir\".\n"; + } + } + else { + // Module directory specified, load all modules in dir. + argv.push_back(string("--module-dir=")+moduleOrDir); + } + + newbie = new ForkedBroker (argv); + newbie_port = newbie->getPort(); + ForkedBroker * broker = newbie; + + if ( verbosity > 0 ) + std::cerr << "new broker created: pid == " + << broker->getPID() + << " log-prefix == " + << "soak-" << brokerId + << endl; + brokers.push_back ( broker ); + + ++ brokerId; +} + + + + + +bool +killFrontBroker ( brokerVector & brokers, int verbosity ) +{ + cerr << "killFrontBroker: waiting for newbie sync...\n"; + if ( ! wait_for_newbie() ) + return false; + cerr << "killFrontBroker: newbie synced.\n"; + + if ( verbosity > 0 ) + cout << "killFrontBroker pid: " << brokers[0]->getPID() << " on port " << brokers[0]->getPort() << endl; + try { brokers[0]->kill(9); } + catch ( const exception& error ) { + if ( verbosity > 0 ) + { + cout << "error killing broker: " + << error.what() + << endl; + } + + return false; + } + delete brokers[0]; + brokers.erase ( brokers.begin() ); + return true; +} + + + + + +/* + * The optional delay is to avoid killing newbie brokers that have just + * been added and are still in the process of updating. This causes + * spurious, test-generated errors that scare everybody. + */ +void +killAllBrokers ( brokerVector & brokers, int delay ) +{ + if ( delay > 0 ) + { + std::cerr << "Killing all brokers after delay of " << delay << endl; + sleep ( delay ); + } + + for ( uint i = 0; i < brokers.size(); ++ i ) + try { brokers[i]->kill(9); } + catch ( const exception& error ) + { + std::cerr << "killAllBrokers Warning: exception during kill on broker " + << i + << " " + << error.what() + << endl; + } +} + + + + + +pid_t +runDeclareQueuesClient ( brokerVector brokers, + char const * host, + char const * path, + int verbosity, + int durable, + char const * queue_prefix, + int n_queues + ) +{ + string name("declareQueues"); + int port = brokers[0]->getPort ( ); + + if ( verbosity > 1 ) + cout << "startDeclareQueuesClient: host: " + << host + << " port: " + << port + << endl; + stringstream portSs; + portSs << port; + + vector<const char*> argv; + argv.push_back ( "declareQueues" ); + argv.push_back ( host ); + string portStr = portSs.str(); + argv.push_back ( portStr.c_str() ); + if ( durable ) + argv.push_back ( "1" ); + else + argv.push_back ( "0" ); + + argv.push_back ( queue_prefix ); + + char n_queues_str[20]; + sprintf ( n_queues_str, "%d", n_queues ); + argv.push_back ( n_queues_str ); + + argv.push_back ( 0 ); + pid_t pid = fork(); + + if ( ! pid ) { + execv ( path, const_cast<char * const *>(&argv[0]) ); + perror ( "error executing declareQueues: " ); + return 0; + } + + allMyChildren.add ( name, pid, DECLARING_CLIENT ); + return pid; +} + + + + + +pid_t +startReceivingClient ( brokerVector brokers, + char const * host, + char const * receiverPath, + char const * reportFrequency, + int verbosity, + char const * queue_name + ) +{ + string name("receiver"); + int port = brokers[0]->getPort ( ); + + if ( verbosity > 1 ) + cout << "startReceivingClient: port " << port << endl; + + // verbosity has to be > 1 to let clients talk. + int client_verbosity = (verbosity > 1 ) ? 1 : 0; + + char portStr[100]; + char verbosityStr[100]; + sprintf(portStr, "%d", port); + sprintf(verbosityStr, "%d", client_verbosity); + + + vector<const char*> argv; + argv.push_back ( "resumingReceiver" ); + argv.push_back ( host ); + argv.push_back ( portStr ); + argv.push_back ( reportFrequency ); + argv.push_back ( verbosityStr ); + argv.push_back ( queue_name ); + argv.push_back ( 0 ); + + pid_t pid = fork(); + pids.push_back ( pid ); + + if ( ! pid ) { + execv ( receiverPath, const_cast<char * const *>(&argv[0]) ); + perror ( "error executing receiver: " ); + return 0; + } + + allMyChildren.add ( name, pid, RECEIVING_CLIENT ); + return pid; +} + + + + + +pid_t +startSendingClient ( brokerVector brokers, + char const * host, + char const * senderPath, + char const * nMessages, + char const * reportFrequency, + int verbosity, + int durability, + char const * queue_name + ) +{ + string name("sender"); + int port = brokers[0]->getPort ( ); + + if ( verbosity > 1) + cout << "startSenderClient: port " << port << endl; + char portStr[100]; + char verbosityStr[100]; + // + // verbosity has to be > 1 to let clients talk. + int client_verbosity = (verbosity > 1 ) ? 1 : 0; + + sprintf ( portStr, "%d", port); + sprintf ( verbosityStr, "%d", client_verbosity); + + vector<const char*> argv; + argv.push_back ( "replayingSender" ); + argv.push_back ( host ); + argv.push_back ( portStr ); + argv.push_back ( nMessages ); + argv.push_back ( reportFrequency ); + argv.push_back ( verbosityStr ); + if ( durability ) + argv.push_back ( "1" ); + else + argv.push_back ( "0" ); + argv.push_back ( queue_name ); + argv.push_back ( 0 ); + + pid_t pid = fork(); + pids.push_back ( pid ); + + if ( ! pid ) { + execv ( senderPath, const_cast<char * const *>(&argv[0]) ); + perror ( "error executing sender: " ); + return 0; + } + + allMyChildren.add ( name, pid, SENDING_CLIENT ); + return pid; +} + + + +#define HUNKY_DORY 0 +#define BAD_ARGS 1 +#define CANT_FORK_DQ 2 +#define CANT_FORK_RECEIVER 3 +#define CANT_FORK_SENDER 4 +#define DQ_FAILED 5 +#define ERROR_ON_CHILD 6 +#define HANGING 7 +#define ERROR_KILLING_BROKER 8 + +}} // namespace qpid::tests + +using namespace qpid::tests; + +// If you want durability, use the "dir" option of "moduleOrDir" . +int +main ( int argc, char const ** argv ) +{ + int brokerKills = 0; + if ( argc != 11 ) { + cerr << "Usage: " + << argv[0] + << "moduleOrDir declareQueuesPath senderPath receiverPath nMessages reportFrequency verbosity durable n_queues n_brokers" + << endl; + cerr << "\tverbosity is an integer, durable is 0 or 1\n"; + return BAD_ARGS; + } + signal ( SIGCHLD, childExit ); + + int i = 1; + char const * moduleOrDir = argv[i++]; + char const * declareQueuesPath = argv[i++]; + char const * senderPath = argv[i++]; + char const * receiverPath = argv[i++]; + char const * nMessages = argv[i++]; + char const * reportFrequency = argv[i++]; + int verbosity = atoi(argv[i++]); + int durable = atoi(argv[i++]); + int n_queues = atoi(argv[i++]); + int n_brokers = atoi(argv[i++]); + + char const * host = "127.0.0.1"; + + allMyChildren.verbosity = verbosity; + + string clusterName; + + srand ( getpid() ); + + makeClusterName ( clusterName ); + + brokerVector brokers; + + if ( verbosity > 1 ) + cout << "Starting initial cluster...\n"; + + for ( int i = 0; i < n_brokers; ++ i ) { + startNewBroker ( brokers, + moduleOrDir, + clusterName, + verbosity, + durable ); + } + + + if ( verbosity > 0 ) + printBrokers ( brokers ); + + // Get prefix for each queue name. + stringstream queue_prefix; + queue_prefix << "failover_soak_" << getpid(); + string queue_prefix_str(queue_prefix.str()); + + // Run the declareQueues child. + int childStatus; + pid_t dqClientPid = + runDeclareQueuesClient ( brokers, + host, + declareQueuesPath, + verbosity, + durable, + queue_prefix_str.c_str(), + n_queues + ); + if ( -1 == dqClientPid ) { + cerr << "END_OF_TEST ERROR_START_DECLARE_1\n"; + return CANT_FORK_DQ; + } + + // Don't continue until declareQueues is finished. + pid_t retval = waitpid ( dqClientPid, & childStatus, 0); + if ( retval != dqClientPid) { + cerr << "END_OF_TEST ERROR_START_DECLARE_2\n"; + return DQ_FAILED; + } + allMyChildren.exited ( dqClientPid, childStatus ); + + + /* + Start one receiving and one sending client for each queue. + */ + for ( int i = 0; i < n_queues; ++ i ) { + + stringstream queue_name; + queue_name << queue_prefix.str() << '_' << i; + string queue_name_str(queue_name.str()); + + // Receiving client --------------------------- + pid_t receivingClientPid = + startReceivingClient ( brokers, + host, + receiverPath, + reportFrequency, + verbosity, + queue_name_str.c_str() ); + if ( -1 == receivingClientPid ) { + cerr << "END_OF_TEST ERROR_START_RECEIVER\n"; + return CANT_FORK_RECEIVER; + } + + + // Sending client --------------------------- + pid_t sendingClientPid = + startSendingClient ( brokers, + host, + senderPath, + nMessages, + reportFrequency, + verbosity, + durable, + queue_name_str.c_str() ); + if ( -1 == sendingClientPid ) { + cerr << "END_OF_TEST ERROR_START_SENDER\n"; + return CANT_FORK_SENDER; + } + } + + + int minSleep = 2, + maxSleep = 6; + + int totalBrokers = n_brokers; + + int loop = 0; + + while ( 1 ) + { + ++ loop; + + /* + if ( verbosity > 1 ) + std::cerr << "------- loop " << loop << " --------\n"; + + if ( verbosity > 0 ) + cout << totalBrokers << " brokers have been added to the cluster.\n\n\n"; + */ + + // Sleep for a while. ------------------------- + int sleepyTime = mrand ( minSleep, maxSleep ); + sleep ( sleepyTime ); + + int bullet = mrand ( 100 ); + if ( bullet >= 95 ) + { + fprintf ( stderr, "Killing oldest broker...\n" ); + + // Kill the oldest broker. -------------------------- + if ( ! killFrontBroker ( brokers, verbosity ) ) + { + allMyChildren.killEverybody(); + killAllBrokers ( brokers, 5 ); + std::cerr << "END_OF_TEST ERROR_BROKER\n"; + return ERROR_KILLING_BROKER; + } + ++ brokerKills; + + // Start a new broker. -------------------------- + if ( verbosity > 0 ) + cout << "Starting new broker.\n\n"; + + startNewBroker ( brokers, + moduleOrDir, + clusterName, + verbosity, + durable ); + ++ totalBrokers; + printBrokers ( brokers ); + cerr << brokerKills << " brokers have been killed.\n\n\n"; + } + + int retval = allMyChildren.checkChildren(); + if ( retval ) + { + std::cerr << "END_OF_TEST ERROR_CLIENT\n"; + allMyChildren.killEverybody(); + killAllBrokers ( brokers, 5 ); + return ERROR_ON_CHILD; + } + + // If all children have exited, quit. + int unfinished = allMyChildren.unfinished(); + if ( unfinished == 0 ) { + killAllBrokers ( brokers, 5 ); + + if ( verbosity > 1 ) + cout << "failoverSoak: all children have exited.\n"; + + std::cerr << "END_OF_TEST SUCCESSFUL\n"; + return HUNKY_DORY; + } + + } + + allMyChildren.killEverybody(); + killAllBrokers ( brokers, 5 ); + + std::cerr << "END_OF_TEST SUCCESSFUL\n"; + + return HUNKY_DORY; +} + + + diff --git a/qpid/cpp/src/tests/fanout_perftest b/qpid/cpp/src/tests/fanout_perftest new file mode 100755 index 0000000000..d8a7661f49 --- /dev/null +++ b/qpid/cpp/src/tests/fanout_perftest @@ -0,0 +1,22 @@ +#!/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. +# + +exec `dirname $0`/run_perftest 10000 --mode fanout --npubs 16 --nsubs 16 --size 64 diff --git a/qpid/cpp/src/tests/federated_cluster_test b/qpid/cpp/src/tests/federated_cluster_test new file mode 100755 index 0000000000..70bec5e703 --- /dev/null +++ b/qpid/cpp/src/tests/federated_cluster_test @@ -0,0 +1,152 @@ +#!/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. +# + +# Test reliability of the replication feature in the face of link +# failures: +srcdir=`dirname $0` +source ./test_env.sh + +trap stop_brokers EXIT + +fail() { + echo $1 + exit 1 +} + +stop_brokers() { + if [[ $BROKER_A ]] ; then + ../qpidd --no-module-dir -q --port $BROKER_A + unset BROKER_A + fi + if [[ $NODE_1 ]] ; then + ../qpidd --no-module-dir -q --port $NODE_1 + unset NODE_1 + fi + if [[ $NODE_2 ]] ; then + ../qpidd --no-module-dir -q --port $NODE_2 + unset NODE_2 + fi + if [ -f cluster.ports ]; then + rm cluster.ports + fi +} + +start_brokers() { + #start single node... + BROKER_A=`../qpidd --daemon --port 0 --no-data-dir --no-module-dir --auth no --log-enable info+` || fail "BROKER_A failed to start" + + #...and start cluster + $srcdir/start_cluster 2 || fail "Could not start cluster" + NODE_1=$(head -1 cluster.ports) + NODE_2=$(tail -1 cluster.ports) + test -n "$NODE_1" || fail "NODE_1 failed to start" + test -n "$NODE_2" || fail "NODE_2 failed to start" +} + +setup() { + #create exchange on both cluster and single broker + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_A" add exchange direct test-exchange + $PYTHON_COMMANDS/qpid-config -a "localhost:$NODE_1" add exchange direct test-exchange + + #create dynamic routes for test exchange + $PYTHON_COMMANDS/qpid-route dynamic add "localhost:$NODE_2" "localhost:$BROKER_A" test-exchange + $PYTHON_COMMANDS/qpid-route dynamic add "localhost:$BROKER_A" "localhost:$NODE_2" test-exchange + + #create test queue on cluster and bind it to the test exchange + $PYTHON_COMMANDS/qpid-config -a "localhost:$NODE_1" add queue test-queue + $PYTHON_COMMANDS/qpid-config -a "localhost:$NODE_1" bind test-exchange test-queue to-cluster + + #create test queue on single broker and bind it to the test exchange + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_A" add queue test-queue + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_A" bind test-exchange test-queue from-cluster +} + +run_test_pull_to_cluster_two_consumers() { + #start consumers on each of the two nodes of the cluster + ./receiver --port $NODE_1 --queue test-queue --credit-window 1 > fed1.out.tmp & + ./receiver --port $NODE_2 --queue test-queue --credit-window 1 > fed2.out.tmp & + + #send stream of messages to test exchange on single broker + for i in `seq 1 1000`; do echo Message $i >> fed.in.tmp; done + ./sender --port $BROKER_A --exchange test-exchange --routing-key to-cluster --send-eos 2 < fed.in.tmp + + #combine output of the two consumers, sort it and compare with the expected stream + wait + sort -g -k 2 fed1.out.tmp fed2.out.tmp > fed.out.tmp + diff fed.in.tmp fed.out.tmp || fail "federated link to cluster failed: expectations not met!" + + rm -f fed*.tmp #cleanup +} + +run_test_pull_to_cluster() { + #send stream of messages to test exchange on single broker + for i in `seq 1 1000`; do echo Message $i >> fed.in.tmp; done + ./sender --port $BROKER_A --exchange test-exchange --routing-key to-cluster --send-eos 1 < fed.in.tmp + + #consume from remaining node of the cluster + ./receiver --port $NODE_2 --queue test-queue > fed.out.tmp + + #verify all messages are received + diff fed.in.tmp fed.out.tmp || fail "federated link to cluster failed: expectations not met!" + + rm -f fed*.tmp #cleanup +} + +run_test_pull_from_cluster() { + #start consumer on single broker + ./receiver --port $BROKER_A --queue test-queue --credit-window 1 > fed.out.tmp & + + #send stream of messages to test exchange on cluster + for i in `seq 1 1000`; do echo Message $i >> fed.in.tmp; done + ./sender --port $NODE_2 --exchange test-exchange --routing-key from-cluster --send-eos 1 < fed.in.tmp + + #verify all messages are received + wait + diff fed.in.tmp fed.out.tmp || fail "federated link from cluster failed: expectations not met!" + + rm -f fed*.tmp #cleanup +} + + +if test -d ${PYTHON_DIR}; then + . $srcdir/ais_check + + rm -f fed*.tmp #cleanup any files left from previous run + start_brokers + echo "brokers started" + setup + echo "setup completed" + run_test_pull_to_cluster_two_consumers + echo "federated link to cluster verified" + run_test_pull_from_cluster + echo "federated link from cluster verified" + if [[ $TEST_NODE_FAILURE ]] ; then + #kill first cluster node and retest + kill -9 $(../qpidd --check --port $NODE_1) && unset NODE_1 + echo "killed first cluster node; waiting for links to re-establish themselves..." + sleep 5 + echo "retesting..." + run_test_pull_to_cluster + echo "federated link to cluster verified" + run_test_pull_from_cluster + echo "federated link from cluster verified" + fi +fi diff --git a/qpid/cpp/src/tests/federated_cluster_test_with_node_failure b/qpid/cpp/src/tests/federated_cluster_test_with_node_failure new file mode 100755 index 0000000000..f144a676de --- /dev/null +++ b/qpid/cpp/src/tests/federated_cluster_test_with_node_failure @@ -0,0 +1,23 @@ +#!/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. +# + +srcdir=`dirname $0` +TEST_NODE_FAILURE=1 $srcdir/federated_cluster_test diff --git a/qpid/cpp/src/tests/federated_topic_test b/qpid/cpp/src/tests/federated_topic_test new file mode 100755 index 0000000000..b1063c7e8c --- /dev/null +++ b/qpid/cpp/src/tests/federated_topic_test @@ -0,0 +1,129 @@ +#!/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 + +MY_DIR=$(dirname $(which $0)) +source ./test_env.sh + +trap stop_brokers EXIT + +start_broker() { + ${MY_DIR}/../qpidd --daemon --port 0 --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" + ${MY_DIR}/topic_listener -p $MY_PORT > $LOG 2>&1 && rm -f $LOG +} + +publish() { + ${MY_DIR}/topic_publisher --messages $MESSAGES --batches $BATCHES --subscribers $SUBSCRIBERS -p $PORT_A +} + +setup_routes() { + BROKER_A="localhost:$PORT_A" + BROKER_B="localhost:$PORT_B" + BROKER_C="localhost:$PORT_C" + if (($VERBOSE)); then + echo "Establishing routes for topic..." + fi + $PYTHON_COMMANDS/qpid-route route add $BROKER_B $BROKER_A amq.topic topic_control B B + $PYTHON_COMMANDS/qpid-route route add $BROKER_C $BROKER_B amq.topic topic_control C C + if (($VERBOSE)); then + echo "linked A->B->C" + fi + $PYTHON_COMMANDS/qpid-route route add $BROKER_B $BROKER_C amq.topic topic_control B B + $PYTHON_COMMANDS/qpid-route 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 + + $PYTHON_COMMANDS/qpid-route route add $BROKER_B $BROKER_C amq.direct response B B + $PYTHON_COMMANDS/qpid-route 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" + $PYTHON_COMMANDS/qpid-route 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..201b06a4a2 --- /dev/null +++ b/qpid/cpp/src/tests/federation.py @@ -0,0 +1,2091 @@ +#!/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 +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) + + 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) + + + 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) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "amq.direct", "amq.direct", "my-key", "", "", False, False, False, 0) + self.assertEqual(result.status, 0) + + bridge = qmf.getObjects(_class="bridge")[0] + result = bridge.close() + self.assertEqual(result.status, 0) + + result = link.close() + self.assertEqual(result.status, 0) + + self.verify_cleanup() + + def test_pull_from_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) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "amq.direct", "amq.fanout", "my-key", "", "", False, False, False, 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_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 = bridge.close() + self.assertEqual(result.status, 0) + result = link.close() + self.assertEqual(result.status, 0) + + 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) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "amq.direct", "amq.fanout", "my-key", "", "", False, True, False, 0) + self.assertEqual(result.status, 0) + + 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 = link.close() + self.assertEqual(result.status, 0) + + 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) + + link = qmf.getObjects(_class="link")[0] + result = link.bridge(False, "my-bridge-queue", "amq.fanout", "my-key", "", "", True, False, False, 1) + self.assertEqual(result.status, 0) + + 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 = link.close() + self.assertEqual(result.status, 0) + + self.verify_cleanup() + + 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) + r_res = r_link.bridge(False, "amq.direct", "amq.direct", "key", "", "", False, False, False, 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) + 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) + 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) + 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) + 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) + 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) + self.assertEqual(result.status, 0) + result = link.bridge(False, "fed.topic_reorigin_2", "fed.topic_reorigin_2", "", "", "", False, False, True, 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) + self.assertEqual(result.status, 0) + result = link.bridge(False, "fed.direct_reorigin_2", "fed.direct_reorigin_2", "", "", "", False, False, True, 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(self): + session = self.session + r_conn = self.connect(host=self.remote_host(), port=self.remote_port()) + r_session = r_conn.session("test_dynamic_headers") + + 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) + 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':'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", 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) + self.assertEqual(result.status, 0) + result = link.bridge(False, "fed.headers_reorigin_2", "fed.headers_reorigin_2", "", "", "", False, False, True, 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) + 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) + + 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) + + self.assertEqual(result.status, 0) + result = link.bridge(False, "fed.xml_reorigin_2", "fed.xml_reorigin_2", "", "", "", False, False, True, 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) + + 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) + 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 + 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 + 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 + 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 + 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 + 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() + + 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/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/install_env.sh.in b/qpid/cpp/src/tests/install_env.sh.in new file mode 100644 index 0000000000..2231954cb8 --- /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.4/site-packages:$PYTHONPATH diff --git a/qpid/cpp/src/tests/logging.cpp b/qpid/cpp/src/tests/logging.cpp new file mode 100644 index 0000000000..fc55d642c3 --- /dev/null +++ b/qpid/cpp/src/tests/logging.cpp @@ -0,0 +1,385 @@ +/* + * + * 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/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 boost; +using namespace qpid::log; + +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(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_EQUAL(string(BOOST_CURRENT_FUNCTION) + ": foo\n", out->last()); + + 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}; + 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-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(!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(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(critical, "foo"); int srcline=__LINE__; + ifstream log("logging.tmp"); + string line; + getline(log, line); + string expect=(format("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(critical, str); + ifstream log("logging.tmp"); + string line; + getline(log, line, '\0'); + string expect="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_SUITE_END() + +}} // namespace qpid::tests diff --git a/qpid/cpp/src/tests/long_cluster_tests.py b/qpid/cpp/src/tests/long_cluster_tests.py new file mode 100755 index 0000000000..f77837f0c4 --- /dev/null +++ b/qpid/cpp/src/tests/long_cluster_tests.py @@ -0,0 +1,38 @@ +#!/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, unittest +from testlib import TestBaseCluster + +class LongClusterTests(TestBaseCluster): + """Long/Soak cluster tests with async store ability""" + + + def test_LongCluster_01_DummyTest(self): + """Dummy test - a placeholder for the first of the long/soak python cluster tests""" + pass + +# Start the test here + +if __name__ == '__main__': + if os.getenv("STORE_LIB") != None: + print "NOTE: Store enabled for the following tests:" + if not unittest.main(): sys.exit(1) + diff --git a/qpid/cpp/src/tests/multiq_perftest b/qpid/cpp/src/tests/multiq_perftest new file mode 100755 index 0000000000..10f9edd2a6 --- /dev/null +++ b/qpid/cpp/src/tests/multiq_perftest @@ -0,0 +1,22 @@ +#!/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. +# + +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..59548b23f7 --- /dev/null +++ b/qpid/cpp/src/tests/perfdist @@ -0,0 +1,87 @@ +#!/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. +# + + +# +# 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/policy.acl b/qpid/cpp/src/tests/policy.acl new file mode 100644 index 0000000000..ef46026555 --- /dev/null +++ b/qpid/cpp/src/tests/policy.acl @@ -0,0 +1 @@ +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..0216b5ca7b --- /dev/null +++ b/qpid/cpp/src/tests/python_tests @@ -0,0 +1,29 @@ +#!/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. +# + +# Run the python tests. +source ./test_env.sh +test -d $PYTHON_DIR || { echo "Skipping python tests, no python dir."; exit 0; } +QPID_PORT=${QPID_PORT:-5672} +PYTHON_TESTS=${PYTHON_TESTS:-$*} +FAILING=${FAILING:-/dev/null} + +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..9f8b9890c4 --- /dev/null +++ b/qpid/cpp/src/tests/python_tests.ps1 @@ -0,0 +1,45 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT 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 +} + +$PYTHON_TEST_DIR = "$srcdir\..\..\..\tests\src\py" +$QMF_LIB = "$srcdir\..\..\..\extras\qmf\src\py" + +if (Test-Path env:FAILING) { + $fails = "-I $env:FAILING" +} +if (Test-Path env:PYTHON_TESTS) { + $tests = "$env:PYTHON_TESTS" +} +else { + $tests = "$args" +} + +#cd $PYTHON_DIR +$env:PYTHONPATH="$PYTHON_DIR;$PYTHON_TEST_DIR;$env:PYTHONPATH;$QMF_LIB" +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-build-rinstall b/qpid/cpp/src/tests/qpid-build-rinstall new file mode 100755 index 0000000000..1a92f8750a --- /dev/null +++ b/qpid/cpp/src/tests/qpid-build-rinstall @@ -0,0 +1,28 @@ +#!/bin/sh +# +# 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..2f5e8e5afe --- /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) + { + 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..ff787a46dd --- /dev/null +++ b/qpid/cpp/src/tests/qpid-cluster-benchmark @@ -0,0 +1,58 @@ +#!/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 values +PORT="5672" +COUNT=10000 +FLOW=100 # Flow control limit on queue depth for latency. +REPEAT=10 +QUEUES=4 +CLIENTS=3 + +while getopts "p:c:f:r:t:b:q:c" opt; do + case $opt in + p) PORT=$OPTARG;; + c) COUNT=$OPTARG;; + f) FLOW=$OPTARG;; + r) REPEAT=$OPTARG;; + s) SCALE=$OPTARG;; + b) BROKERS=$OPTARG;; + q) QUEUES=$OPTARG;; + c) CLIENTS=$OPTARG;; + *) echo "Unknown option"; exit 1;; + esac +done + +BROKERS=${BROKERS:-$(echo $HOSTS | sed "s/\>/:$PORT/g;s/ /,/g")} # Broker URL list +BROKER=`echo $BROKERS | awk -F, '{print $1}'` # First broker + +run_test() { echo $*; shift; "$@"; echo; echo; echo; } + +# Multiple pubs/subs connect via multiple brokers (active-active) +run_test "multi-host-thruput" qpid-cpp-benchmark --repeat $REPEAT -b $BROKERS --no-timestamp --summarize -q$QUEUES -s$CLIENTS -r$CLIENTS -m $COUNT + +# Multiple pubs/subs connect via single broker (active-passive) +run_test "single-host-thruput" qpid-cpp-benchmark --repeat $REPEAT -b $BROKER --no-timestamp --summarize -q$QUEUES -s$CLIENTS -r$CLIENTS -m $COUNT + +# Latency +run_test "latency" qpid-cpp-benchmark --repeat $REPEAT -b $BROKER --connection-options '{tcp-nodelay:true}' -m $COUNT --flow-control $FLOW + diff --git a/qpid/cpp/src/tests/qpid-cluster-lag.py b/qpid/cpp/src/tests/qpid-cluster-lag.py new file mode 100755 index 0000000000..5b24353241 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-cluster-lag.py @@ -0,0 +1,93 @@ +#!/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. +# + +"""%prog [options] broker... +Check for brokers that lag behind other brokers in a cluster.""" + +import os, os.path, sys, socket, time, re +from qpid.messaging import * +from optparse import OptionParser +from threading import Thread + +class Browser(Thread): + def __init__(self, broker, queue, timeout): + Thread.__init__(self) + self.broker = broker + self.queue = queue + self.timeout = timeout + self.error = None + self.time = None + + def run(self): + try: + self.connection = Connection(self.broker) + self.connection.open() + self.session = self.connection.session() + self.receiver = self.session.receiver("%s;{mode:browse}"%self.queue) + self.msg = self.receiver.fetch(timeout=self.timeout) + self.time = time.time() + if (self.msg.content != self.queue): + raise Exception("Wrong message content, expected '%s' found '%s'"% + (self.queue, self.msg.content)) + except Empty: + self.error = "No message on queue %s"%self.queue + except Exception, e: + self.error = "Error: %s"%e + +def main(argv): + op = OptionParser(usage=__doc__) + op.add_option("--timeout", type="float", default=None, metavar="TIMEOUT", + help="Give up after TIMEOUT milliseconds, default never timeout") + (opts, args) = op.parse_args(argv) + if (len(args) <= 1): op.error("No brokers were specified") + brokers = args[1:] + + # Put a message on a uniquely named queue. + queue = "%s:%s:%s"%(os.path.basename(args[0]), socket.gethostname(), os.getpid()) + connection = Connection(brokers[0]) + connection.open() + session = connection.session() + sender = session.sender( + "%s;{create:always,delete:always,node:{durable:False}}"%queue) + sender.send(Message(content=queue)) + start = time.time() + # Browse for the message on each broker + if opts.timeout: opts.timeout + threads = [Browser(b, queue, opts.timeout) for b in brokers] + for t in threads: t.start() + delays=[] + + for t in threads: + t.join() + if t.error: + delay=t.error + else: + delay = t.time-start + delays.append([delay, t.broker]) + print "%s: %s"%(t.broker,delay) + if delays: + delays.sort() + print "lag: %s (%s-%s)"%(delays[-1][0] - delays[0][0], delays[-1][1], delays[0][1]) + # Clean up + sender.close() + session.close() + connection.close() + +if __name__ == "__main__": sys.exit(main(sys.argv)) diff --git a/qpid/cpp/src/tests/qpid-cpp-benchmark b/qpid/cpp/src/tests/qpid-cpp-benchmark new file mode 100755 index 0000000000..6138108558 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-cpp-benchmark @@ -0,0 +1,260 @@ +#!/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, qpid.messaging, re +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("--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("--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("--connection-options", type="str", + help="Connection options for senders & receivers") +op.add_option("--flow-control", default=0, type="int", metavar="N", + help="Flow control each sender to limit queue depth to 2*N. 0 means no flow control.") +op.add_option("--durable", default=False, action="store_true", + help="Use durable queues and messages") + +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 + +clients = Clients() + +def start_receive(queue, index, opts, ready_queue, broker, host): + address_opts=["create:receiver"] + 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 = ["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", ready_queue, + "--report-header=no" + ] + command += opts.receive_arg + if opts.connection_options: + command += ["--connection-options",opts.connection_options] + if host: command = ssh_command(host, command) + return clients.add(Popen(command, stdout=PIPE)) + +def start_send(queue, opts, broker, host): + address="%s;{%s}"%(queue,",".join(opts.send_option)) + command = ["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=no", + "--flow-control", str(opts.flow_control), + "--durable", str(opts.durable) + ] + command += opts.send_arg + if opts.connection_options: + command += ["--connection-options",opts.connection_options] + if host: command = ssh_command(host, command) + return clients.add(Popen(command, stdout=PIPE)) + +def first_line(p): + out,err=p.communicate() + if p.returncode != 0: raise Exception("Process failed: %s"%(out.strip())) + return out.split("\n")[0] + +def delete_queues(queues, broker): + c = qpid.messaging.Connection(broker) + c.open() + for q in queues: + try: + s = c.session() + snd = s.sender("%s;{delete:always}"%(q)) + snd.close() + s.sync() + except qpid.messaging.exceptions.NotFound: pass # Ignore "no such queue" + c.close() + +def print_header(timestamp): + if timestamp: latency_header="\tl-min\tl-max\tl-avg" + else: latency_header="" + print "send-tp\t\trecv-tp%s"%latency_header + +def parse(parser, lines): # Parse sender/receiver output + for l in lines: + fn_val = zip(parser, l) + 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): + for send,recv in map(None, send_stats, recv_stats): + line="" + if send: line += "%d"%send[0] + if recv: + line += "\t\t%d"%recv[0] + if len(recv) == 4: line += "\t%.2f\t%.2f\t%.2f"%tuple(recv[1:]) + print line + +def print_summary(send_stats, recv_stats): + 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\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) + print summary + + +class ReadyReceiver: + """A receiver for ready messages""" + def __init__(self, queue, broker): + delete_queues([queue], broker) + self.connection = qpid.messaging.Connection(broker) + self.connection.open() + self.receiver = self.connection.session().receiver( + "%s;{create:receiver,delete:receiver,node:{durable:false}}"%(queue)) + self.receiver.session.sync() + self.timeout=10 + + def wait(self, receivers): + try: + for i in receivers: self.receiver.fetch(self.timeout) + self.connection.close() + except qpid.messaging.Empty: + for r in receivers: + if (r.poll() is not None): + out,err=r.communicate() + raise Exception("Receiver error: %s"%(out)) + raise Exception("Timed out waiting for receivers to be ready") + +def flatten(l): return sum(map(lambda s: s.split(","), 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() + if not opts.broker: opts.broker = ["127.0.0.1"] # Deafult to local broker + opts.broker = flatten(opts.broker) + opts.client_host = flatten(opts.client_host) + 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): + delete_queues(queues, opts.broker[0]) + ready_receiver = ReadyReceiver(ready_queue, opts.broker[0]) + receivers = [start_receive(q, j, opts, ready_queue, brokers.next(), client_hosts.next()) + for q in queues for j in xrange(opts.receivers)] + ready_receiver.wait(filter(None, receivers)) # Wait for receivers to be ready. + senders = [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) + send_stats=parse_senders(senders) + recv_stats=parse_receivers(receivers) + if opts.summarize: print_summary(send_stats, recv_stats) + else: print_data(send_stats, recv_stats) + delete_queues(queues, opts.broker[0]) + 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..20eb4568f3 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-latency-test.cpp @@ -0,0 +1,469 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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() +{ + Duration t(EPOCH, now()); + return t; +} + +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, missedRate = 0; + AbsTime start = now(); + while (true) { + AbsTime sentAt=now(); + msg.getDeliveryProperties().setTimestamp(Duration(EPOCH, sentAt)); + async(session).messageTransfer(arg::content=msg, arg::acceptMode=1); + if (opts.sync) session.sync(); + ++sent; + AbsTime waitTill(start, sent*interval); + Duration delay(sentAt, waitTill); + if (delay < 0) + ++missedRate; + else + 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..8a5cf05775 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-perftest.cpp @@ -0,0 +1,746 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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/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; + 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), 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.") + ("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 nearest 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"), 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.totalPubs, 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.txPub){ + session.txSelect(); + } + SubscriptionManager subs(session); + LocalQueue lq; + subs.setFlowControl(1, SubscriptionManager::UNLIMITED, true); + subs.subscribe(lq, fqn("pub_start")); + + for (size_t j = 0; j < opts.iterations; ++j) { + 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 = *reinterpret_cast<const size_t*>(msg.getData().data() + offset); + 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; + } + } +}; + +}} // 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..0cb4afa0ee --- /dev/null +++ b/qpid/cpp/src/tests/qpid-ping.cpp @@ -0,0 +1,76 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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/client/SubscriptionManager.h" +#include "qpid/client/Connection.h" +#include "qpid/client/AsyncSession.h" +#include "qpid/sys/Time.h" +#include "qpid/sys/Thread.h" +#include "qpid/sys/Runnable.h" +#include "qpid/sys/Monitor.h" +#include "qpid/framing/Uuid.h" +#include <string> +#include <iostream> + +using namespace std; +using namespace qpid::sys; +using namespace qpid::framing; +using namespace qpid::client; +using namespace qpid; + +struct PingOptions : public qpid::TestOptions { + int timeout; // Timeout in seconds. + bool quiet; // No output + PingOptions() : timeout(1), quiet(false) { + addOptions() + ("timeout,t", optValue(timeout, "SECONDS"), "Max time to wait.") + ("quiet,q", optValue(quiet), "Don't print anything to stderr/stdout."); + } +}; + +int main(int argc, char** argv) { + try { + PingOptions opts; + opts.parse(argc, argv); + opts.con.heartbeat = (opts.timeout+1)/2; + Connection connection; + opts.open(connection); + if (!opts.quiet) cout << "Opened connection." << endl; + AsyncSession s = connection.newSession(); + string qname(Uuid(true).str()); + s.queueDeclare(arg::queue=qname,arg::autoDelete=true,arg::exclusive=true); + s.messageTransfer(arg::content=Message("hello", qname)); + if (!opts.quiet) cout << "Sent message." << endl; + SubscriptionManager subs(s); + subs.get(qname); + if (!opts.quiet) cout << "Received message." << endl; + s.sync(); + s.close(); + connection.close(); + if (!opts.quiet) cout << "Success." << endl; + return 0; + } catch (const exception& e) { + cerr << "Error: " << e.what() << endl; + 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..9c713e872a --- /dev/null +++ b/qpid/cpp/src/tests/qpid-receive.cpp @@ -0,0 +1,269 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 checkRedelivered; + uint capacity; + uint ackFrequency; + uint tx; + uint rollbackFrequency; + bool printContent; + bool printHeaders; + bool failoverUpdates; + qpid::log::Options log; + bool reportTotal; + uint reportEvery; + bool reportHeader; + string readyAddress; + uint receiveRate; + + Options(const std::string& argv0=std::string()) + : qpid::Options("Options"), + help(false), + url("amqp:tcp:127.0.0.1"), + timeout(0), + forever(false), + messages(0), + ignoreDuplicates(false), + checkRedelivered(false), + capacity(1000), + ackFrequency(100), + tx(0), + rollbackFrequency(0), + printContent(true), + printHeaders(false), + failoverUpdates(false), + log(argv0), + reportTotal(false), + reportEvery(0), + reportHeader(true), + receiveRate(0) + { + 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)") + ("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-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.") + ("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::ostringstream msg; + std::cout << msg << *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"); + +class SequenceTracker +{ + uint lastSn; + public: + SequenceTracker() : lastSn(0) {} + + bool isDuplicate(Message& message) + { + uint sn = message.getProperties()[SN]; + if (lastSn < sn) { + lastSn = sn; + return false; + } else { + return true; + } + } +}; + +}} // 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(opts.capacity); + Message msg; + uint count = 0; + uint txCount = 0; + SequenceTracker sequenceTracker; + 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); + + // 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 (!opts.ignoreDuplicates || !sequenceTracker.isDuplicate(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.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: " << 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; + std::cout << std::endl; + } + if (opts.printContent) + std::cout << msg.getContent() << std::endl;//TODO: handle map or list messages + if (opts.messages && count >= opts.messages) done = true; + } + } else if (opts.checkRedelivered && !msg.getRedelivered()) { + throw qpid::Exception("duplicate sequence number received, message not marked as redelivered!"); + } + 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()) { // 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); + } + 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); + } + // Clear out message properties & content for next iteration. + msg = Message(); // TODO aconway 2010-12-01: should be done by fetch + } + if (opts.reportTotal) reporter.report(); + if (opts.tx) { + if (opts.rollbackFrequency && (++txCount % opts.rollbackFrequency == 0)) { + session.rollback(); + } else { + session.commit(); + } + } else { + session.acknowledge(); + } + session.close(); + connection.close(); + return 0; + } + } 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..ef5e98e2a0 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-send.cpp @@ -0,0 +1,375 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 "TestOptions.h" +#include "Statistics.h" + +#include <fstream> +#include <iostream> +#include <memory> + +using namespace std; +using namespace qpid::messaging; +using namespace qpid::types; + +typedef std::vector<std::string> string_vector; + +namespace qpid { +namespace tests { + +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; + uint flowControl; + bool sequence; + bool timestamp; + + Options(const std::string& argv0=std::string()) + : qpid::Options("Options"), + help(false), + url("amqp:tcp: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), + flowControl(0), + sequence(true), + timestamp(true) + { + 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.") + ("flow-control", qpid::optValue(flowControl,"N"), "Do end to end flow control to limit queue depth to 2*N. 0 means no flow control.") + ("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)") + ("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::ostringstream msg; + std::cout << msg << *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; + } + } + + 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] = 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"); + +}} // namespace qpid::tests + +using namespace qpid::tests; + +class ContentGenerator { + public: + virtual ~ContentGenerator() {} + virtual bool setContent(Message& msg) = 0; +}; + +class GetlineContentGenerator : public ContentGenerator { + public: + virtual bool setContent(Message& msg) { + string content; + bool got = getline(std::cin, content); + if (got) msg.setContent(content); + return got; + } +}; + +class FixedContentGenerator : public ContentGenerator { + public: + FixedContentGenerator(const string& s) : content(s) {} + virtual bool setContent(Message& msg) { + msg.setContent(content); + return true; + } + private: + std::string content; +}; + +class MapContentGenerator : public ContentGenerator { + public: + MapContentGenerator(const Options& opt) : opts(opt) {} + virtual bool setContent(Message& msg) { + Variant::Map map; + opts.setEntries(map); + encode(map, msg); + return true; + } + private: + const Options& opts; +}; + +int main(int argc, char ** argv) +{ + Connection connection; + Options opts; + try { + 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()) { + if (opts.flowControl) + throw Exception("Can't use reply-to and flow-control together"); + msg.setReplyTo(Address(opts.replyto)); + } + if (!opts.userid.empty()) msg.setUserId(opts.userid); + 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)); + + qpid::sys::AbsTime start = qpid::sys::now(); + int64_t interval = 0; + if (opts.sendRate) interval = qpid::sys::TIME_SEC/opts.sendRate; + + Receiver flowControlReceiver; + Address flowControlAddress("flow-"+Uuid(true).str()+";{create:always,delete:always}"); + uint flowSent = 0; + if (opts.flowControl) { + flowControlReceiver = session.createReceiver(flowControlAddress); + flowControlReceiver.setCapacity(2); + } + + while (contentGen->setContent(msg)) { + ++sent; + if (opts.sequence) + msg.getProperties()[SN] = sent; + if (opts.timestamp) + msg.getProperties()[TS] = int64_t( + qpid::sys::Duration(qpid::sys::EPOCH, qpid::sys::now())); + if (opts.flowControl) { + if ((sent % opts.flowControl) == 0) { + msg.setReplyTo(flowControlAddress); + ++flowSent; + } + else + msg.setReplyTo(Address()); // Clear the reply address. + } + 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.flowControl && flowSent == 2) { + flowControlReceiver.get(Duration::SECOND); + --flowSent; + } + + 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); + } + } + for ( ; flowSent>0; --flowSent) + flowControlReceiver.get(Duration::SECOND); + 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; + } + } 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..5e69e0ade1 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-src-rinstall @@ -0,0 +1,31 @@ +#!/bin/sh +# +# 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-test-cluster b/qpid/cpp/src/tests/qpid-test-cluster new file mode 100755 index 0000000000..9887406ef9 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-test-cluster @@ -0,0 +1,109 @@ +#!/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. +# + +usage() { + echo "Usage: `basename $0` [options] start|stop|restart|check [hosts] +Start/stop/restart a cluster on specified hosts or on \$HOSTS via ssh. + +Options: + -l USER Run qpidd and copy files as USER. + -e SCRIPT Source SCRIPT for environment settings. Copies SCRIPT to each host. + Default is $DEFAULT_ENV. + -c CONFIG Use CONFIG as qpidd config file. Copies CONFIG to each host. + Default is $DEFAULT_CONF + -d Delete data-dir and log file before starting broker. +" + exit 1 +} + +DEFAULT_CONF=~/qpid-test-qpidd.conf +DEFAULT_ENV=~/qpid-test-env.sh + +test -f $DEFAULT_CONF && CONF_FILE=$DEFAULT_CONF +test -f $DEFAULT_ENV && ENV_FILE=$DEFAULT_ENV + +while getopts "l:e:c:d" opt; do + case $opt in + l) SSHOPTS="-l$OPTARG $SSHOPTS" ; RSYNC_USER="$OPTARG@" ;; + e) ENV_FILE=$OPTARG ;; + c) CONF_FILE=$OPTARG ;; + d) DO_DELETE=1 ;; + *) usage;; + esac +done +shift `expr $OPTIND - 1` +test "$*" || usage +CMD=$1; shift +HOSTS=${*:-$HOSTS} + +conf_value() { test -f "$CONF_FILE" && awk -F= "/^$1=/ {print \$2}" $CONF_FILE; } + +if test -n "$CONF_FILE"; then + test -f "$CONF_FILE" || { echo Config file not found: $CONF_FILE; exit 1; } + RSYNCFILES="$RSYNCFILES $CONF_FILE" + QPIDD_ARGS="$QPIDD_ARGS --config $CONF_FILE" + CONF_PORT=`conf_value port` + CONF_DATA_DIR=`conf_value data-dir` + CONF_LOG_FILE=`conf_value log-to-file` +fi + +if test -n "$ENV_FILE"; then + test -f "$ENV_FILE" || { echo Environment file not found: $ENV_FILE; exit 1; } + RSYNCFILES="$RSYNCFILES $ENV_FILE" + SOURCE_ENV="source $ENV_FILE ; " +fi + +test -n "$RSYNCFILES" && rsynchosts $RSYNCFILES # Copy conf/env files to all hosts + +do_start() { + for h in $HOSTS; do + COMMAND="qpidd -d $QPIDD_ARGS" + id -nG | grep '\<ais\>' >/dev/null && COMMAND="sg ais -c '$COMMAND'" + if test "$DO_DELETE"; then COMMAND="rm -rf $CONF_DATA_DIR $CONF_LOG_FILE; $COMMAND"; fi + ssh $h "$SOURCE_ENV $COMMAND" || { echo "Failed to start on $h"; exit 1; } + done +} + +do_stop() { + for h in $HOSTS; do + ssh $h "$SOURCE_ENV qpidd -q --no-module-dir --no-data-dir $QPIDD_ARGS" + done +} + +do_status() { + for h in $HOSTS; do + if ssh $h "$SOURCE_ENV qpidd -c --no-module-dir --no-data-dir $QPIDD_ARGS > /dev/null"; then + echo "$h ok" + else + echo "$h not running" + STATUS=1 + fi + done +} + +case $CMD in + start) do_start ;; + stop) do_stop ;; + restart) do_stop ; do_start ;; + status) do_status ;; + *) usage;; +esac + +exit $STATUS 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..d0ba2f1245 --- /dev/null +++ b/qpid/cpp/src/tests/qpid-txtest.cpp @@ -0,0 +1,340 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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/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(); + } + } + } 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) { + std::vector<std::string> inDoubtXids; + framing::DtxRecoverResult dtxRes = session.dtxRecover().get(); + const framing::Array& xidArr = dtxRes.getInDoubt(); + xidArr.collect(inDoubtXids); + + 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/queue_flow_limit_tests.py b/qpid/cpp/src/tests/queue_flow_limit_tests.py new file mode 100644 index 0000000000..dec7cfb3af --- /dev/null +++ b/qpid/cpp/src/tests/queue_flow_limit_tests.py @@ -0,0 +1,371 @@ +#!/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 import datatypes, messaging +from qpid.messaging import Message, Empty +from threading import Thread, Lock +from logging import getLogger +from time import sleep, time +from os import environ, popen + +class QueueFlowLimitTests(TestBase010): + + 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; + + + self.session.queue_declare(queue=name, arguments=args) + + 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 + """ + self.session.queue_delete(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) + + 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-addr=%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() + 10 + 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() + 10 + 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() + 10 + 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" + 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. + 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() + 10 + 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/quick_perftest b/qpid/cpp/src/tests/quick_perftest new file mode 100755 index 0000000000..362f9ee96a --- /dev/null +++ b/qpid/cpp/src/tests/quick_perftest @@ -0,0 +1,22 @@ +#!/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. +# + +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..0a6b29b33f --- /dev/null +++ b/qpid/cpp/src/tests/quick_topictest @@ -0,0 +1,30 @@ +#!/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. +# + + +# 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..c872fcec12 --- /dev/null +++ b/qpid/cpp/src/tests/quick_txtest @@ -0,0 +1,22 @@ +#!/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. +# + +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/reliable_replication_test b/qpid/cpp/src/tests/reliable_replication_test new file mode 100755 index 0000000000..f57d11a263 --- /dev/null +++ b/qpid/cpp/src/tests/reliable_replication_test @@ -0,0 +1,93 @@ +#!/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. +# + +# Test reliability of the replication feature in the face of link +# failures: + +source ./test_env.sh + +trap stop_brokers EXIT + +stop_brokers() { + if [[ $BROKER_A ]] ; then + $QPIDD_EXEC --no-module-dir -q --port $BROKER_A + unset BROKER_A + fi + if [[ $BROKER_B ]] ; then + $QPIDD_EXEC --no-module-dir -q --port $BROKER_B + unset BROKER_B + fi +} + +setup() { + rm -f replication-source.log replication-dest.log + $QPIDD_EXEC --daemon --port 0 --no-data-dir --no-module-dir --auth no --load-module $REPLICATING_LISTENER_LIB --replication-queue replication --create-replication-queue true --log-enable trace+ --log-to-file replication-source.log --log-to-stderr 0 > qpidd-repl.port + BROKER_A=`cat qpidd-repl.port` + + $QPIDD_EXEC --daemon --port 0 --no-data-dir --no-module-dir --auth no --load-module $REPLICATION_EXCHANGE_LIB --log-enable info+ --log-to-file replication-dest.log --log-to-stderr 0 > qpidd-repl.port + BROKER_B=`cat qpidd-repl.port` + + echo "Testing replication from port $BROKER_A to port $BROKER_B" + + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_B" add exchange replication replication + $PYTHON_COMMANDS/qpid-route --ack 500 queue add "localhost:$BROKER_B" "localhost:$BROKER_A" replication replication + + #create test queue (only replicate enqueues for this test): + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_A" add queue queue-a --generate-queue-events 1 + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_B" add queue queue-a +} + +send() { + ./sender --port $BROKER_A --routing-key queue-a --send-eos 1 < replicated.expected +} + +receive() { + rm -f replicated.actual + ./receiver --port $BROKER_B --queue queue-a > replicated.actual +} + +bounce_link() { + echo "Destroying link..." + $PYTHON_COMMANDS/qpid-route link del "localhost:$BROKER_B" "localhost:$BROKER_A" + echo "Link destroyed; recreating route..." + sleep 2 + $PYTHON_COMMANDS/qpid-route --ack 500 queue add "localhost:$BROKER_B" "localhost:$BROKER_A" replication replication + echo "Route re-established" +} + +if test -d ${PYTHON_DIR} && test -e $REPLICATING_LISTENER_LIB && test -e $REPLICATION_EXCHANGE_LIB ; then + setup + for i in `seq 1 100000`; do echo Message $i; done > replicated.expected + send & + receive & + for i in `seq 1 5`; do sleep 10; bounce_link; done; + wait + #check that received list is identical to sent list + diff replicated.actual replicated.expected || FAIL=1 + if [[ $FAIL ]]; then + echo reliable replication test failed: expectations not met! + exit 1 + else + echo replication reliable in the face of link failures + rm -f replication.actual replication.expected replication-source.log replication-dest.log qpidd-repl.port + fi +fi + 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/replication_test b/qpid/cpp/src/tests/replication_test new file mode 100755 index 0000000000..8c37568875 --- /dev/null +++ b/qpid/cpp/src/tests/replication_test @@ -0,0 +1,182 @@ +#!/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. +# + +# Run a test of the replication feature + +source ./test_env.sh + +trap stop_brokers INT TERM QUIT + +stop_brokers() { + if [ x$BROKER_A != x ]; then + $QPIDD_EXEC --no-module-dir -q --port $BROKER_A + unset BROKER_A + fi + if [ x$BROKER_B != x ]; then + $QPIDD_EXEC --no-module-dir -q --port $BROKER_B + unset BROKER_B + fi +} + +if test -d ${PYTHON_DIR} && test -f "$REPLICATING_LISTENER_LIB" && test -f "$REPLICATION_EXCHANGE_LIB"; then + rm -f queue-*.repl replication-*.log #cleanup from any earlier runs + + $QPIDD_EXEC --daemon --port 0 --no-data-dir --no-module-dir --auth no --load-module $REPLICATING_LISTENER_LIB --replication-queue replication --create-replication-queue true --log-enable info+ --log-to-file replication-source.log --log-to-stderr 0 > qpidd.port + BROKER_A=`cat qpidd.port` + + $QPIDD_EXEC --daemon --port 0 --no-data-dir --no-module-dir --auth no --load-module $REPLICATION_EXCHANGE_LIB --log-enable info+ --log-to-file replication-dest.log --log-to-stderr 0 > qpidd.port + BROKER_B=`cat qpidd.port` + echo "Running replication test between localhost:$BROKER_A and localhost:$BROKER_B" + + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_B" add exchange replication replication + $PYTHON_COMMANDS/qpid-route --ack 5 queue add "localhost:$BROKER_B" "localhost:$BROKER_A" replication replication + + #create test queues + + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_A" add queue queue-a --generate-queue-events 2 + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_A" add queue queue-b --generate-queue-events 2 + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_A" add queue queue-c --generate-queue-events 1 + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_A" add queue queue-d --generate-queue-events 2 + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_A" add queue queue-e --generate-queue-events 1 + + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_B" add queue queue-a + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_B" add queue queue-b + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_B" add queue queue-c + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_B" add queue queue-e + #queue-d deliberately not declared on DR; this error case should be handled + + #publish and consume from test queues on broker A: + i=1 + while [ $i -le 10 ]; do + echo Message $i for A >> queue-a-input.repl + i=`expr $i + 1` + done + i=1 + while [ $i -le 20 ]; do + echo Message $i for B >> queue-b-input.repl + i=`expr $i + 1` + done + i=1 + while [ $i -le 15 ]; do + echo Message $i for C >> queue-c-input.repl + i=`expr $i + 1` + done + + ./sender --port $BROKER_A --routing-key queue-a --send-eos 1 < queue-a-input.repl + ./sender --port $BROKER_A --routing-key queue-b --send-eos 1 < queue-b-input.repl + ./sender --port $BROKER_A --routing-key queue-c --send-eos 1 < queue-c-input.repl + echo dummy | ./sender --port $BROKER_A --routing-key queue-d --send-eos 1 + + ./receiver --port $BROKER_A --queue queue-a --messages 5 > /dev/null + ./receiver --port $BROKER_A --queue queue-b --messages 10 > /dev/null + ./receiver --port $BROKER_A --queue queue-c --messages 10 > /dev/null + ./receiver --port $BROKER_A --queue queue-d > /dev/null + + + # What we are doing is putting a message on the end of repliaction queue & waiting for it on remote side + # making sure all the messages have been flushed from the replication queue. + echo dummy | ./sender --port $BROKER_A --routing-key queue-e --send-eos 1 + ./receiver --port $BROKER_B --queue queue-e --messages 1 > /dev/null + + #shutdown broker A then check that broker Bs versions of the queues are as expected + $QPIDD_EXEC --no-module-dir -q --port $BROKER_A + unset BROKER_A + + #validate replicated queues: + ./receiver --port $BROKER_B --queue queue-a > queue-a-backup.repl + ./receiver --port $BROKER_B --queue queue-b > queue-b-backup.repl + ./receiver --port $BROKER_B --queue queue-c > queue-c-backup.repl + + + tail -5 queue-a-input.repl > queue-a-expected.repl + tail -10 queue-b-input.repl > queue-b-expected.repl + diff queue-a-backup.repl queue-a-expected.repl || FAIL=1 + diff queue-b-backup.repl queue-b-expected.repl || FAIL=1 + diff queue-c-backup.repl queue-c-input.repl || FAIL=1 + + grep 'queue-d does not exist' replication-dest.log > /dev/null || echo "WARNING: Expected error to be logged!" + + stop_brokers + + # now check offsets working (enqueue based on position being set, not queue abs position) + + $QPIDD_EXEC --daemon --port 0 --no-data-dir --no-module-dir --auth no --load-module $REPLICATING_LISTENER_LIB --replication-queue replication --create-replication-queue true --log-enable info+ --log-to-file replication-source.log --log-to-stderr 0 > qpidd.port + BROKER_A=`cat qpidd.port` + + $QPIDD_EXEC --daemon --port 0 --no-data-dir --no-module-dir --auth no --load-module $REPLICATION_EXCHANGE_LIB --log-enable info+ --log-to-file replication-dest.log --log-to-stderr 0 > qpidd.port + BROKER_B=`cat qpidd.port` + + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_B" add exchange replication replication + $PYTHON_COMMANDS/qpid-route --ack 5 queue add "localhost:$BROKER_B" "localhost:$BROKER_A" replication replication + + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_A" add queue queue-e --generate-queue-events 2 + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_B" add queue queue-e + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_A" add queue queue-d --generate-queue-events 1 + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_B" add queue queue-d + + i=1 + while [ $i -le 10 ]; do + echo Message $i for A >> queue-e-input.repl + i=`expr $i + 1` + done + + ./sender --port $BROKER_A --routing-key queue-e --send-eos 1 < queue-e-input.repl + ./receiver --port $BROKER_A --queue queue-e --messages 10 > /dev/null + + # What we are doing is putting a message on the end of repliaction queue & waiting for it on remote side + # making sure all the messages have been flushed from the replication queue. + echo dummy | ./sender --port $BROKER_A --routing-key queue-d --send-eos 1 + ./receiver --port $BROKER_B --queue queue-d --messages 1 > /dev/null + + # now check offsets working + $QPIDD_EXEC --no-module-dir -q --port $BROKER_B + unset BROKER_B + $QPIDD_EXEC --daemon --port 0 --no-data-dir --no-module-dir --auth no --load-module $REPLICATION_EXCHANGE_LIB --log-enable info+ --log-to-file replication-dest.log --log-to-stderr 0 > qpidd.port + BROKER_B=`cat qpidd.port` + + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_B" add queue queue-e + $PYTHON_COMMANDS/qpid-config -a "localhost:$BROKER_B" add exchange replication replication + $PYTHON_COMMANDS/qpid-route --ack 5 queue add "localhost:$BROKER_B" "localhost:$BROKER_A" replication replication + # now send another 15 + i=11 + while [ $i -le 15 ]; do + echo Message $i for A >> queue-e1-input.repl + i=`expr $i + 1` + done + ./sender --port $BROKER_A --routing-key queue-e --send-eos 1 < queue-e1-input.repl + + ./receiver --port $BROKER_B --queue queue-e > queue-e-backup.repl + diff queue-e-backup.repl queue-e1-input.repl || FAIL=1 + + stop_brokers + + if [ x$FAIL != x ]; then + echo replication test failed: expectations not met! + exit 1 + else + echo queue state replicated as expected + rm -f queue-*.repl replication-*.log + fi + +else + echo "Skipping replication test, plugins not built or python utils not located" +fi + diff --git a/qpid/cpp/src/tests/restart_cluster b/qpid/cpp/src/tests/restart_cluster new file mode 100755 index 0000000000..6a6abc8042 --- /dev/null +++ b/qpid/cpp/src/tests/restart_cluster @@ -0,0 +1,38 @@ +#!/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. +# + +# Re-start a cluster on the local host. + +srcdir=`dirname $0` +$srcdir/stop_cluster +exec $srcdir/start_cluster "$@" +#!/bin/sh +# Re-start a cluster on the local host. + +srcdir=`dirname $0` +$srcdir/stop_cluster +exec $srcdir/start_cluster "$@" +#!/bin/sh +# Re-start a cluster on the local host. + +srcdir=`dirname $0` +$srcdir/stop_cluster +exec $srcdir/start_cluster "$@" 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..553746eb49 --- /dev/null +++ b/qpid/cpp/src/tests/ring_queue_test @@ -0,0 +1,174 @@ +#!/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. +# + +# 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="-a ${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..56ee57e898 --- /dev/null +++ b/qpid/cpp/src/tests/rsynchosts @@ -0,0 +1,49 @@ +#!/bin/sh +# +# 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) file [file...] +Synchronize the contents of each file or directory to the same absolute path on +each host in \$HOSTS. +" + exit 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 -aRO --delete $FILES $h:/ || { echo "rsync to $h failed"; rm -f $OK_FILE; } & +done +wait +test -f $OK_FILE + diff --git a/qpid/cpp/src/tests/run-unit-tests b/qpid/cpp/src/tests/run-unit-tests new file mode 100755 index 0000000000..862a76c4f5 --- /dev/null +++ b/qpid/cpp/src/tests/run-unit-tests @@ -0,0 +1,48 @@ +#!/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. +# + +# +# Library names (without path or .so) and CppUnit test paths can be +# specified on the command line or in env var UNIT_TESTS. For example: +# +# Selected test classes: +# ./run-unit-tests ValueTest ClientChannelTest +# +# Individual test method +# ./run-unit-tests ValueTest :ValueTest::testStringValueEquals +# +# Build and run selected tests: +# make check TESTS=run-unit-tests UNIT_TESTS=ClientChannelTest +# + +for u in $* $UNIT_TESTS ; do + case $u in + :*) TEST_ARGS="$TEST_ARGS $u" ;; # A test path. + *) TEST_ARGS="$TEST_ARGS .libs/$u.so" ;; # A test library. + esac +done +test -z "$TEST_ARGS" && TEST_ARGS=".libs/*Test.so" + +test -z "$srcdir" && srcdir=. + +# libdlclose_noop prevents unloading symbols needed for valgrind output. +export LD_PRELOAD=.libs/libdlclose_noop.so +source $srcdir/run_test DllPlugInTester -c -b $TEST_ARGS diff --git a/qpid/cpp/src/tests/run_acl_tests b/qpid/cpp/src/tests/run_acl_tests new file mode 100755 index 0000000000..41f41e20e1 --- /dev/null +++ b/qpid/cpp/src/tests/run_acl_tests @@ -0,0 +1,62 @@ +#!/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. +# + +# Run the acl tests. $srcdir is set by the Makefile. +source ./test_env.sh +DATA_DIR=`pwd`/data_dir + +trap stop_brokers INT TERM QUIT + +start_brokers() { + ../qpidd --daemon --port 0 --no-module-dir --data-dir $DATA_DIR --load-module $ACL_LIB --acl-file policy.acl --auth no > qpidd.port + LOCAL_PORT=`cat qpidd.port` +} + +stop_brokers() { + $QPIDD_EXEC --no-module-dir -q --port $LOCAL_PORT +} + +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 +} + +if test -d ${PYTHON_DIR} ; then + rm -rf $DATA_DIR + mkdir -p $DATA_DIR + cp $srcdir/policy.acl $DATA_DIR + start_brokers + echo "Running acl tests using brokers on ports $LOCAL_PORT" + $QPID_PYTHON_TEST -b localhost:$LOCAL_PORT -m acl || EXITCODE=1 + stop_brokers || EXITCODE=1 + test_loading_acl_from_absolute_path || EXITCODE=1 + rm -rf $DATA_DIR + 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..a1848779c7 --- /dev/null +++ b/qpid/cpp/src/tests/run_acl_tests.ps1 @@ -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. +# + +# Run the acl tests. + +$srcdir = Split-Path $myInvocation.InvocationName +$PYTHON_DIR = "$srcdir\..\..\..\python" +if (!(Test-Path $PYTHON_DIR -pathType Container)) { + "Skipping acl tests as python libs not found" + exit 1 +} + +$PYTHON_TEST_DIR = "$srcdir\..\..\..\tests\src\py" +$QMF_LIB = "$srcdir\..\..\..\extras\qmf\src\py" +$env:PYTHONPATH="$PYTHON_DIR;$srcdir;$PYTHON_TEST_DIR;$QMF_LIB" +$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 10)) { + 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..ec5c71b646 --- /dev/null +++ b/qpid/cpp/src/tests/run_cli_tests @@ -0,0 +1,81 @@ +#!/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. +# + +# 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 ../.libs/xml.so ] ; then + xargs="--load-module ../.libs/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 --no-data-dir --no-module-dir --auth no $xargs > qpidd.port + LOCAL_PORT=`cat qpidd.port` + ../qpidd --daemon --port 0 --no-data-dir --no-module-dir --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_cluster_authentication_soak b/qpid/cpp/src/tests/run_cluster_authentication_soak new file mode 100755 index 0000000000..7bc406c4ca --- /dev/null +++ b/qpid/cpp/src/tests/run_cluster_authentication_soak @@ -0,0 +1,26 @@ +#! /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. + + +source ./test_env.sh +source $srcdir/ais_check +source sasl_test_setup.sh + +with_ais_group ./cluster_authentication_soak 500 + diff --git a/qpid/cpp/src/tests/run_cluster_authentication_test b/qpid/cpp/src/tests/run_cluster_authentication_test new file mode 100755 index 0000000000..647200b869 --- /dev/null +++ b/qpid/cpp/src/tests/run_cluster_authentication_test @@ -0,0 +1,26 @@ +#! /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. + + +source ./test_env.sh +source $srcdir/ais_check +source sasl_test_setup.sh + +with_ais_group ./cluster_authentication_soak + diff --git a/qpid/cpp/src/tests/run_cluster_test b/qpid/cpp/src/tests/run_cluster_test new file mode 100755 index 0000000000..c022eea1fe --- /dev/null +++ b/qpid/cpp/src/tests/run_cluster_test @@ -0,0 +1,26 @@ +#!/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. +# + + +# Run the tests +srcdir=`dirname $0` +. $srcdir/ais_check +with_ais_group $srcdir/run_test ./cluster_test diff --git a/qpid/cpp/src/tests/run_cluster_tests b/qpid/cpp/src/tests/run_cluster_tests new file mode 100755 index 0000000000..e136d3810a --- /dev/null +++ b/qpid/cpp/src/tests/run_cluster_tests @@ -0,0 +1,37 @@ +#!/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. +# + +source ./test_env.sh +source $srcdir/ais_check + +test -x $QPID_PYTHON_TEST || { echo Skipping test, $QPID_PYTHON_TEST not found; exit 0; } + +# Delete old cluster test data +OUTDIR=${OUTDIR:-brokertest.tmp} +rm -rf $OUTDIR +mkdir -p $OUTDIR + +# Ignore tests requiring a store by default. +CLUSTER_TESTS_IGNORE=${CLUSTER_TESTS_IGNORE:--i cluster_tests.StoreTests.* -I $srcdir/cluster_tests.fail} +CLUSTER_TESTS=${CLUSTER_TESTS:-$*} + +with_ais_group $QPID_PYTHON_TEST -DOUTDIR=$OUTDIR -m cluster_tests $CLUSTER_TESTS_IGNORE $CLUSTER_TESTS || exit 1 +rm -rf $OUTDIR diff --git a/qpid/cpp/src/tests/run_failover_soak b/qpid/cpp/src/tests/run_failover_soak new file mode 100755 index 0000000000..cce8b07a26 --- /dev/null +++ b/qpid/cpp/src/tests/run_failover_soak @@ -0,0 +1,38 @@ +#!/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. +# + +source ./test_env.sh +. $srcdir/ais_check + +host=127.0.0.1 + +unset QPID_NO_MODULE_DIR # failover_soak uses --module-dir, dont want clash +MODULES=${MODULES:-$moduledir} +MESSAGES=${MESSAGES:-500000} +REPORT_FREQUENCY=${REPORT_FREQUENCY:-20000} +VERBOSITY=${VERBOSITY:-0} +DURABILITY=${DURABILITY:-0} +N_QUEUES=${N_QUEUES:-1} +N_BROKERS=${N_BROKERS:-4} + +rm -f soak-*.log +exec ./failover_soak $MODULES ./declare_queues ./replaying_sender ./resuming_receiver $MESSAGES $REPORT_FREQUENCY $VERBOSITY $DURABILITY $N_QUEUES $N_BROKERS + diff --git a/qpid/cpp/src/tests/run_federation_tests b/qpid/cpp/src/tests/run_federation_tests new file mode 100755 index 0000000000..590f74746e --- /dev/null +++ b/qpid/cpp/src/tests/run_federation_tests @@ -0,0 +1,64 @@ +#!/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. +# + +# Run the federation tests. + +source ./test_env.sh + +trap stop_brokers INT TERM QUIT + +if [ -f ../.libs/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" +fi + +start_brokers() { + ../qpidd --daemon --port 0 --no-data-dir $MODULES --auth no > qpidd.port + LOCAL_PORT=`cat qpidd.port` + ../qpidd --daemon --port 0 --no-data-dir $MODULES --auth no > qpidd.port + REMOTE_PORT=`cat qpidd.port` + + ../qpidd --daemon --port 0 --no-data-dir $MODULES --auth no > qpidd.port + REMOTE_B1=`cat qpidd.port` + ../qpidd --daemon --port 0 --no-data-dir $MODULES --auth no > qpidd.port + REMOTE_B2=`cat qpidd.port` +} + +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..35353a870f --- /dev/null +++ b/qpid/cpp/src/tests/run_federation_tests.ps1 @@ -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. +# + +# 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 +} + +$PYTHON_TEST_DIR = "$srcdir\..\..\..\tests\src\py" +$QMF_LIB = "$srcdir\..\..\..\extras\qmf\src\py" + +# 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_header_test b/qpid/cpp/src/tests/run_header_test new file mode 100755 index 0000000000..34008132cc --- /dev/null +++ b/qpid/cpp/src/tests/run_header_test @@ -0,0 +1,37 @@ +#!/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. +# + +# 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=`dirname $0` +source ./test_env.sh + +test -f qpidd.port && QPID_PORT=`cat qpidd.port` + +if test -d ${PYTHON_DIR} ; then + ./header_test -p $QPID_PORT + $srcdir/header_test.py "localhost" $QPID_PORT +else + echo "Skipping header test as python libs not found" +fi + 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..7d3e43a30f --- /dev/null +++ b/qpid/cpp/src/tests/run_header_test.ps1 @@ -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. +# + +# 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 +} + +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 +$env:PYTHONPATH="$PYTHON_DIR;$env:PYTHONPATH" +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..a4584e6884 --- /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 --no-data-dir --no-module-dir --auth no > qpidd.port + LOCAL_PORT=`cat qpidd.port` + ../qpidd --daemon --port 0 --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_long_cluster_tests b/qpid/cpp/src/tests/run_long_cluster_tests new file mode 100755 index 0000000000..5dce0be585 --- /dev/null +++ b/qpid/cpp/src/tests/run_long_cluster_tests @@ -0,0 +1,24 @@ +#!/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. +# + +srcdir=`dirname $0` +$srcdir/run_cluster_tests 'cluster_tests.LongTests.*' -DDURATION=4 + diff --git a/qpid/cpp/src/tests/run_perftest b/qpid/cpp/src/tests/run_perftest new file mode 100755 index 0000000000..5ad7c1ff4f --- /dev/null +++ b/qpid/cpp/src/tests/run_perftest @@ -0,0 +1,28 @@ +#!/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. +# + +# 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..f921cf5e7e --- /dev/null +++ b/qpid/cpp/src/tests/run_queue_flow_limit_tests @@ -0,0 +1,57 @@ +#!/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 tests against Queue producer flow control. + +source ./test_env.sh +test -d $PYTHON_DIR || { echo "Skipping queue flow control tests, no python dir."; exit 0; } + +LOG_FILE=queue_flow_limit_test.log +PORT="" + +trap stop_broker INT TERM QUIT + +error() { + echo $* + exit 1; +} + +start_broker() { + # Note: if you change the DEFAULT_THRESHOLDS, you will need to update queue_flow_limit_tests.py + DEFAULT_THRESHOLDS="--default-flow-stop-threshold=80 --default-flow-resume-threshold=70" + rm -rf $LOG_FILE + PORT=$($QPIDD_EXEC $DEFAULT_THRESHOLDS --auth=no --no-module-dir --daemon --port=0 -t --log-to-file $LOG_FILE) || error "Could not start broker" +} + +stop_broker() { + test -n "$PORT" && $QPIDD_EXEC --no-module-dir --quit --port $PORT +} + +start_broker +echo "Running Queue flow limit tests using broker on port $PORT" +$QPID_PYTHON_TEST -m queue_flow_limit_tests $SKIPTESTS -b localhost:$PORT $@ +RETCODE=$? +stop_broker +if test x$RETCODE != x0; then + echo "FAIL queue flow limit tests"; exit 1; +fi +rm -rf $LOG_FILE + 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..7ca870841e --- /dev/null +++ b/qpid/cpp/src/tests/run_ring_queue_test @@ -0,0 +1,36 @@ +#!/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. +# +#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..76b46737f0 --- /dev/null +++ b/qpid/cpp/src/tests/run_store_tests.ps1 @@ -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. +# + +# 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 +$PYTHON_DIR = "$srcdir\..\..\..\python" +if (!(Test-Path $PYTHON_DIR -pathType Container)) { + "Skipping store tests as python libs not found" + exit 1 +} + +$QMF_LIB = "$srcdir\..\..\..\extras\qmf\src\py" + +# 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_DIR;$PYTHON_TEST_DIR;$env:PYTHONPATH;$QMF_LIB" +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:PYTHONPATH="$PYTHON_DIR;$srcdir" +$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..6ec1fd892b --- /dev/null +++ b/qpid/cpp/src/tests/run_test @@ -0,0 +1,82 @@ +#!/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. +# + +# +# 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. +# + +srcdir=`dirname $0` +source ./test_env.sh +source $srcdir/vg_check + +# Export variables from makefile. +export srcdir + +# Set QPID_PORT if qpidd.port exists. +test -s qpidd.port && QPID_PORT=`cat qpidd.port` +export QPID_PORT + +# Avoid silly libtool error messages if these are not defined +test -z "$LC_ALL" && LC_ALL= +test -z "$LC_CTYPE" && LC_CTYPE= +test -z "$LC_COLLATE" && LC_COLLATE= +test -z "$LC_MESSAGES" && LC_MESSAGES= +export LC_ALL LC_CTYPE LC_COLLATE LC_MESSAGES + +VG_LOG="`basename $1`.vglog" +rm -f $VG_LOG* + +# 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 +--log-file=$VG_LOG -- +" +ERROR=0 +if grep -l "^# Generated by .*libtool" "$1" >/dev/null 2>&1; then + # This is a libtool "executable". Valgrind it if VALGRIND specified. + test -n "$VALGRIND" && VALGRIND="$VALGRIND $VALGRIND_OPTS" + # Hide output unless there's an error. + $LIBTOOL --mode=execute $VALGRIND "$@" 2>&1 || ERROR=1 + test -n "$VALGRIND" && { vg_check $VG_LOG* || ERROR=1 ; } +elif file $1 | grep -q text; then + # This is a non-libtool shell script, just execute it. + exec "$@" +else + # This is a real executable, valgrind it. + test -n "$VALGRIND" && VALGRIND="$VALGRIND $VALGRIND_OPTS" + # Hide output unless there's an error. + $VALGRIND "$@" 2>&1 || ERROR=1 + test -n "$VALGRIND" && { vg_check $VG_LOG* || ERROR=1 ; } +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..ca990bc057 --- /dev/null +++ b/qpid/cpp/src/tests/run_test.ps1 @@ -0,0 +1,69 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +$srcdir = Split-Path $myInvocation.InvocationName + +# Set up environment and run a test executable or script. +$env:QPID_DATA_DIR = "" +$env:BOOST_TEST_SHOW_PROGRESS = "yes" + +# 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 = $args[0] +$is_script = $prog -match ".ps1$" +if (!$is_script) { + . $srcdir\find_prog.ps1 $prog + $args[0] = $prog + $env:QPID_LIB_DIR = "..\$sub" + $env:PATH += ";$dir\$sub;..\$sub" +} + +# 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) +} + +$si = new-object System.Diagnostics.ProcessStartInfo +$si.WorkingDirectory = $pwd +$si.UseShellExecute = $false +$si.CreateNoWindow = $true +$si.RedirectStandardOutput = $true +if ($is_script) { + $si.FileName = (get-command powershell.exe).Definition + $si.Arguments = $args +} +else { + $si.FileName = $args[0] + if ($args.length -gt 1) { + $si.Arguments = $args[1..($args.length-1)] + } +} +$p = [System.Diagnostics.Process]::Start($si) +$line = "" +while (($line = $p.StandardOutput.ReadLine()) -ne $null) { + $line +} +# ReadToEnd() works, but doesn't show any output until the program exits. +#$p.StandardOutput.ReadToEnd() +$p.WaitForExit() +$status = $p.ExitCode +exit $status diff --git a/qpid/cpp/src/tests/sasl.mk b/qpid/cpp/src/tests/sasl.mk new file mode 100644 index 0000000000..20eaa7c7a5 --- /dev/null +++ b/qpid/cpp/src/tests/sasl.mk @@ -0,0 +1,49 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT 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 that are only relevant if SASL is enabled. +if HAVE_SASL + +check_PROGRAMS+=cluster_authentication_soak +cluster_authentication_soak_INCLUDES=$(PUBLIC_INCLUDES) +cluster_authentication_soak_SOURCES=cluster_authentication_soak.cpp ForkedBroker.h ForkedBroker.cpp +cluster_authentication_soak_LDADD=$(lib_client) $(lib_broker) + +# Note: sasl_version is not a test -- it is a tool used by tests. +check_PROGRAMS+=sasl_version +sasl_version_SOURCES=sasl_version.cpp +sasl_version_LDADD=$(lib_client) + +TESTS += run_cluster_authentication_test sasl_fed sasl_fed_ex_dynamic sasl_fed_ex_link sasl_fed_ex_queue sasl_fed_ex_route sasl_fed_ex_route_cluster sasl_fed_ex_link_cluster sasl_fed_ex_queue_cluster sasl_fed_ex_dynamic_cluster +LONG_TESTS += run_cluster_authentication_soak +EXTRA_DIST += run_cluster_authentication_test \ + sasl_fed \ + sasl_fed_ex \ + run_cluster_authentication_soak \ + sasl_fed_ex_dynamic \ + sasl_fed_ex_link \ + sasl_fed_ex_queue \ + sasl_fed_ex_route \ + sasl_fed_ex_dynamic_cluster \ + sasl_fed_ex_link_cluster \ + sasl_fed_ex_queue_cluster \ + sasl_fed_ex_route_cluster + + +endif # HAVE_SASL diff --git a/qpid/cpp/src/tests/sasl_fed b/qpid/cpp/src/tests/sasl_fed new file mode 100755 index 0000000000..884c44177c --- /dev/null +++ b/qpid/cpp/src/tests/sasl_fed @@ -0,0 +1,166 @@ +#! /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. +# + +source ./test_env.sh + +# 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=$builddir/sasl_config + +my_random_number=$RANDOM +tmp_root=/tmp/sasl_fed_$my_random_number +mkdir -p $tmp_root + + +#-------------------------------------------------- +#echo " Starting broker 1" +#-------------------------------------------------- +$QPIDD_EXEC \ + -p 0 \ + --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 \ + -d > $tmp_root/broker_1_port + +broker_1_port=`cat $tmp_root/broker_1_port` + + +#-------------------------------------------------- +#echo " Starting broker 2" +#-------------------------------------------------- +$QPIDD_EXEC \ + -p 0 \ + --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 \ + -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 -a localhost:$broker_1_port add exchange direct $EXCHANGE_NAME +$QPID_CONFIG_EXEC -a localhost:$broker_2_port add exchange direct $EXCHANGE_NAME + + +#-------------------------------------------------- +#echo " add queues" +#-------------------------------------------------- +$QPID_CONFIG_EXEC -a localhost:$broker_1_port add queue $QUEUE_NAME +$QPID_CONFIG_EXEC -a localhost:$broker_2_port add queue $QUEUE_NAME + +sleep 5 + +#-------------------------------------------------- +#echo " create bindings" +#-------------------------------------------------- +$QPID_CONFIG_EXEC -a localhost:$broker_1_port bind $EXCHANGE_NAME $QUEUE_NAME $ROUTING_KEY +$QPID_CONFIG_EXEC -a 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 " +#-------------------------------------------------- +$builddir/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 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 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..716a806874 --- /dev/null +++ b/qpid/cpp/src/tests/sasl_fed_ex @@ -0,0 +1,361 @@ +#! /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. +# + +#=============================================================================== +# 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 ./test_env.sh + +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 [cluster]" + echo + exit 1 +fi + +# Has the user told us to do clustering ? ----------- +clustering_flag= +if [ $# -eq "2" ] && [ "$2" == "cluster" ]; then + clustering_flag=true +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 + +# 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_ex." + 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 /usr/bin/certutil 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=$builddir/sasl_config + +tmp_root=${builddir}/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 + +CLUSTER_NAME_SUFFIX=`hostname | tr '.' ' ' | awk '{print $1}'` +CLUSTER_1_NAME=sasl_fed_ex_cluster_1_${CLUSTER_NAME_SUFFIX} +CLUSTER_2_NAME=sasl_fed_ex_cluster_2_${CLUSTER_NAME_SUFFIX} + +print "CLUSTER_1_NAME == ${CLUSTER_1_NAME}" +print "CLUSTER_2_NAME == ${CLUSTER_2_NAME}" + +SSL_LIB=${moduledir}/ssl.so +CLUSTER_LIB=${moduledir}/cluster.so + +export QPID_SSL_CERT_NAME=${TEST_HOSTNAME} + +export QPID_NO_MODULE_DIR=1 +export QPID_LOAD_MODULE=$SSLCONNECTOR_LIB +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 \ + --load-module ${SSL_LIB} \ + --mgmt-enable=yes \ + --log-enable info+ \ + --log-source yes \ + --daemon " + + +function start_brokers { + if [ $1 ]; then + # clustered ---------------------------------------- + print "Starting SRC cluster" + + print " src broker 1" + $QPIDD_EXEC \ + --port=${SRC_TCP_PORT} \ + --ssl-port ${SRC_SSL_PORT} \ + ${COMMON_BROKER_OPTIONS} \ + --load-module ${CLUSTER_LIB} \ + --cluster-name ${CLUSTER_1_NAME} \ + --log-to-file $tmp_root/qpidd_src.log 2> /dev/null + + broker_ports[0]=${SRC_TCP_PORT} + + print " src broker 2" + $QPIDD_EXEC \ + --port=${SRC_TCP_PORT_2} \ + --ssl-port ${SRC_SSL_PORT_2} \ + ${COMMON_BROKER_OPTIONS} \ + --load-module ${CLUSTER_LIB} \ + --cluster-name ${CLUSTER_1_NAME} \ + --log-to-file $tmp_root/qpidd_src_2.log 2> /dev/null + + broker_ports[1]=${SRC_TCP_PORT_2} + + + print "Starting DST cluster" + + print " dst broker 1" + $QPIDD_EXEC \ + --port=${DST_TCP_PORT} \ + --ssl-port ${DST_SSL_PORT} \ + ${COMMON_BROKER_OPTIONS} \ + --load-module ${CLUSTER_LIB} \ + --cluster-name ${CLUSTER_2_NAME} \ + --log-to-file $tmp_root/qpidd_dst.log 2> /dev/null + + broker_ports[2]=${DST_TCP_PORT} + + print " dst broker 2" + $QPIDD_EXEC \ + --port=${DST_TCP_PORT_2} \ + --ssl-port ${DST_SSL_PORT_2} \ + ${COMMON_BROKER_OPTIONS} \ + --load-module ${CLUSTER_LIB} \ + --cluster-name ${CLUSTER_2_NAME} \ + --log-to-file $tmp_root/qpidd_dst_2.log 2> /dev/null + + broker_ports[3]=${DST_TCP_PORT_2} + + else + # 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} + fi +} + + +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 $clustering_flag + + +# 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 -a localhost:${SRC_TCP_PORT} add exchange direct $EXCHANGE_NAME +$QPID_CONFIG_EXEC -a localhost:${DST_TCP_PORT} add exchange direct $EXCHANGE_NAME + + +print "add queues" +$QPID_CONFIG_EXEC -a localhost:${SRC_TCP_PORT} add queue $QUEUE_NAME +$QPID_CONFIG_EXEC -a localhost:${DST_TCP_PORT} add queue $QUEUE_NAME + + +print "create bindings" +$QPID_CONFIG_EXEC -a localhost:${SRC_TCP_PORT} bind $EXCHANGE_NAME $QUEUE_NAME $ROUTING_KEY +$QPID_CONFIG_EXEC -a 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 + +# This should work the same whether or not we are running a clustered test. +# In the case of clustered tests, the status is not printed by qpid_route. +# So in either case, I will 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_fed_ex_dynamic b/qpid/cpp/src/tests/sasl_fed_ex_dynamic new file mode 100755 index 0000000000..c20b8d69a0 --- /dev/null +++ b/qpid/cpp/src/tests/sasl_fed_ex_dynamic @@ -0,0 +1,27 @@ +#! /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. +# + + +source ./test_env.sh + +${srcdir}/sasl_fed_ex dynamic + + diff --git a/qpid/cpp/src/tests/sasl_fed_ex_dynamic_cluster b/qpid/cpp/src/tests/sasl_fed_ex_dynamic_cluster new file mode 100755 index 0000000000..b0cceccecb --- /dev/null +++ b/qpid/cpp/src/tests/sasl_fed_ex_dynamic_cluster @@ -0,0 +1,28 @@ +#! /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. +# + + +source ./test_env.sh +source $srcdir/ais_check + +with_ais_group ${srcdir}/sasl_fed_ex dynamic cluster + + diff --git a/qpid/cpp/src/tests/sasl_fed_ex_link b/qpid/cpp/src/tests/sasl_fed_ex_link new file mode 100755 index 0000000000..7b232d4874 --- /dev/null +++ b/qpid/cpp/src/tests/sasl_fed_ex_link @@ -0,0 +1,27 @@ +#! /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. +# + + +source ./test_env.sh + +${srcdir}/sasl_fed_ex link + + diff --git a/qpid/cpp/src/tests/sasl_fed_ex_link_cluster b/qpid/cpp/src/tests/sasl_fed_ex_link_cluster new file mode 100755 index 0000000000..4139300b12 --- /dev/null +++ b/qpid/cpp/src/tests/sasl_fed_ex_link_cluster @@ -0,0 +1,28 @@ +#! /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. +# + + +source ./test_env.sh +source $srcdir/ais_check + +with_ais_group ${srcdir}/sasl_fed_ex link cluster + + diff --git a/qpid/cpp/src/tests/sasl_fed_ex_queue b/qpid/cpp/src/tests/sasl_fed_ex_queue new file mode 100755 index 0000000000..be0c10cf63 --- /dev/null +++ b/qpid/cpp/src/tests/sasl_fed_ex_queue @@ -0,0 +1,27 @@ +#! /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. +# + + +source ./test_env.sh + +${srcdir}/sasl_fed_ex queue + + diff --git a/qpid/cpp/src/tests/sasl_fed_ex_queue_cluster b/qpid/cpp/src/tests/sasl_fed_ex_queue_cluster new file mode 100755 index 0000000000..f251420e08 --- /dev/null +++ b/qpid/cpp/src/tests/sasl_fed_ex_queue_cluster @@ -0,0 +1,28 @@ +#! /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. +# + + +source ./test_env.sh +source ${srcdir}/ais_check + +with_ais_group ${srcdir}/sasl_fed_ex queue cluster + + diff --git a/qpid/cpp/src/tests/sasl_fed_ex_route b/qpid/cpp/src/tests/sasl_fed_ex_route new file mode 100755 index 0000000000..dd5c4f3cac --- /dev/null +++ b/qpid/cpp/src/tests/sasl_fed_ex_route @@ -0,0 +1,27 @@ +#! /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. +# + + +source ./test_env.sh + +${srcdir}/sasl_fed_ex route + + diff --git a/qpid/cpp/src/tests/sasl_fed_ex_route_cluster b/qpid/cpp/src/tests/sasl_fed_ex_route_cluster new file mode 100755 index 0000000000..a5d1542def --- /dev/null +++ b/qpid/cpp/src/tests/sasl_fed_ex_route_cluster @@ -0,0 +1,28 @@ +#! /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. +# + + +source ./test_env.sh +source ${srcdir}/ais_check + +with_ais_group ${srcdir}/sasl_fed_ex route cluster + + 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..6395ba6ec3 --- /dev/null +++ b/qpid/cpp/src/tests/sasl_test_setup.sh @@ -0,0 +1,41 @@ +#! /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. +# + +SASL_PW=/usr/sbin/saslpasswd2 +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 +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..cc192d25bd --- /dev/null +++ b/qpid/cpp/src/tests/shared_perftest @@ -0,0 +1,22 @@ +#!/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. +# + +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.mk b/qpid/cpp/src/tests/ssl.mk new file mode 100644 index 0000000000..435db0c55b --- /dev/null +++ b/qpid/cpp/src/tests/ssl.mk @@ -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. +# + +TESTS+=ssl_test +EXTRA_DIST+=ssl_test +CLEAN_LOCAL += test_cert_db cert.password diff --git a/qpid/cpp/src/tests/ssl_test b/qpid/cpp/src/tests/ssl_test new file mode 100755 index 0000000000..cbf75eb237 --- /dev/null +++ b/qpid/cpp/src/tests/ssl_test @@ -0,0 +1,142 @@ +#!/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. +# + +# Run a simple test over SSL +source ./test_env.sh + +CONFIG=$(dirname $0)/config.null +CERT_DIR=`pwd`/test_cert_db +CERT_PW_FILE=`pwd`/cert.password +TEST_HOSTNAME=127.0.0.1 +TEST_CLIENT_CERT=rumplestiltskin +COUNT=10 + +trap cleanup EXIT + +error() { echo $*; exit 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 /usr/bin/certutil + certutil -S -d ${CERT_DIR} -n ${TEST_CLIENT_CERT} -s "CN=${TEST_CLIENT_CERT}" -t "CT,," -x -f ${CERT_PW_FILE} -z /usr/bin/certutil +} + +delete_certs() { + if [[ -e ${CERT_DIR} ]] ; then + rm -rf ${CERT_DIR} + fi +} + +COMMON_OPTS="--daemon --no-data-dir --no-module-dir --auth no --config $CONFIG --load-module $SSL_LIB --ssl-cert-db $CERT_DIR --ssl-cert-password-file $CERT_PW_FILE --ssl-cert-name $TEST_HOSTNAME --require-encryption" +start_broker() { # $1 = extra opts + ../qpidd --transport ssl --port 0 --ssl-port 0 $COMMON_OPTS $1; +} + +stop_brokers() { + test -n "$PORT" && ../qpidd --no-module-dir -qp $PORT + test -n "$PORT2" && ../qpidd --no-module-dir -qp $PORT2 + PORT="" + PORT2="" +} + +cleanup() { + stop_brokers + delete_certs +} + +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" +PORT=`start_broker` || error "Could not start broker" +echo "Running SSL test on port $PORT" +export QPID_NO_MODULE_DIR=1 +export QPID_LOAD_MODULE=$SSLCONNECTOR_LIB +export QPID_SSL_CERT_DB=${CERT_DIR} +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; } + +#### Client Authentication tests + +PORT2=`start_broker --ssl-require-client-authentication` || error "Could not start broker" +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 -z $CLUSTER_LIB && exit 0 # Exit if cluster not supported. + +## Test failover in a cluster using SSL only +. $srcdir/ais_check # Will exit if clustering not enabled. + +pick_port() { + # We need a fixed port to set --cluster-url. Use qpidd to pick a free port. + PICK=`../qpidd --no-module-dir -dp0` + ../qpidd --no-module-dir -qp $PICK + echo $PICK +} +ssl_cluster_broker() { # $1 = port + ../qpidd $COMMON_OPTS --load-module $CLUSTER_LIB --cluster-name ssl_test.$HOSTNAME.$$ --cluster-url amqp:ssl:$TEST_HOSTNAME:$1 --port 0 --ssl-port $1 --transport ssl > /dev/null + # Wait for broker to be ready + qpid-ping -Pssl -b $TEST_HOSTNAME -qp $1 || { echo "Cannot connect to broker on $1"; exit 1; } + echo "Running SSL cluster broker on port $1" +} + +PORT1=`pick_port`; ssl_cluster_broker $PORT1 +PORT2=`pick_port`; ssl_cluster_broker $PORT2 + +# Pipe receive output to uniq to remove duplicates +./qpid-receive --connection-options "{reconnect:true, reconnect-timeout:5}" --failover-updates -b amqp:ssl:$TEST_HOSTNAME:$PORT1 -a "foo;{create:always}" -f | uniq > ssl_test_receive.tmp & +./qpid-send -b amqp:ssl:$TEST_HOSTNAME:$PORT2 --content-string=one -a "foo;{create:always}" +../qpidd --no-module-dir -qp $PORT1 # Kill broker 1 receiver should fail-over. +./qpid-send -b amqp:ssl:$TEST_HOSTNAME:$PORT2 --content-string=two -a "foo;{create:always}" --send-eos 1 +wait # Wait for qpid-receive +{ echo one; echo two; } > ssl_test_receive.cmp +diff ssl_test_receive.tmp ssl_test_receive.cmp || { echo "Failover failed"; exit 1; } +rm -f ssl_test_receive.* + diff --git a/qpid/cpp/src/tests/start_broker b/qpid/cpp/src/tests/start_broker new file mode 100755 index 0000000000..093c44051a --- /dev/null +++ b/qpid/cpp/src/tests/start_broker @@ -0,0 +1,24 @@ +#!/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. +# + +# Start a test broker. +srcdir=`dirname $0` +exec $srcdir/run_test ../qpidd --auth=no --no-module-dir --daemon --port=0 --log-to-file qpidd.log "$@" > qpidd.port diff --git a/qpid/cpp/src/tests/start_broker.ps1 b/qpid/cpp/src/tests/start_broker.ps1 new file mode 100644 index 0000000000..9263262b9f --- /dev/null +++ b/qpid/cpp/src/tests/start_broker.ps1 @@ -0,0 +1,60 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Get the directory where this script resides. +function Get-ScriptPath + { Split-Path $myInvocation.ScriptName } + +# Start a test broker and capture it's port (from stdout) to qpidd.port +# This script will exit immediately after spawning the broker process. To avoid +# running more tests before the broker is initialized, wait for the qpidd.port +# file to appear before exiting. +if (Test-Path qpidd.port) { + Remove-Item qpidd.port +} + +# 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 --port=0 --log-to-file qpidd.log $args | foreach { set-content qpidd.port `$_ }" +$cmdblock = $executioncontext.invokecommand.NewScriptBlock($cmdline) +$srcdir = Get-ScriptPath +. $srcdir\background.ps1 $cmdblock + +$wait_time = 0 +while (!(Test-Path qpidd.port) -and ($wait_time -lt 10)) { + Start-Sleep 2 + $wait_time += 2 +} +if (Test-Path qpidd.port) { + exit 0 +} +"Time out waiting for broker to start" +exit 1 diff --git a/qpid/cpp/src/tests/start_cluster b/qpid/cpp/src/tests/start_cluster new file mode 100755 index 0000000000..bc35a2eddc --- /dev/null +++ b/qpid/cpp/src/tests/start_cluster @@ -0,0 +1,42 @@ +#!/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. +# + +# Start a cluster of brokers on local host, put the list of ports for cluster members in cluster.ports +# + +# Execute command with the ais group set. +source ./test_env.sh +. `dirname $0`/ais_check + +rm -f cluster*.log cluster.ports qpidd.port + +SIZE=${1:-3}; shift +CLUSTER=$HOSTNAME.$$ +OPTS="-d --no-module-dir --load-module $CLUSTER_LIB --cluster-name=$CLUSTER --auth=no --log-enable notice+ --log-enable debug+:cluster $@" + +for (( i=0; i<SIZE; ++i )); do + DDIR=`mktemp -d /tmp/start_cluster.XXXXXXXXXX` + PORT=`with_ais_group ../qpidd -p0 --log-to-file=cluster$i.log $OPTS --data-dir=$DDIR` || exit 1 + echo $PORT >> cluster.ports +done + +head -n 1 cluster.ports > qpidd.port # First member's port for tests. + diff --git a/qpid/cpp/src/tests/start_cluster_hosts b/qpid/cpp/src/tests/start_cluster_hosts new file mode 100755 index 0000000000..778b4248da --- /dev/null +++ b/qpid/cpp/src/tests/start_cluster_hosts @@ -0,0 +1,70 @@ +#!/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. +# + +# +# Start a cluster of brokers on local host, put the list of host port addresses +# in cluster.ports +# +# Arguments: [-k] [-p port] HOST [HOST...] +# -p port to start broker on, can be 0. Actual ports recorded in cluster.addr. +# -k kill any qpidd processes owned by this user before starting. +# +# Start a broker on each named host. Name a host twice to start multiple brokers. +# +# You must be able to ssh to each host and be in group ais. +# $QPIDD must be executable on each host. +# Logs go to syslog on each host, with a unique prefix per broker. +# + +QPIDD=${QPIDD:-$PWD/../qpidd} +LIBQPIDCLUSTER=${LIBQPIDCLUSTER:-$PWD/../.libs/cluster.so} +NAME=$USER # User name is default cluster name. +RESTART=NO + +while getopts "kp:n:q:r" ARG ; do + case $ARG in + k) KILL=yes ;; + p) PORT="$OPTARG" ;; + n) NAME=$OPTARG ;; + q) QPIDD=$OPTARG ;; + l) LIBQPIDCLUSTER=$OPTARG ;; + r) RESTART=yes ;; + *) echo "Error parsing options: $ARG"; exit 1 ;; + esac +done +shift `expr $OPTIND - 1` +test -n "$PORT" && PORTOPT="-p $PORT" +test "$KILL" = yes && KILL="$QPIDD --no-module-dir -q $PORTOPT ;" +CLUSTER=${*:-$CLUSTER} # Use args or env +test -z "$CLUSTER" && { echo Must specify at least one host; exit 1; } + + +OPTS="-d $PORTOPT --load-module $LIBQPIDCLUSTER --cluster-name=$NAME --no-data-dir --auth=no --log-to-syslog --log-enable=info+" + +num=0 +for h in $CLUSTER; do + num=`expr $num + 1` # Give a unique log prefix to each node. + cmd="$KILL $QPIDD $OPTS --log-prefix $num.$h" + out=`echo "$cmd" | ssh $h newgrp ais` || { echo == $h error: $out ; exit 1; } + if [ "$PORT" = 0 ] ; then p=$out; else p=$PORT; fi + echo "$h $p" +done + diff --git a/qpid/cpp/src/tests/stop_broker b/qpid/cpp/src/tests/stop_broker new file mode 100755 index 0000000000..248fd1fc5c --- /dev/null +++ b/qpid/cpp/src/tests/stop_broker @@ -0,0 +1,41 @@ +#!/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. +# + +# Stop the broker, check for errors. +# +QPID_PORT=`cat qpidd.port` +export QPID_PORT +rm -f qpidd.port + +../qpidd --no-module-dir --quit || ERROR=1 + +# Check qpidd.log. +egrep 'warning\|error\|critical' qpidd.log && { + echo "WARNING: Suspicious broker log entries in qpidd.log, above." +} + +# Check valgrind log. +if test -n "$VALGRIND"; then + . `dirname $0`/vg_check $VG_LOG* + vg_check qpidd.vglog* || ERROR=1 +fi + +exit $ERROR diff --git a/qpid/cpp/src/tests/stop_broker.ps1 b/qpid/cpp/src/tests/stop_broker.ps1 new file mode 100644 index 0000000000..4fdeb26e2b --- /dev/null +++ b/qpid/cpp/src/tests/stop_broker.ps1 @@ -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. +# + +# Stop the broker, check for errors. +Get-Content -path qpidd.port -totalCount 1 | Set-Variable -name qpid_port +Remove-Item qpidd.port + +# 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 +} + +# Piping the output makes the script wait for qpidd to finish. +Invoke-Expression "$prog --quit --port $qpid_port" | Write-Output +$stopped = $? + +# Check qpidd.log. +filter bad_stuff { + $_ -match "( warning | error | critical )" +} + +$qpidd_errors = $false +Get-Content -path qpidd.log | where { bad_stuff } | Out-Default | Set-Variable -name qpidd_errors -value $true +if ($qpidd_errors -eq $true) { + "WARNING: Suspicious broker log entries in qpidd.log, above." +} +if ($stopped -eq $true) { + exit 0 +} +exit 1 diff --git a/qpid/cpp/src/tests/stop_cluster b/qpid/cpp/src/tests/stop_cluster new file mode 100755 index 0000000000..d598a2255a --- /dev/null +++ b/qpid/cpp/src/tests/stop_cluster @@ -0,0 +1,33 @@ +#!/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. +# + +# Stop brokers on ports listed in cluster.ports + +PORTS=`cat cluster.ports` +for PORT in $PORTS ; do + $QPIDD_EXEC --no-module-dir -qp $PORT || ERROR="$ERROR $PORT" +done +rm -f cluster.ports qpidd.port + +if [ -n "$ERROR" ]; then + echo "Errors stopping brokers on ports: $ERROR" + exit 1 +fi diff --git a/qpid/cpp/src/tests/store.py b/qpid/cpp/src/tests/store.py new file mode 100755 index 0000000000..77e8a78e5d --- /dev/null +++ b/qpid/cpp/src/tests/store.py @@ -0,0 +1,197 @@ +#!/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") + + # 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) + + 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/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.sh.in b/qpid/cpp/src/tests/test_env.sh.in new file mode 100644 index 0000000000..842d7729cb --- /dev/null +++ b/qpid/cpp/src/tests/test_env.sh.in @@ -0,0 +1,79 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT 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 configure/cmake. +srcdir=`absdir @abs_srcdir@` +builddir=`absdir @abs_builddir@` +top_srcdir=`absdir @abs_top_srcdir@` +top_builddir=`absdir @abs_top_builddir@` +moduledir=$top_builddir/src@builddir_lib_suffix@ +testmoduledir=$builddir@builddir_lib_suffix@ +export QPID_INSTALL_PREFIX=@prefix@ + +# 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=$srcdir:$PYTHON_DIR:$PYTHON_COMMANDS:$QPID_TESTS_PY:$QMF_LIB:$PYTHONPATH +export QPID_CONFIG_EXEC=$PYTHON_COMMANDS/qpid-config +export QPID_ROUTE_EXEC=$PYTHON_COMMANDS/qpid-route +export QPID_CLUSTER_EXEC=$PYTHON_COMMANDS/qpid-cluster + +# Executables +export QPIDD_EXEC=$top_builddir/src/qpidd +export QPID_WATCHDOG_EXEC=$top_builddir/src/qpidd_watchdog + +# Test executables +export QPID_TEST_EXEC_DIR=$builddir +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:$PATH + +# Modules +export 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 REPLICATING_LISTENER_LIB replicating_listener.so +exportmodule REPLICATION_EXCHANGE_LIB replication_exchange.so +exportmodule SSLCONNECTOR_LIB sslconnector.so +exportmodule SSL_LIB ssl.so +exportmodule WATCHDOG_LIB watchdog.so +exportmodule XML_LIB xml.so + +# Qpid options +export QPID_NO_MODULE_DIR=1 # Don't accidentally load installed modules +export QPID_DATA_DIR= # Default to no data dir, not ~/.qpidd + +# Options for boost test framework +export BOOST_TEST_SHOW_PROGRESS=yes +export BOOST_TEST_CATCH_SYSTEM_ERRORS=no diff --git a/qpid/cpp/src/tests/test_store.cpp b/qpid/cpp/src/tests/test_store.cpp new file mode 100644 index 0000000000..257e77b6b4 --- /dev/null +++ b/qpid/cpp/src/tests/test_store.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. + * + */ + + +/**@file + * Plug-in message store for tests. + * + * Add functionality as required, build up a comprehensive set of + * features to support persistent behavior tests. + * + * Current features special "action" messages can: + * - raise exception from enqueue. + * - force host process to exit. + * - do async completion after a delay. + */ + +#include "qpid/broker/NullMessageStore.h" +#include "qpid/broker/Broker.h" +#include "qpid/framing/AMQFrame.h" +#include "qpid/log/Statement.h" +#include "qpid/Plugin.h" +#include "qpid/Options.h" +#include <boost/cast.hpp> +#include <boost/lexical_cast.hpp> +#include <memory> +#include <fstream> + +using namespace qpid; +using namespace broker; +using namespace std; +using namespace qpid::sys; + +namespace qpid { +namespace tests { + +struct TestStoreOptions : public Options { + + string name; + string dump; + + 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.") + ; + } +}; + +struct Completer : public Runnable { + boost::intrusive_ptr<PersistableMessage> message; + int usecs; + Completer(boost::intrusive_ptr<PersistableMessage> m, int u) : message(m), usecs(u) {} + void run() { + qpid::sys::usleep(usecs); + message->enqueueComplete(); + delete this; + } +}; + +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); + if (!options.dump.empty()) + dump.reset(new ofstream(options.dump.c_str())); + } + + ~TestStore() { + for_each(threads.begin(), threads.end(), boost::bind(&Thread::join, _1)); + } + + virtual bool isNull() const { return false; } + + void enqueue(TransactionContext* , + const boost::intrusive_ptr<PersistableMessage>& pmsg, + const PersistableQueue& ) + { + Message* msg = dynamic_cast<Message*>(pmsg.get()); + assert(msg); + + // Dump the message if there is a dump file. + if (dump.get()) { + msg->getFrames().getMethod()->print(*dump); + *dump << endl << " "; + msg->getFrames().getHeaders()->print(*dump); + *dump << endl << " "; + *dump << msg->getFrames().getContentSize() << endl; + } + + // Check the message for special instructions. + string data = msg->getFrames().getContent(); + size_t i = string::npos; + size_t j = string::npos; + if (strncmp(data.c_str(), TEST_STORE_DO.c_str(), strlen(TEST_STORE_DO.c_str())) == 0 + && (i = data.find(name+"[")) != string::npos + && (j = data.find("]", i)) != string::npos) + { + size_t start = i+name.size()+1; + string action = data.substr(start, j-start); + + if (action == EXCEPTION) { + throw Exception(QPID_MSG("TestStore " << name << " throwing exception for: " << data)); + } + else if (action == EXIT_PROCESS) { + // FIXME aconway 2009-04-10: this is a dubious way to + // close the process at best, it can cause assertions or seg faults + // rather than clean exit. + QPID_LOG(critical, "TestStore " << name << " forcing process exit for: " << data); + exit(0); + } + else if (strncmp(action.c_str(), ASYNC.c_str(), strlen(ASYNC.c_str())) == 0) { + std::string delayStr(action.substr(ASYNC.size())); + int delay = boost::lexical_cast<int>(delayStr); + threads.push_back(Thread(*new Completer(msg, delay))); + } + else { + QPID_LOG(error, "TestStore " << name << " unknown action " << action); + msg->enqueueComplete(); + } + } + else + msg->enqueueComplete(); + } + + private: + static const string TEST_STORE_DO, EXCEPTION, EXIT_PROCESS, ASYNC; + TestStoreOptions options; + string name; + Broker& broker; + vector<Thread> threads; + std::auto_ptr<ofstream> dump; +}; + +const string TestStore::TEST_STORE_DO = "TEST_STORE_DO: "; +const string TestStore::EXCEPTION = "exception"; +const string TestStore::EXIT_PROCESS = "exit_process"; +const string TestStore::ASYNC="async "; + +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..de672f938a --- /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 <boost/assign/list_of.hpp> +#include <vector> +#include <set> +#include <ostream> +#include <sstream> +#include <exception> + +// 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/test_watchdog b/qpid/cpp/src/tests/test_watchdog new file mode 100755 index 0000000000..2b4ae9246e --- /dev/null +++ b/qpid/cpp/src/tests/test_watchdog @@ -0,0 +1,36 @@ +#!/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. +# + +# Tests for the watchdog plug-in + +source ./test_env.sh +# Start a broker with watchdog, freeze it with kill -STOP, verify that it is killed. +PORT=`$QPIDD_EXEC -dp0 --no-data-dir --auth=no --no-module-dir --load-module $WATCHDOG_LIB --log-to-file=qpidd_watchdog.log --watchdog-interval 2` || exit 1 +PID=`$QPIDD_EXEC --no-module-dir -cp $PORT` || exit 1 +kill -STOP $PID +sleep 3 + +if kill -0 $PID 2>/dev/null; then + echo "Hung process did not die." + kill $PID +else + true +fi diff --git a/qpid/cpp/src/tests/test_wrap b/qpid/cpp/src/tests/test_wrap new file mode 100755 index 0000000000..dd43c5a2e2 --- /dev/null +++ b/qpid/cpp/src/tests/test_wrap @@ -0,0 +1,48 @@ +#!/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. +# + +# Read the started broker port, set appropriate env vars +# then run the program under test + +QPID_PORT=`cat qpidd.port` +export QPID_PORT + +program=$1 +shift + +QPID_LOG_TO_FILE=`basename $program`.log +export QPID_LOG_TO_FILE + +ERROR=0 +$program $* || ERROR=1 + +# Check qpidd.log. +egrep 'warning\|error\|critical' $QPID_LOG_TO_FILE && { + echo "WARNING: Suspicious broker log entries in $QPID_LOG_TO_FILE, above." +} + +# Check valgrind log. +#if test -n "$VALGRIND"; then +# . `dirname $0`/vg_check $VG_LOG* +# vg_check qpidd.vglog* || ERROR=1 +#fi + +exit $ERROR diff --git a/qpid/cpp/src/tests/testagent.cpp b/qpid/cpp/src/tests/testagent.cpp new file mode 100644 index 0000000000..98520b424a --- /dev/null +++ b/qpid/cpp/src/tests/testagent.cpp @@ -0,0 +1,203 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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/Manageable.h> +#include <qpid/management/ManagementObject.h> +#include <qpid/agent/ManagementAgent.h> +#include <qpid/sys/Mutex.h> +#include <qpid/sys/Time.h> +#include "qmf/org/apache/qpid/agent/example/Parent.h" +#include "qmf/org/apache/qpid/agent/example/Child.h" +#include "qmf/org/apache/qpid/agent/example/ArgsParentCreate_child.h" +#include "qmf/org/apache/qpid/agent/example/EventChildCreated.h" +#include "qmf/org/apache/qpid/agent/example/Package.h" + +#include <signal.h> +#include <cstdlib> +#include <iostream> + +#include <sstream> + +static bool running = true; + +using namespace std; +using qpid::management::ManagementAgent; +using qpid::management::ManagementObject; +using qpid::management::Manageable; +using qpid::management::Args; +using qpid::sys::Mutex; +namespace _qmf = qmf::org::apache::qpid::agent::example; + +class ChildClass; + +//============================================================== +// CoreClass is the operational class that corresponds to the +// "Parent" class in the management schema. +//============================================================== +class CoreClass : public Manageable +{ + string name; + ManagementAgent* agent; + _qmf::Parent* mgmtObject; + std::vector<ChildClass*> children; + Mutex vectorLock; + +public: + + CoreClass(ManagementAgent* agent, string _name); + ~CoreClass() { mgmtObject->resourceDestroy(); } + + ManagementObject* GetManagementObject(void) const + { return mgmtObject; } + + void doLoop(); + status_t ManagementMethod (uint32_t methodId, Args& args, string& text); +}; + +class ChildClass : public Manageable +{ + string name; + _qmf::Child* mgmtObject; + +public: + + ChildClass(ManagementAgent* agent, CoreClass* parent, string name); + ~ChildClass() { mgmtObject->resourceDestroy(); } + + ManagementObject* GetManagementObject(void) const + { return mgmtObject; } + + void doWork() + { + mgmtObject->inc_count(2); + } +}; + +CoreClass::CoreClass(ManagementAgent* _agent, string _name) : name(_name), agent(_agent) +{ + static uint64_t persistId = 0x111222333444555LL; + mgmtObject = new _qmf::Parent(agent, this, name); + + agent->addObject(mgmtObject, persistId++); + mgmtObject->set_state("IDLE"); +} + +void CoreClass::doLoop() +{ + // Periodically bump a counter to provide a changing statistical value + while (running) { + qpid::sys::sleep(1); + mgmtObject->inc_count(); + mgmtObject->set_state("IN_LOOP"); + + { + Mutex::ScopedLock _lock(vectorLock); + + for (std::vector<ChildClass*>::iterator iter = children.begin(); + iter != children.end(); + iter++) { + (*iter)->doWork(); + } + } + } +} + +Manageable::status_t CoreClass::ManagementMethod(uint32_t methodId, Args& args, string& /*text*/) +{ + Mutex::ScopedLock _lock(vectorLock); + + switch (methodId) { + case _qmf::Parent::METHOD_CREATE_CHILD: + _qmf::ArgsParentCreate_child& ioArgs = (_qmf::ArgsParentCreate_child&) args; + + ChildClass *child = new ChildClass(agent, this, ioArgs.i_name); + ioArgs.o_childRef = child->GetManagementObject()->getObjectId(); + + children.push_back(child); + + agent->raiseEvent(_qmf::EventChildCreated(ioArgs.i_name)); + + return STATUS_OK; + } + + return STATUS_NOT_IMPLEMENTED; +} + +ChildClass::ChildClass(ManagementAgent* agent, CoreClass* parent, string name) +{ + mgmtObject = new _qmf::Child(agent, this, parent, name); + + agent->addObject(mgmtObject); +} + + +//============================================================== +// Main program +//============================================================== + +ManagementAgent::Singleton* singleton; + +void shutdown(int) +{ + running = false; +} + +int main_int(int argc, char** argv) +{ + singleton = new ManagementAgent::Singleton(); + const char* host = argc>1 ? argv[1] : "127.0.0.1"; + int port = argc>2 ? atoi(argv[2]) : 5672; + + signal(SIGINT, shutdown); + + // Create the qmf management agent + ManagementAgent* agent = singleton->getInstance(); + + // Register the Qmf_example schema with the agent + _qmf::Package packageInit(agent); + + // Start the agent. It will attempt to make a connection to the + // management broker + agent->init(host, port, 5, false, ".magentdata"); + + // Allocate some core objects + CoreClass core1(agent, "Example Core Object #1"); + CoreClass core2(agent, "Example Core Object #2"); + CoreClass core3(agent, "Example Core Object #3"); + + core1.doLoop(); + + // done, cleanup and exit + delete singleton; + + return 0; +} + +int main(int argc, char** argv) +{ + try { + return main_int(argc, argv); + } catch(std::exception& e) { + cerr << "Top Level Exception: " << e.what() << endl; + return 1; + } +} + diff --git a/qpid/cpp/src/tests/testagent.mk b/qpid/cpp/src/tests/testagent.mk new file mode 100644 index 0000000000..19d91ccab9 --- /dev/null +++ b/qpid/cpp/src/tests/testagent.mk @@ -0,0 +1,51 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Build a simple qmf agent for test purposes. + +TESTAGENT_GEN_SRC= \ + testagent_gen/qmf/org/apache/qpid/agent/example/Parent.h \ + testagent_gen/qmf/org/apache/qpid/agent/example/Child.h \ + testagent_gen/qmf/org/apache/qpid/agent/example/Parent.cpp \ + testagent_gen/qmf/org/apache/qpid/agent/example/Child.cpp \ + testagent_gen/qmf/org/apache/qpid/agent/example/ArgsParentCreate_child.h \ + testagent_gen/qmf/org/apache/qpid/agent/example/EventChildCreated.h \ + testagent_gen/qmf/org/apache/qpid/agent/example/EventChildDestroyed.h \ + testagent_gen/qmf/org/apache/qpid/agent/example/EventChildCreated.cpp \ + testagent_gen/qmf/org/apache/qpid/agent/example/EventChildDestroyed.cpp \ + testagent_gen/qmf/org/apache/qpid/agent/example/Package.h \ + testagent_gen/qmf/org/apache/qpid/agent/example/Package.cpp + +$(TESTAGENT_GEN_SRC): testagent_gen.timestamp +if GENERATE +TESTAGENT_DEPS=../mgen.timestamp +endif # GENERATE +testagent_gen.timestamp: testagent.xml ${TESTAGENT_DEPS} + $(QMF_GEN) -o testagent_gen/qmf $(srcdir)/testagent.xml + touch $@ + +CLEANFILES+=$(TESTAGENT_GEN_SRC) testagent_gen.timestamp + +testagent-testagent.$(OBJEXT): $(TESTAGENT_GEN_SRC) +qpidtest_PROGRAMS+=testagent +testagent_CXXFLAGS=$(CXXFLAGS) -Itestagent_gen +testagent_SOURCES=testagent.cpp $(TESTAGENT_GEN_SRC) +testagent_LDADD=$(top_builddir)/src/libqmf.la + +EXTRA_DIST+=testagent.xml diff --git a/qpid/cpp/src/tests/testagent.xml b/qpid/cpp/src/tests/testagent.xml new file mode 100644 index 0000000000..0b1436f999 --- /dev/null +++ b/qpid/cpp/src/tests/testagent.xml @@ -0,0 +1,64 @@ +<schema package="org.apache.qpid.agent.example"> + +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> + + <!-- + =============================================================== + Parent + =============================================================== + --> + <class name="Parent"> + + This class represents a parent object + + <property name="name" type="lstr" access="RC" index="y"/> + + <statistic name="state" type="sstr" desc="Operational state of the link"/> + <statistic name="count" type="count64" unit="tick" desc="Counter that increases monotonically"/> + + <method name="create_child" desc="Create child object"> + <arg name="name" dir="I" type="lstr"/> + <arg name="childRef" dir="O" type="objId"/> + </method> + </class> + + + <!-- + =============================================================== + Child + =============================================================== + --> + <class name="Child"> + <property name="ParentRef" type="objId" references="Parent" access="RC" index="y" parentRef="y"/> + <property name="name" type="lstr" access="RC" index="y"/> + + <statistic name="count" type="count64" unit="tick" desc="Counter that increases monotonically"/> + + <method name="delete"/> + </class> + + <eventArguments> + <arg name="childName" type="lstr"/> + </eventArguments> + + <event name="ChildCreated" args="childName"/> + <event name="ChildDestroyed" args="childName"/> +</schema> + diff --git a/qpid/cpp/src/tests/testlib.py b/qpid/cpp/src/tests/testlib.py new file mode 100644 index 0000000000..fe57a84a81 --- /dev/null +++ b/qpid/cpp/src/tests/testlib.py @@ -0,0 +1,766 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT 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 qpid python tests. +# + +import os, re, signal, subprocess, time, unittest + +class TestBase(unittest.TestCase): + """ + Base class for qpid tests. Provides broker start/stop/kill methods + """ + + """ + The following environment vars control if and how the test is run, and determine where many of the helper + executables/libs are to be found. + """ + _storeLib = os.getenv("STORE_LIB") + _storeEnable = _storeLib != None # Must be True for durability to be enabled during the test + _qpiddExec = os.getenv("QPIDD_EXEC", "/usr/sbin/qpidd") + _tempStoreDir = os.path.abspath(os.getenv("TMP_DATA_DIR", "/tmp/qpid")) + + """Global message counter ensures unique messages""" + _msgCnt = 0 + + # --- Helper functions for parameter handling --- + + def _paramBool(self, key, val, keyOnly = False): + if val == None: + return "" + if keyOnly: + if val: + return " --%s" % key + else: + return "" + else: + if val: + return " --%s yes" % key + else: + return " --%s no" % key + + # --- Helper functions for message creation --- + + def _makeMessage(self, msgSize): + msg = "Message-%04d" % self._msgCnt + self._msgCnt = self._msgCnt + 1 + msgLen = len(msg) + if msgSize > msgLen: + for i in range(msgLen, msgSize): + if i == msgLen: + msg += "-" + else: + msg += chr(ord('a') + (i % 26)) + return msg + + def _makeMessageList(self, numMsgs, msgSize): + if msgSize == None: + msgSize = 12 + msgs = "" + for m in range(0, numMsgs): + msgs += "%s\n" % self._makeMessage(msgSize) + return msgs + + # --- Starting and stopping a broker --- + + def startBroker(self, qpiddArgs, logFile = None): + """Start a single broker daemon, returns tuple (pid, port)""" + if self._qpiddExec == None: + raise Exception("Environment variable QPIDD is not set") + cmd = "%s --daemon --port=0 %s" % (self._qpiddExec, qpiddArgs) + portStr = os.popen(cmd).read() + if len(portStr) == 0: + err = "Broker daemon startup failed." + if logFile != None: + err += " See log file %s" % logFile + raise Exception(err) + port = int(portStr) + pidStr = os.popen("%s -p %d -c" % (self._qpiddExec, port)).read() + try: + pid = int(pidStr) + except: + raise Exception("Unable to get pid: \"%s -p %d -c\" returned %s" % (self._qpiddExec, port, pidStr)) + #print "started broker: pid=%d, port=%d args: %s" % (pid, port, qpiddArgs) + return (pid, port) + + def killBroker(self, nodeTuple, ignoreFailures = False): + """Kill a broker using kill -9""" + try: + os.kill(nodeTuple[self.PID], signal.SIGKILL) + try: + os.waitpid(nodeTuple[self.PID], 0) + except: + pass + #print "killed broker: port=%d pid=%d" % (nodeTuple[self.PORT], nodeTuple[self.PID]) + except: + if ignoreFailures: + print "WARNING: killBroker (port=%d pid=%d) failed - ignoring." % (nodeTuple[self.PORT], nodeTuple[self.PID]) + else: + raise + + def stopBroker(self, nodeTuple, ignoreFailures = False): + """Stop a broker using qpidd -q""" + try: + ret = os.spawnl(os.P_WAIT, self._qpiddExec, self._qpiddExec, "--port=%d" % nodeTuple[self.PORT], "--quit", "--no-module-dir") + if ret != 0: + raise Exception("stopBroker(): port=%d: qpidd -q returned %d" % (nodeTuple[self.PORT], ret)) + try: + os.waitpid(nodeTuple[self.PID], 0) + except: + pass + #print "stopped broker: port=%d pid=%d" % (nodeTuple[self.PORT], nodeTuple[self.PID]) + except: + if ignoreFailures: + print "WARNING: stopBroker (port=%d pid=%d) failed - ignoring." % (nodeTuple[self.PORT], nodeTuple[self.PID]) + else: + raise + + + +class TestBaseCluster(TestBase): + """ + Base class for cluster tests. Provides methods for starting and stopping clusters and cluster nodes. + """ + + """ + The following environment vars control if and how the test is run, and determine where many of the helper + executables/libs are to be found. + """ + _clusterLib = os.getenv("CLUSTER_LIB") + _clusterTestEnable = _clusterLib != None # Must be True for these cluster tests to run + _xmlLib = os.getenv("XML_LIB") + _xmlEnable = _xmlLib != None + _qpidConfigExec = os.getenv("QPID_CONFIG_EXEC", "/usr/bin/qpid-config") + _qpidRouteExec = os.getenv("QPID_ROUTE_EXEC", "/usr/bin/qpid-route") + _receiverExec = os.getenv("RECEIVER_EXEC", "/usr/libexec/qpid/test/receiver") + _senderExec = os.getenv("SENDER_EXEC", "/usr/libexec/qpid/test/sender") + + + """ + _clusterDict is a dictionary of clusters: + key = cluster name (string) + val = dictionary of node numbers: + key = integer node number + val = tuple containing (pid, port) + For example, two clusters "TestCluster0" and "TestCluster1" containing several nodes would look as follows: + {"TestCluster0": {0: (pid0-0, port0-0), 1: (pid0-1, port0-1), ...}, "TestCluster1": {0: (pid1-0, port1-0), 1: (pid1-1, port1-1), ...}} + where pidm-n and portm-n are the int pid and port for TestCluster m node n respectively. + """ + _clusterDict = {} + + """Index for (pid, port) tuple""" + PID = 0 + PORT = 1 + + def run(self, res): + """ Skip cluster testing if env var RUN_CLUSTER_TESTS is not defined.""" + if not self._clusterTestEnable: + return + unittest.TestCase.run(self, res) + + # --- Private helper / convenience functions --- + + def _checkPids(self, clusterName = None): + for pid, port in self.getTupleList(): + try: + os.kill(pid, 0) + except: + raise Exception("_checkPids(): Broker with pid %d expected but does not exist! (crashed?)" % pid) + + + # --- Starting cluster node(s) --- + + def createClusterNode(self, nodeNumber, clusterName): + """Create a node and add it to the named cluster""" + if self._tempStoreDir == None: + raise Exception("Environment variable TMP_DATA_DIR is not set") + if self._clusterLib == None: + raise Exception("Environment variable LIBCLUSTER is not set") + name = "%s-%d" % (clusterName, nodeNumber) + dataDir = os.path.join(self._tempStoreDir, "cluster", name) + logFile = "%s.log" % dataDir + args = "--no-module-dir --load-module=%s --data-dir=%s --cluster-name=%s --auth=no --log-enable=notice+ --log-to-file=%s" % \ + (self._clusterLib, dataDir, clusterName, logFile) + if self._storeEnable: + if self._storeLib == None: + raise Exception("Environment variable LIBSTORE is not set") + args += " --load-module %s" % self._storeLib + self._clusterDict[clusterName][nodeNumber] = self.startBroker(args, logFile) + + def createCluster(self, clusterName, numberNodes = 0): + """Create a cluster containing an initial number of nodes""" + self._clusterDict[clusterName] = {} + for n in range(0, numberNodes): + self.createClusterNode(n, clusterName) + + def waitForNodes(self, clusterName): + """Wait for all nodes to become active (ie finish cluster sync)""" + # TODO - connect to each known node in cluster + # Until this is done, wait a bit (hack) + time.sleep(1) + + # --- Cluster and node status --- + + def getTupleList(self, clusterName = None): + """Get list of (pid, port) tuples of all known cluster brokers""" + tList = [] + for c, l in self._clusterDict.iteritems(): + if clusterName == None or c == clusterName: + for t in l.itervalues(): + tList.append(t) + return tList + + def getNumBrokers(self): + """Get total number of brokers in all known clusters""" + return len(self.getTupleList()) + + def checkNumBrokers(self, expected = None, checkPids = True): + """Check that the total number of brokers in all known clusters is the expected value""" + if expected != None and self.getNumBrokers() != expected: + raise Exception("Unexpected number of brokers: expected %d, found %d" % (expected, self.getNumBrokers())) + if checkPids: + self._checkPids() + + def getClusterTupleList(self, clusterName): + """Get list of (pid, port) tuples of all nodes in named cluster""" + if clusterName in self._clusterDict: + return self._clusterDict[clusterName].values() + return [] + + def getNumClusterBrokers(self, clusterName): + """Get total number of brokers in named cluster""" + return len(self.getClusterTupleList(clusterName)) + + def getNodeTuple(self, nodeNumber, clusterName): + """Get the (pid, port) tuple for the given cluster node""" + return self._clusterDict[clusterName][nodeNumber] + + def checkNumClusterBrokers(self, clusterName, expected = None, checkPids = True, waitForNodes = True): + """Check that the total number of brokers in the named cluster is the expected value""" + if expected != None and self.getNumClusterBrokers(clusterName) != expected: + raise Exception("Unexpected number of brokers in cluster %s: expected %d, found %d" % \ + (clusterName, expected, self.getNumClusterBrokers(clusterName))) + if checkPids: + self._checkPids(clusterName) + if waitForNodes: + self.waitForNodes(clusterName) + + def clusterExists(self, clusterName): + """ Return True if clusterName exists, False otherwise""" + return clusterName in self._clusterDict.keys() + + def clusterNodeExists(self, clusterName, nodeNumber): + """ Return True if nodeNumber in clusterName exists, False otherwise""" + if clusterName in self._clusterDict.keys(): + return nodeNumber in self._clusterDict[nodeName] + return False + + def createCheckCluster(self, clusterName, size): + """Create a cluster using the given name and size, then check the number of brokers""" + self.createCluster(clusterName, size) + self.checkNumClusterBrokers(clusterName, size) + + # --- Kill cluster nodes using signal 9 --- + + def killNode(self, nodeNumber, clusterName, updateDict = True, ignoreFailures = False): + """Kill the given node in the named cluster using kill -9""" + self.killBroker(self.getNodeTuple(nodeNumber, clusterName), ignoreFailures) + if updateDict: + del(self._clusterDict[clusterName][nodeNumber]) + + def killCluster(self, clusterName, updateDict = True, ignoreFailures = False): + """Kill all nodes in the named cluster""" + for n in self._clusterDict[clusterName].iterkeys(): + self.killNode(n, clusterName, False, ignoreFailures) + if updateDict: + del(self._clusterDict[clusterName]) + + def killClusterCheck(self, clusterName): + """Kill the named cluster and check that the name is removed from the cluster dictionary""" + self.killCluster(clusterName) + if self.clusterExists(clusterName): + raise Exception("Unable to kill cluster %s; %d nodes still exist" % \ + (clusterName, self.getNumClusterBrokers(clusterName))) + + def killAllClusters(self, ignoreFailures = False): + """Kill all known clusters""" + for n in self._clusterDict.iterkeys(): + self.killCluster(n, False, ignoreFailures) + self._clusterDict.clear() + + def killAllClustersCheck(self, ignoreFailures = False): + """Kill all known clusters and check that the cluster dictionary is empty""" + self.killAllClusters(ignoreFailures) + self.checkNumBrokers(0) + + # --- Stop cluster nodes using qpidd -q --- + + def stopNode(self, nodeNumber, clusterName, updateDict = True, ignoreFailures = False): + """Stop the given node in the named cluster using qpidd -q""" + self.stopBroker(self.getNodeTuple(nodeNumber, clusterName), ignoreFailures) + if updateDict: + del(self._clusterDict[clusterName][nodeNumber]) + + def stopAllClusters(self, ignoreFailures = False): + """Stop all known clusters""" + for n in self._clusterDict.iterkeys(): + self.stopCluster(n, False, ignoreFailures) + self._clusterDict.clear() + + + def stopCluster(self, clusterName, updateDict = True, ignoreFailures = False): + """Stop all nodes in the named cluster""" + for n in self._clusterDict[clusterName].iterkeys(): + self.stopNode(n, clusterName, False, ignoreFailures) + if updateDict: + del(self._clusterDict[clusterName]) + + def stopCheckCluster(self, clusterName, ignoreFailures = False): + """Stop the named cluster and check that the name is removed from the cluster dictionary""" + self.stopCluster(clusterName, True, ignoreFailures) + if self.clusterExists(clusterName): + raise Exception("Unable to kill cluster %s; %d nodes still exist" % (clusterName, self.getNumClusterBrokers(clusterName))) + + def stopAllCheck(self, ignoreFailures = False): + """Kill all known clusters and check that the cluster dictionary is empty""" + self.stopAllClusters() + self.checkNumBrokers(0) + + # --- qpid-config functions --- + + def _qpidConfig(self, nodeNumber, clusterName, action): + """Configure some aspect of a qpid broker using the qpid_config executable""" + port = self.getNodeTuple(nodeNumber, clusterName)[self.PORT] + #print "%s -a localhost:%d %s" % (self._qpidConfigExec, port, action) + ret = os.spawnl(os.P_WAIT, self._qpidConfigExec, self._qpidConfigExec, "-a", "localhost:%d" % port, *action.split()) + if ret != 0: + raise Exception("_qpidConfig(): cluster=\"%s\" nodeNumber=%d port=%d action=\"%s\" returned %d" % \ + (clusterName, nodeNumber, port, action, ret)) + + def addExchange(self, nodeNumber, clusterName, exchangeType, exchangeName, durable = False, sequence = False, \ + ive = False): + """Add a named exchange.""" + action = "add exchange %s %s" % (exchangeType, exchangeName) + action += self._paramBool("durable", durable, True) + action += self._paramBool("sequence", sequence, True) + action += self._paramBool("ive", ive, True) + self._qpidConfig(nodeNumber, clusterName, action) + + def deleteExchange(self, nodeNumber, clusterName, exchangeName): + """Delete a named exchange""" + self._qpidConfig(nodeNumber, clusterName, "del exchange %s" % exchangeName) + + def addQueue(self, nodeNumber, clusterName, queueName, configArgs = None): + """Add a queue using qpid-config.""" + action = "add queue %s" % queueName + if self._storeEnable: + action += " --durable" + if configArgs != None: + action += " %s" % configArgs + self._qpidConfig(nodeNumber, clusterName, action) + + def delQueue(self, nodeNumber, clusterName, queueName): + """Delete a named queue using qpid-config.""" + self._qpidConfig(nodeNumber, clusterName, "del queue %s" % queueName) + + def bind(self, nodeNumber, clusterName, exchangeName, queueName, key): + """Create an exchange-queue binding using qpid-config.""" + self._qpidConfig(nodeNumber, clusterName, "bind %s %s %s" % (exchangeName, queueName, key)) + + def unbind(self, nodeNumber, clusterName, exchangeName, queueName, key): + """Remove an exchange-queue binding using qpid-config.""" + self._qpidConfig(nodeNumber, clusterName, "unbind %s %s %s" % (exchangeName, queueName, key)) + + # --- qpid-route functions (federation) --- + + def brokerDict(self, nodeNumber, clusterName, host = "localhost", user = None, password = None): + """Returns a dictionary containing the broker info to be passed to route functions""" + port = self.getNodeTuple(nodeNumber, clusterName)[self.PORT] + return {"cluster": clusterName, "node":nodeNumber, "port":port, "host":host, "user":user, "password":password} + + def _brokerStr(self, brokerDict): + """Set up a broker string in the format [user/password@]host:port""" + str = "" + if brokerDict["user"] !=None and brokerDict["password"] != None: + str = "%s@%s" % (brokerDict["user"], brokerDict["password"]) + str += "%s:%d" % (brokerDict["host"], brokerDict["port"]) + return str + + def _qpidRoute(self, action): + """Set up a route using qpid-route""" + #print "%s %s" % (self._qpidRouteExec, action) + ret = os.spawnl(os.P_WAIT, self._qpidRouteExec, self._qpidRouteExec, *action.split()) + if ret != 0: + raise Exception("_qpidRoute(): action=\"%s\" returned %d" % (action, ret)) + + def routeDynamicAdd(self, destBrokerDict, srcBrokerDict, exchangeName): + self._qpidRoute("dynamic add %s %s %s" % (self._brokerStr(destBrokerDict), self._brokerStr(srcBrokerDict), exchangeName)) + + def routeDynamicDelete(self, destBrokerDict, srcBrokerDict, exchangeName): + self._qpidRoute("dynamic del %s %s %s" % (self._brokerStr(destBrokerDict), self._brokerStr(srcBrokerDict), exchangeName)) + + def routeAdd(self, destBrokerDict, srcBrokerDict, exchangeName, routingKey): + self._qpidRoute("route add %s %s %s %s" % (self._brokerStr(destBrokerDict), self._brokerStr(srcBrokerDict), exchangeName, routingKey)) + + def routeDelete(self, destBrokerDict, srcBrokerDict, exchangeName, routingKey): + self._qpidRoute("route del %s %s %s %s" % (self._brokerStr(destBrokerDict), self._brokerStr(srcBrokerDict), exchangeName, routingKey)) + + def routeQueueAdd(self, destBrokerDict, srcBrokerDict, exchangeName, queueName): + self._qpidRoute("queue add %s %s %s %s" % (self._brokerStr(destBrokerDict), self._brokerStr(srcBrokerDict), exchangeName, queueName)) + + def routeQueueDelete(self, destBrokerDict, srcBrokerDict, exchangeName, queueName): + self._qpidRoute("queue del %s %s %s %s" % (self._brokerStr(destBrokerDict), self._brokerStr(srcBrokerDict), exchangeName, queueName)) + + def routeLinkAdd(self, destBrokerDict, srcBrokerDict): + self._qpidRoute("link add %s %s" % (self._brokerStr(destBrokerDict), self._brokerStr(srcBrokerDict))) + + def routeLinkDelete(self, destBrokerDict, srcBrokerDict): + self._qpidRoute("link del %s %s" % (self._brokerStr(destBrokerDict), self._brokerStr(srcBrokerDict))) + + # --- Message send and receive functions --- + + def _receiver(self, action): + if self._receiverExec == None: + raise Exception("Environment variable RECEIVER is not set") + cmd = "%s %s" % (self._receiverExec, action) + #print cmd + return subprocess.Popen(cmd.split(), stdout = subprocess.PIPE) + + def _sender(self, action): + if self._senderExec == None: + raise Exception("Environment variable SENDER is not set") + cmd = "%s %s" % (self._senderExec, action) + #print cmd + return subprocess.Popen(cmd.split(), stdin = subprocess.PIPE) + + def createReciever(self, nodeNumber, clusterName, queueName, numMsgs = None, receiverArgs = None): + port = self.getNodeTuple(nodeNumber, clusterName)[self.PORT] + action = "--port %d --queue %s" % (port, queueName) + if numMsgs != None: + action += " --messages %d" % numMsgs + if receiverArgs != None: + action += " %s" % receiverArgs + return self._receiver(action) + + def createSender(self, nodeNumber, clusterName, exchangeName, routingKey, senderArgs = None): + port = self.getNodeTuple(nodeNumber, clusterName)[self.PORT] + action = "--port %d --exchange %s" % (port, exchangeName) + if routingKey != None and len(routingKey) > 0: + action += " --routing-key %s" % routingKey + if self._storeEnable: + action += " --durable yes" + if senderArgs != None: + action += " %s" % senderArgs + return self._sender(action) + + def createBindDirectExchangeQueue(self, nodeNumber, clusterName, exchangeName, queueName): + self.addExchange(nodeNumber, clusterName, "direct", exchangeName) + self.addQueue(nodeNumber, clusterName, queueName) + self.bind(nodeNumber, clusterName, exchangeName, queueName, queueName) + + def createBindTopicExchangeQueues(self, nodeNumber, clusterName, exchangeName, queueNameKeyList): + self.addExchange(nodeNumber, clusterName, "topic", exchangeName) + for queueName, key in queueNameKeyList.iteritems(): + self.addQueue(nodeNumber, clusterName, queueName) + self.bind(nodeNumber, clusterName, exchangeName, queueName, key) + + def createBindFanoutExchangeQueues(self, nodeNumber, clusterName, exchangeName, queueNameList): + self.addExchange(nodeNumber, clusterName, "fanout", exchangeName) + for queueName in queueNameList: + self.addQueue(nodeNumber, clusterName, queueName) + self.bind(nodeNumber, clusterName, exchangeName, queueName, "") + + def sendMsgs(self, nodeNumber, clusterName, exchangeName, routingKey, numMsgs, msgSize = None, wait = True): + msgs = self._makeMessageList(numMsgs, msgSize) + sender = self.createSender(nodeNumber, clusterName, exchangeName, routingKey) + sender.stdin.write(msgs) + sender.stdin.close() + if wait: + sender.wait() + return msgs + + def receiveMsgs(self, nodeNumber, clusterName, queueName, numMsgs, wait = True): + receiver = self.createReciever(nodeNumber, clusterName, queueName, numMsgs) + cnt = 0 + msgs = "" + while cnt < numMsgs: + rx = receiver.stdout.readline() + if rx == "" and receiver.poll() != None: break + msgs += rx + cnt = cnt + 1 + if wait: + receiver.wait() + return msgs + + + # --- Exchange-specific helper inner classes --- + + class TestHelper: + """ + This is a "virtual" superclass for test helpers, and is not useful on its own, but the + per-exchange subclasses are designed to keep track of the messages sent to and received + from queues which have bindings to that exchange type. + """ + + def __init__(self, testBaseCluster, clusterName, numNodes, exchangeName, queueNameList): + + """Dictionary of queues and lists of messages sent to them.""" + self._txMsgs = {} + """Dictionary of queues and lists of messages received from them.""" + self._rxMsgs = {} + """List of node numbers currently in the cluster""" + self._nodes = [] + """List of node numbers which have been killed and can therefore be recovered""" + self._deadNodes = [] + """Last node to be used""" + self._lastNode = None + + self._testBaseCluster = testBaseCluster + self._clusterName = clusterName + self._exchangeName = exchangeName + self._queueNameList = queueNameList + self._addQueues(queueNameList) + self._testBaseCluster.createCheckCluster(clusterName, numNodes) + self._nodes.extend(range(0, numNodes)) + + def _addQueues(self, queueNameList): + for qn in queueNameList: + if not qn in self._txMsgs: + self._txMsgs[qn] = [] + if not qn in self._rxMsgs: + self._rxMsgs[qn] = [] + + def _bindQueue(self, queueName, bindingKey, nodeNumber = None): + """Bind a queue to an exchange using a binding key.""" + if nodeNumber == None: + nodeNumber = self._nodes[0] # first available node + self._testBaseCluster.addQueue(nodeNumber, self._clusterName, queueName) + self._testBaseCluster.bind(nodeNumber, self._clusterName, self._exchangeName, queueName, bindingKey) + + def _highestNodeNumber(self): + """Find the highest node number used so far between the current nodes and those stopped/killed.""" + highestNode = self._nodes[-1] + if len(self._deadNodes) == 0: + return highestNode + highestDeadNode = self._deadNodes[-1] + if highestNode > highestDeadNode: + return highestNode + return highestDeadNode + + def killCluster(self): + """Kill all nodes in the cluster""" + self._testBaseCluster.killCluster(self._clusterName) + self._testBaseCluster.checkNumClusterBrokers(self._clusterName, 0) + self._deadNodes.extend(self._nodes) + self._deadNodes.sort() + del self._nodes[:] + + def restoreCluster(self, lastNode = None, restoreNodes = True): + """Restore a previously killed cluster""" + self._testBaseCluster.createCluster(self._clusterName) + if restoreNodes: + numNodes = len(self._deadNodes) + self.restoreNodes(lastNode) + self._testBaseCluster.checkNumClusterBrokers(self._clusterName, numNodes) + + def addNodes(self, numberOfNodes = 1): + """Add a fixed number of nodes to the cluster.""" + nodeStart = self._highestNodeNumber() + 1 + for i in range(0, numberOfNodes): + nodeNumber = nodeStart + i + self._testBaseCluster.createClusterNode(nodeNumber, self._clusterName) + self._nodes.append(nodeNumber) + self._testBaseCluster.checkNumClusterBrokers(self._clusterName, len(self._nodes)) + self._testBaseCluster.waitForNodes(self._clusterName) + + def restoreNode(self, nodeNumber): + """Restore a cluster node that has been previously killed""" + if nodeNumber not in self._deadNodes: + raise Exception("restoreNode(): Node number %d not in dead node list %s" % (nodeNumber, self._deadNodes)) + self._testBaseCluster.createClusterNode(nodeNumber, self._clusterName) + self._deadNodes.remove(nodeNumber) + self._nodes.append(nodeNumber) + self._nodes.sort() + + def restoreNodes(self, lastNode = None): + """Restore all known cluster nodes that have been previously killed starting with a known last-used node""" + if len(self._nodes) == 0: # restore last-used node first + if lastNode == None: + lastNode = self._lastNode + self.restoreNode(lastNode) + while len(self._deadNodes) > 0: + self.restoreNode(self._deadNodes[0]) + self._testBaseCluster.waitForNodes(self._clusterName) + + def killNode(self, nodeNumber): + """Kill a cluster node (if it is in the _nodes list).""" + if nodeNumber not in self._nodes: + raise Exception("killNode(): Node number %d not in node list %s" % (nodeNumber, self._nodes)) + self._testBaseCluster.killNode(nodeNumber, self._clusterName) + self._nodes.remove(nodeNumber) + self._deadNodes.append(nodeNumber) + self._deadNodes.sort() + + def sendMsgs(self, routingKey, numMsgs, nodeNumber = None, msgSize = None, wait = True): + """Send a fixed number of messages using the given routing key.""" + if nodeNumber == None: + nodeNumber = self._nodes[0] # Use first available node + msgs = self._testBaseCluster._makeMessageList(numMsgs, msgSize) + sender = self._testBaseCluster.createSender(nodeNumber, self._clusterName, self._exchangeName, routingKey) + sender.stdin.write(msgs) + sender.stdin.close() + if wait: + sender.wait() + self._lastNode = nodeNumber + return msgs.split() + + # TODO - this i/f is messy: one mumMsgs can be given, but a list of queues + # so assuming numMsgs for each queue + # A mechanism is needed to specify a different numMsgs per queue + def receiveMsgs(self, numMsgs, nodeNumber = None, queueNameList = None, wait = True): + """Receive a fixed number of messages from a named queue. If numMsgs == None, get all remaining messages.""" + if nodeNumber == None: + nodeNumber = self._nodes[0] # Use first available node + if queueNameList == None: + queueNameList = self._txMsgs.iterkeys() + for qn in queueNameList: + nm = numMsgs + if nm == None: + nm = len(self._txMsgs[qn]) - len(self._rxMsgs[qn]) # get all remaining messages + if nm > 0: + while nm > 0: + receiver = self._testBaseCluster.createReciever(nodeNumber, self._clusterName, qn, nm) + cnt = 0 + while cnt < nm: + rx = receiver.stdout.readline().strip() + if rx == "": + if receiver.poll() != None: break + elif rx not in self._rxMsgs[qn]: + self._rxMsgs[qn].append(rx) + cnt = cnt + 1 + nm = nm - cnt + if wait: + receiver.wait() + self._rxMsgs[qn].sort() + self._lastNode = nodeNumber + + def receiveRemainingMsgs(self, nodeNumber = None, queueNameList = None, wait = True): + """Receive all remaining messages on named queue.""" + self.receiveMsgs(None, nodeNumber, queueNameList, wait) + + def checkMsgs(self): + """Return True if all expected messages have been received (ie the transmit and receive list are identical).""" + txMsgTot = 0 + rxMsgTot = 0 + for qn, txMsgList in self._txMsgs.iteritems(): + rxMsgList = self._rxMsgs[qn] + txMsgTot = txMsgTot + len(txMsgList) + rxMsgTot = rxMsgTot + len(rxMsgList) + if len(txMsgList) != len(rxMsgList): + return False + for i, m in enumerate(txMsgList): + if m != rxMsgList[i]: + return False + if txMsgTot == 0 and rxMsgTot == 0: + print "WARNING: No messages were either sent or received" + return True + + def finalizeTest(self): + """Recover all the remaining messages on all queues, then check that all expected messages were received.""" + self.receiveRemainingMsgs() + self._testBaseCluster.stopAllCheck() + if not self.checkMsgs(): + self.printMsgs() + self._testBaseCluster.fail("Send - receive message mismatch") + + def printMsgs(self, txMsgs = True, rxMsgs = True): + """Print all messages transmitted and received.""" + for qn, txMsgList in self._txMsgs.iteritems(): + print "Queue: %s" % qn + if txMsgs: + print " txMsgList = %s" % txMsgList + if rxMsgs: + rxMsgList = self._rxMsgs[qn] + print " rxMsgList = %s" % rxMsgList + + + class DirectExchangeTestHelper(TestHelper): + + def __init__(self, testBaseCluster, clusterName, numNodes, exchangeName, queueNameList): + TestBaseCluster.TestHelper.__init__(self, testBaseCluster, clusterName, numNodes, exchangeName, queueNameList) + self._testBaseCluster.addExchange(0, clusterName, "direct", exchangeName) + for qn in queueNameList: + self._bindQueue(qn, qn) + + def addQueues(self, queueNameList): + self._addQueues(queueNameList) + for qn in queueNameList: + self._bindQueue(qn, qn) + + def sendMsgs(self, numMsgs, nodeNumber = None, queueNameList = None, msgSize = None, wait = True): + if queueNameList == None: + queueNameList = self._txMsgs.iterkeys() + for qn in queueNameList: + self._txMsgs[qn].extend(TestBaseCluster.TestHelper.sendMsgs(self, qn, numMsgs, nodeNumber, msgSize, wait)) + + + class TopicExchangeTestHelper(TestHelper): + + def __init__(self, testBaseCluster, clusterName, numNodes, exchangeName, queueNameKeyList): + self._queueNameKeyList = queueNameKeyList + TestBaseCluster.TestHelper.__init__(self, testBaseCluster, clusterName, numNodes, exchangeName, queueNameKeyList.iterkeys()) + self._testBaseCluster.addExchange(0, clusterName, "topic", exchangeName) + for qn, bk in queueNameKeyList.iteritems(): + self._bindQueue(qn, bk) + + def addQueues(self, queueNameKeyList): + self._addQueues(queueNameKeyList.iterkeys()) + for qn, bk in queueNameKeyList.iteritems(): + self._bindQueue(qn, bk) + + def _prepareRegex(self, bk): + # This regex conversion is not very complete - there are other chars that should be escaped too + return "^%s$" % bk.replace(".", r"\.").replace("*", r"[^.]*").replace("#", ".*") + + def sendMsgs(self, routingKey, numMsgs, nodeNumber = None, msgSize = None, wait = True): + msgList = TestBaseCluster.TestHelper.sendMsgs(self, routingKey, numMsgs, nodeNumber, msgSize, wait) + for qn, bk in self._queueNameKeyList.iteritems(): + if re.match(self._prepareRegex(bk), routingKey): + self._txMsgs[qn].extend(msgList) + + + class FanoutExchangeTestHelper(TestHelper): + + def __init__(self, testBaseCluster, clusterName, numNodes, exchangeName, queueNameList): + TestBaseCluster.TestHelper.__init__(self, testBaseCluster, clusterName, numNodes, exchangeName, queueNameList) + self._testBaseCluster.addExchange(0, clusterName, "fanout", exchangeName) + for qn in queueNameList: + self._bindQueue(qn, "") + + def addQueues(self, queueNameList): + self._addQueues(queueNameList) + for qn in queueNameList: + self._bindQueue(qn, "") + + def sendMsgs(self, numMsgs, nodeNumber = None, msgSize = None, wait = True): + msgList = TestBaseCluster.TestHelper.sendMsgs(self, "", numMsgs, nodeNumber, msgSize, wait) + for ml in self._txMsgs.itervalues(): + ml.extend(msgList) + diff --git a/qpid/cpp/src/tests/topic_perftest b/qpid/cpp/src/tests/topic_perftest new file mode 100755 index 0000000000..cd440b2458 --- /dev/null +++ b/qpid/cpp/src/tests/topic_perftest @@ -0,0 +1,22 @@ +#!/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. +# + +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..257c24bd81 --- /dev/null +++ b/qpid/cpp/src/tests/topictest @@ -0,0 +1,61 @@ +#!/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. +# + +# 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..59a483c2d5 --- /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 .\topic_listener.exe + +function subscribe { + param ([int]$num, [string]$sub) + "Start subscriber $num" + $LOG = "subscriber_$num.log" + $cmdline = ".\$sub\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\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..a7a905c1b7 --- /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 +{ + string workQueue; + string source; + 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..882d3716d8 --- /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 +{ + string workQueue; + size_t 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 (size_t 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..ed9623bcc0 --- /dev/null +++ b/qpid/cpp/src/tests/unit_test.h @@ -0,0 +1,70 @@ +#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_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/verify_cluster_objects b/qpid/cpp/src/tests/verify_cluster_objects new file mode 100755 index 0000000000..94661cf6b9 --- /dev/null +++ b/qpid/cpp/src/tests/verify_cluster_objects @@ -0,0 +1,107 @@ +#!/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. +# + +# Verify managment objects are consistent in a cluster. +# Arguments: url of one broker in the cluster. + +import qmf.console, sys, re + +class Session(qmf.console.Session): + """A qmf.console.Session that caches useful values""" + + def __init__(self): + qmf.console.Session.__init__(self) + self.classes = None + + def all_classes(self): + if self.classes is None: + self.classes = [c for p in self.getPackages() for c in self.getClasses(p)] + return self.classes + +class Broker: + def __init__(self, url, qmf): + self.url = url + self.qmf = qmf + self.broker = self.qmf.addBroker(url) + self.broker._waitForStable() + self.objects = None + self.ignore_list = [ re.compile("org.apache.qpid.broker:system:") ] + + def get_objects(self): + def ignore(name): + for m in self.ignore_list: + if m.match(name): return True + if self.objects is None: + obj_list = [] + ignored=0 + for c in self.qmf.all_classes(): + for o in self.qmf.getObjects(_key=c, _broker=self.broker): + name=o.getObjectId().getObject() + if not ignore(name): obj_list.append(name) + else: ignored += 1 + self.objects = set(obj_list) + if (len(obj_list) != len(self.objects)): + raise Exception("Duplicates in object list for %s"%(self.url)) + print "%d objects on %s, ignored %d."%(len(self.objects), self.url, ignored) + return self.objects + + def compare(self,other): + def compare1(x,y): + diff = x.get_objects() - y.get_objects() + if diff: + print "ERROR: found on %s but not %s"%(x, y) + for o in diff: print " %s"%(o) + return False + return True + + so = compare1(self, other) + os = compare1(other, self) + return so and os + + def __str__(self): return self.url + + def get_cluster(self): + """Given one Broker, return list of all brokers in its cluster""" + clusters = self.qmf.getObjects(_class="cluster") + if not clusters: raise ("%s is not a cluster member"%(self.url)) + def first_address(url): + """Python doesn't understand the brokers URL syntax. Extract a simple addres""" + return re.compile("amqp:tcp:([^,]*)").match(url).group(1) + return [Broker(first_address(url), self.qmf) + for url in clusters[0].members.split(";")] + + def __del__(self): self.qmf.delBroker(self.broker) + +def main(argv=None): + if argv is None: argv = sys.argv + qmf = Session() + brokers = Broker(argv[1], qmf).get_cluster() + print "%d members in cluster."%(len(brokers)) + base = brokers.pop(0) + try: + for b in brokers: + if not base.compare(b): return 1 + print "No differences." + return 0 + finally: + del base + del brokers + +if __name__ == "__main__": sys.exit(main()) 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..024f20b147 --- /dev/null +++ b/qpid/cpp/src/tests/windows/DisableWin32ErrorWindows.cpp @@ -0,0 +1,76 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 { + +// 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 |