/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ #include "qpid/broker/TopicKeyNode.h" #include "qpid/broker/TopicExchange.h" #include "unit_test.h" #include "test_tools.h" using namespace qpid::broker; using namespace std; namespace qpid { namespace broker { // Class for exercising the pattern match code in the TopicExchange class TopicExchange::TopicExchangeTester { public: typedef std::vector BindingVec; typedef TopicKeyNode TestBindingNode; private: // binding node iterator that collects all routes that are bound class TestFinder : public TestBindingNode::TreeIterator { public: TestFinder(BindingVec& m) : bv(m) {}; ~TestFinder() {}; bool visit(TestBindingNode& node) { if (!node.bindings.bindingVector.empty()) bv.push_back(node.routePattern); return true; } BindingVec& bv; }; public: TopicExchangeTester() {}; ~TopicExchangeTester() {}; bool addBindingKey(const std::string& bKey) { string routingPattern = normalize(bKey); BindingKey *bk = bindingTree.add(routingPattern); if (bk) { // push a dummy binding to mark this node as "non-leaf" bk->bindingVector.push_back(Binding::shared_ptr()); return true; } return false; } bool removeBindingKey(const std::string& bKey){ string routingPattern = normalize(bKey); BindingKey *bk = bindingTree.get(routingPattern); if (bk) { bk->bindingVector.pop_back(); if (bk->bindingVector.empty()) { // no more bindings - remove this node bindingTree.remove(routingPattern); } return true; } return false; } void findMatches(const std::string& rKey, BindingVec& matches) { TestFinder testFinder(matches); bindingTree.iterateMatch( rKey, testFinder ); } void getAll(BindingVec& bindings) { TestFinder testFinder(bindings); bindingTree.iterateAll( testFinder ); } private: TestBindingNode bindingTree; }; } // namespace broker namespace tests { QPID_AUTO_TEST_SUITE(TopicExchangeTestSuite) #define CHECK_NORMALIZED(expect, pattern) BOOST_CHECK_EQUAL(expect, TopicExchange::normalize(pattern)); namespace { // return the count of bindings that match 'pattern' int match(TopicExchange::TopicExchangeTester &tt, const std::string& pattern) { TopicExchange::TopicExchangeTester::BindingVec bv; tt.findMatches(pattern, bv); return int(bv.size()); } // return true if expected contains exactly all bindings that match // against pattern. bool compare(TopicExchange::TopicExchangeTester& tt, const std::string& pattern, const TopicExchange::TopicExchangeTester::BindingVec& expected) { TopicExchange::TopicExchangeTester::BindingVec bv; tt.findMatches(pattern, bv); if (expected.size() != bv.size()) { // std::cout << "match failed 1 f=[" << bv << "]" << std::endl; // std::cout << "match failed 1 e=[" << expected << "]" << std::endl; return false; } TopicExchange::TopicExchangeTester::BindingVec::const_iterator i; for (i = expected.begin(); i != expected.end(); i++) { TopicExchange::TopicExchangeTester::BindingVec::iterator j; for (j = bv.begin(); j != bv.end(); j++) { // std::cout << "matched [" << *j << "]" << std::endl; if (*i == *j) break; } if (j == bv.end()) { // std::cout << "match failed 2 [" << bv << "]" << std::endl; return false; } } return true; } } QPID_AUTO_TEST_CASE(testNormalize) { CHECK_NORMALIZED("", ""); CHECK_NORMALIZED("a.b.c", "a.b.c"); CHECK_NORMALIZED("a.*.c", "a.*.c"); CHECK_NORMALIZED("#", "#"); CHECK_NORMALIZED("#", "#.#.#.#"); CHECK_NORMALIZED("*.*.*.#", "#.*.#.*.#.#.*"); CHECK_NORMALIZED("a.*.*.*.#", "a.*.#.*.#.*.#"); CHECK_NORMALIZED("a.*.*.*.#", "a.*.#.*.#.*"); CHECK_NORMALIZED("*.*.*.#", "*.#.#.*.*.#"); } QPID_AUTO_TEST_CASE(testPlain) { TopicExchange::TopicExchangeTester tt; string pattern("ab.cd.e"); BOOST_CHECK(tt.addBindingKey(pattern)); BOOST_CHECK_EQUAL(1, match(tt, "ab.cd.e")); BOOST_CHECK_EQUAL(0, match(tt, "abx.cd.e")); BOOST_CHECK_EQUAL(0, match(tt, "ab.cd")); BOOST_CHECK_EQUAL(0, match(tt, "ab.cd..e.")); BOOST_CHECK_EQUAL(0, match(tt, "ab.cd.e.")); BOOST_CHECK_EQUAL(0, match(tt, ".ab.cd.e")); BOOST_CHECK(tt.removeBindingKey(pattern)); pattern = ""; BOOST_CHECK(tt.addBindingKey(pattern)); BOOST_CHECK_EQUAL(1, match(tt, "")); BOOST_CHECK(tt.removeBindingKey(pattern)); pattern = "."; BOOST_CHECK(tt.addBindingKey(pattern)); BOOST_CHECK_EQUAL(1, match(tt, ".")); BOOST_CHECK(tt.removeBindingKey(pattern)); } QPID_AUTO_TEST_CASE(testStar) { TopicExchange::TopicExchangeTester tt; string pattern("a.*.b"); BOOST_CHECK(tt.addBindingKey(pattern)); BOOST_CHECK_EQUAL(1, match(tt, "a.xx.b")); BOOST_CHECK_EQUAL(0, match(tt, "a.b")); BOOST_CHECK(tt.removeBindingKey(pattern)); pattern = "*.x"; BOOST_CHECK(tt.addBindingKey(pattern)); BOOST_CHECK_EQUAL(1, match(tt, "y.x")); BOOST_CHECK_EQUAL(1, match(tt, ".x")); BOOST_CHECK_EQUAL(0, match(tt, "x")); BOOST_CHECK(tt.removeBindingKey(pattern)); pattern = "x.x.*"; BOOST_CHECK(tt.addBindingKey(pattern)); BOOST_CHECK_EQUAL(1, match(tt, "x.x.y")); BOOST_CHECK_EQUAL(1, match(tt, "x.x.")); BOOST_CHECK_EQUAL(0, match(tt, "x.x")); BOOST_CHECK_EQUAL(0, match(tt, "q.x.y")); BOOST_CHECK(tt.removeBindingKey(pattern)); } QPID_AUTO_TEST_CASE(testHash) { TopicExchange::TopicExchangeTester tt; string pattern("a.#.b"); BOOST_CHECK(tt.addBindingKey(pattern)); BOOST_CHECK_EQUAL(1, match(tt, "a.b")); BOOST_CHECK_EQUAL(1, match(tt, "a.x.b")); BOOST_CHECK_EQUAL(1, match(tt, "a..x.y.zz.b")); BOOST_CHECK_EQUAL(0, match(tt, "a.b.")); BOOST_CHECK_EQUAL(0, match(tt, "q.x.b")); BOOST_CHECK(tt.removeBindingKey(pattern)); pattern = "a.#"; BOOST_CHECK(tt.addBindingKey(pattern)); BOOST_CHECK_EQUAL(1, match(tt, "a")); BOOST_CHECK_EQUAL(1, match(tt, "a.b")); BOOST_CHECK_EQUAL(1, match(tt, "a.b.c")); BOOST_CHECK(tt.removeBindingKey(pattern)); pattern = "#.a"; BOOST_CHECK(tt.addBindingKey(pattern)); BOOST_CHECK_EQUAL(1, match(tt, "a")); BOOST_CHECK_EQUAL(1, match(tt, "x.y.a")); BOOST_CHECK(tt.removeBindingKey(pattern)); pattern = "a.#.b.#.c"; BOOST_CHECK(tt.addBindingKey(pattern)); BOOST_CHECK_EQUAL(1, match(tt, "a.b.c")); BOOST_CHECK_EQUAL(1, match(tt, "a.x.b.y.c")); BOOST_CHECK_EQUAL(1, match(tt, "a.x.x.b.y.y.c")); BOOST_CHECK(tt.removeBindingKey(pattern)); } QPID_AUTO_TEST_CASE(testMixed) { TopicExchange::TopicExchangeTester tt; string pattern("*.x.#.y"); BOOST_CHECK(tt.addBindingKey(pattern)); BOOST_CHECK_EQUAL(1, match(tt, "a.x.y")); BOOST_CHECK_EQUAL(1, match(tt, "a.x.p.qq.y")); BOOST_CHECK_EQUAL(0, match(tt, "a.a.x.y")); BOOST_CHECK_EQUAL(0, match(tt, "aa.x.b.c")); BOOST_CHECK(tt.removeBindingKey(pattern)); pattern = "a.#.b.*"; BOOST_CHECK(tt.addBindingKey(pattern)); BOOST_CHECK_EQUAL(1, match(tt, "a.b.x")); BOOST_CHECK_EQUAL(1, match(tt, "a.x.x.x.b.x")); BOOST_CHECK(tt.removeBindingKey(pattern)); pattern = "*.*.*.#"; BOOST_CHECK(tt.addBindingKey(pattern)); BOOST_CHECK_EQUAL(1, match(tt, "x.y.z")); BOOST_CHECK_EQUAL(1, match(tt, "x.y.z.a.b.c")); BOOST_CHECK_EQUAL(0, match(tt, "x.y")); BOOST_CHECK_EQUAL(0, match(tt, "x")); BOOST_CHECK(tt.removeBindingKey(pattern)); } QPID_AUTO_TEST_CASE(testMultiple) { TopicExchange::TopicExchangeTester tt; const std::string bindings[] = { "a", "b", "a.b", "b.c", "a.b.c.d", "b.c.d.e", "a.*", "a.#", "a.*.#", "#.b", "*.b", "*.#.b", "a.*.b", "a.#.b", "a.*.#.b", "*.b.*", "#.b.#", }; const size_t nBindings = sizeof(bindings)/sizeof(bindings[0]); // setup bindings for (size_t idx = 0; idx < nBindings; idx++) { BOOST_CHECK(tt.addBindingKey(bindings[idx])); } { // read all bindings, and verify all are present TopicExchange::TopicExchangeTester::BindingVec b; tt.getAll(b); BOOST_CHECK_EQUAL(b.size(), nBindings); for (size_t idx = 0; idx < nBindings; idx++) { bool found = false; for (TopicExchange::TopicExchangeTester::BindingVec::iterator i = b.begin(); i != b.end(); i++) { if (*i == bindings[idx]) { found = true; break; } } BOOST_CHECK(found); } } { // test match on pattern "a" const std::string matches[] = { "a", "a.#" }; const size_t nMatches = 2; TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); BOOST_CHECK(compare(tt, "a", expected)); } { // test match on pattern "a.z" const std::string matches[] = { "a.*", "a.#", "a.*.#" }; const size_t nMatches = 3; TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); BOOST_CHECK(compare(tt, "a.z", expected)); } { // test match on pattern "a.b" const std::string matches[] = { "a.b", "a.*", "a.#", "a.*.#", "#.b", "#.b.#", "*.#.b", "*.b", "a.#.b" }; const size_t nMatches = 9; TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); BOOST_CHECK(compare(tt, "a.b", expected)); } { // test match on pattern "a.c.c.b" const std::string matches[] = { "#.b", "#.b.#", "*.#.b", "a.#.b", "a.#", "a.*.#.b", "a.*.#" }; const size_t nMatches = 7; TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); BOOST_CHECK(compare(tt, "a.c.c.b", expected)); } { // test match on pattern "a.b.c" const std::string matches[] = { "#.b.#", "*.b.*", "a.#", "a.*.#" }; const size_t nMatches = 4; TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); BOOST_CHECK(compare(tt, "a.b.c", expected)); } { // test match on pattern "b" const std::string matches[] = { "#.b", "#.b.#", "b" }; const size_t nMatches = 3; TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); BOOST_CHECK(compare(tt, "b", expected)); } { // test match on pattern "x.b" const std::string matches[] = { "#.b", "#.b.#", "*.#.b", "*.b" }; const size_t nMatches = 4; TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); BOOST_CHECK(compare(tt, "x.b", expected)); } { // test match on pattern "x.y.z.b" const std::string matches[] = { "#.b", "#.b.#", "*.#.b" }; const size_t nMatches = 3; TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); BOOST_CHECK(compare(tt, "x.y.z.b", expected)); } { // test match on pattern "x.y.z.b.a.b.c" const std::string matches[] = { "#.b.#", "#.b.#" }; const size_t nMatches = 2; TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); BOOST_CHECK(compare(tt, "x.y.z.b.a.b.c", expected)); } { // test match on pattern "a.b.c.d" const std::string matches[] = { "#.b.#", "a.#", "a.*.#", "a.b.c.d", }; const size_t nMatches = 4; TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches); BOOST_CHECK(compare(tt, "a.b.c.d", expected)); } // cleanup bindings for (size_t idx = 0; idx < nBindings; idx++) { BOOST_CHECK(tt.removeBindingKey(bindings[idx])); } } QPID_AUTO_TEST_SUITE_END() }} // namespace qpid::tests