Skip to content

Commit

Permalink
Added test for subscription map and retained map, fixed reference cou…
Browse files Browse the repository at this point in the history
…nting bugs
  • Loading branch information
kleunen committed Oct 17, 2020
1 parent 228ddff commit 362c5c3
Show file tree
Hide file tree
Showing 5 changed files with 376 additions and 46 deletions.
2 changes: 2 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ IF (MQTT_TEST_7)
as_buffer_pubsub.cpp
as_buffer_async_pubsub_1.cpp
as_buffer_async_pubsub_2.cpp
subscription_map.cpp
retained_topic_map.cpp
subscription_map_broker.cpp
async_pubsub_2.cpp
)
Expand Down
89 changes: 89 additions & 0 deletions test/retained_topic_map.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright Takatoshi Kondo 2020
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
#include "test_main.hpp"
#include "combi_test.hpp"
#include "checker.hpp"
#include "global_fixture.hpp"

#include "retained_topic_map.hpp"

#include <iostream>

BOOST_AUTO_TEST_SUITE(test_retained_map)

BOOST_AUTO_TEST_CASE(test_retained_topic_map) {
retained_topic_map<std::string> map;
map.insert_or_update("a/b/c", "123");
map.insert_or_update("a/b", "123");

map.erase("a/b/c");
BOOST_TEST(map.size() != 1);

map.erase("a/b");
BOOST_TEST(map.size() == 1);

std::vector<std::string> values = {
"example/test/A", "example/test/B", "example/A/test", "example/B/test"
};

for(auto const &i: values) {
map.insert_or_update(i, i);
}

std::vector<std::string> matches;
map.find(values[0], [&matches](std::string const &a) {
matches.push_back(a);
});
BOOST_TEST(matches.size() == 1);
BOOST_TEST(matches[0] == values[0]);

matches = { };
map.find(values[1], [&matches](std::string const &a) {
matches.push_back(a);
});
BOOST_TEST(matches.size() == 1);
BOOST_TEST(matches[0] == values[1]);

matches = { };
map.find("example/test/+", [&matches](std::string const &a) {
matches.push_back(a);
});
BOOST_TEST(matches.size() == 2);
BOOST_TEST(matches[0] == values[0]);
BOOST_TEST(matches[1] == values[1]);

matches = { };
map.find("example/+/B", [&matches](std::string const &a) {
matches.push_back(a);
});
BOOST_TEST(matches.size() == 1);
BOOST_TEST(matches[0] == values[1]);

matches = { };
map.find("example/#", [&matches](std::string const &a) {
matches.push_back(a);
});

BOOST_TEST(matches.size() == 4);

std::vector<std::string> diff;
std::sort(matches.begin(), matches.end());
std::sort(values.begin(), values.end());
std::set_difference(matches.begin(), matches.end(), values.begin(), values.end(), diff.begin());
BOOST_TEST(diff.empty());

BOOST_TEST(map.erase("non-existent") == 0);

for(auto const &i: values) {
BOOST_TEST(map.size() != 0);
BOOST_TEST(map.erase(i) == 1);
}

BOOST_TEST(map.size() == 1);
}

BOOST_AUTO_TEST_SUITE_END()

26 changes: 12 additions & 14 deletions test/retained_topic_map.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@

template<typename Value>
class retained_topic_map {
using node_id_t = size_t;
using node_id_t = std::size_t;
using path_entry_key = std::pair<node_id_t, MQTT_NS::buffer>;

static constexpr node_id_t root_node_id = 0;

struct path_entry {
node_id_t id;
size_t count = 1;
std::size_t count = 1;

static constexpr size_t max_count = std::numeric_limits<size_t>::max();
static constexpr std::size_t max_count = std::numeric_limits<std::size_t>::max();

boost::optional<Value> value;

Expand Down Expand Up @@ -60,7 +60,7 @@ class retained_topic_map {

if (entry == map.end()) {
entry = map.emplace(path_entry_key(parent_id, MQTT_NS::allocate_buffer(t)), path_entry(next_node_id++)).first;
if (next_node_id == std::numeric_limits<typeof(next_node_id)>::max()) {
if (next_node_id == std::numeric_limits<node_id_t>::max()) {
throw std::overflow_error("Maximum number of topics reached");
}
}
Expand Down Expand Up @@ -189,7 +189,7 @@ class retained_topic_map {
}

// Remove a value at the specified subscription path
bool remove_topic(MQTT_NS::string_view topic) {
bool erase_topic(MQTT_NS::string_view topic) {
auto path = find_topic(topic);
if (path.empty()) {
return false;
Expand Down Expand Up @@ -230,13 +230,7 @@ class retained_topic_map {

// Insert a value at the specified subscription path
void insert_or_update(MQTT_NS::string_view topic, Value const& value) {
auto path = find_topic(topic);
if (path.empty()) {
this->create_topic(topic)->second.value = value;
}
else {
path.back()->second.value = value;
}
this->create_topic(topic)->second.value = value;
}

// Find all stored topics that math the specified subscription
Expand All @@ -245,9 +239,13 @@ class retained_topic_map {
}

// Remove a stored value at the specified topic
void remove(MQTT_NS::string_view topic) {
remove_topic(topic);
std::size_t erase(MQTT_NS::string_view topic) {
return (erase_topic(topic) ? 1 : 0);
}

// Get the size of the map
std::size_t size() const { return map.size(); }

};

#endif // MQTT_RETAINED_TOPIC_MAP_HPP
210 changes: 210 additions & 0 deletions test/subscription_map.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
// Copyright Takatoshi Kondo 2020
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
#include "test_main.hpp"
#include "combi_test.hpp"
#include "checker.hpp"
#include "global_fixture.hpp"

#include "subscription_map.hpp"

#include <iostream>

BOOST_AUTO_TEST_SUITE(test_subscription_map)

BOOST_AUTO_TEST_CASE( failed_erase ) {
using func_t = std::function<void()>;
using value_t = std::shared_ptr<func_t>; // shared_ptr for '<' and hash
using sm_t = multiple_subscription_map<value_t>;

sm_t m;
auto v1 = std::make_shared<func_t>([] { std::cout << "v1" << std::endl; });
auto v2 = std::make_shared<func_t>([] { std::cout << "v2" << std::endl; });

BOOST_TEST(m.size() == 1);
auto it_success1 = m.insert("a/b/c", v1);
assert(it_success1.second);
BOOST_TEST(m.size() != 1);

auto it_success2 = m.insert("a/b", v2);
assert(it_success2.second);
BOOST_TEST(m.size() != 1);

auto e1 = m.erase(it_success1.first, v1);
BOOST_TEST(m.size() != 1);

auto e2 = m.erase(it_success2.first, v2); // Invalid handle was specified is thrown here
BOOST_TEST(m.size() == 1);

}

BOOST_AUTO_TEST_CASE( test_single_subscription ) {
std::string text = "example/test/A";

single_subscription_map< std::string > map;
auto handle = map.insert(text, text);
BOOST_TEST(handle.size() == 3);
BOOST_TEST(map.handle_to_subscription(handle) == text);
BOOST_CHECK_THROW(map.insert(text, text), std::exception);
map.update(handle, "new_value");
map.erase(handle);

BOOST_TEST(map.size() == 1);

map.insert(text, text);
BOOST_TEST(map.size() != 1);

map.erase(text);
BOOST_TEST(map.size() == 1);

std::vector<std::string> values = {
"example/test/A", "example/+/A", "example/#", "#"
};

for(auto const &i: values) {
map.insert(i, i);
}

// Attempt to remove entry which has no value
BOOST_TEST(map.erase("example") == 0);
BOOST_TEST(map.erase(map.lookup("example")) == 0);
BOOST_TEST(map.erase("example") == 0);
BOOST_TEST(map.erase(map.lookup("example")) == 0);

std::vector<std::string> matches;
map.find("example/test/A", [&matches](std::string const &a) {
matches.push_back(a);
});
BOOST_TEST(matches.size() == 4);

matches = {};
map.find("hash_match_only", [&matches](std::string const &a) {
matches.push_back(a);
});
BOOST_TEST(matches.size() == 1);

matches = {};
map.find("example/hash_only", [&matches](std::string const &a) {
matches.push_back(a);
});
BOOST_TEST(matches.size() == 2);

matches = {};
map.find("example/plus/A", [&matches](std::string const &a) {
matches.push_back(a);
});
BOOST_TEST(matches.size() == 3);

BOOST_TEST(map.erase("non-existent") == 0);

for(auto const &i: values) {
BOOST_TEST(map.size() != 0);
BOOST_TEST(map.erase(i) == 1);
}

BOOST_TEST(map.size() == 1);

std::vector< single_subscription_map< std::string >::handle > handles;
for(auto const &i: values) {
handles.push_back(map.insert(i, i));
}

for(auto const &i: handles) {
BOOST_TEST(map.size() != 0);
BOOST_TEST(map.erase(i) == 1);
}

BOOST_TEST(map.size() == 1);

}

BOOST_AUTO_TEST_CASE( test_multiple_subscription ) {
std::string text = "example/test/A";

multiple_subscription_map<std::string> map;

map.insert("a/b/c", "123");
map.insert("a/b", "123");

map.erase("a/b/c", "123");
BOOST_TEST(map.size() != 1);

map.erase("a/b", "123");
BOOST_TEST(map.size() == 1);

std::vector<std::string> values = {
"example/test/A", "example/+/A", "example/#", "#"
};

// Add some duplicates and overlapping paths
map.insert(values[0], values[0]);
BOOST_TEST(map.insert(values[0], values[0]).second == false);
BOOST_TEST(map.insert(values[0], "blaat").second == true);

map.erase(values[0], "blaat");
BOOST_TEST(map.size() != 1);

map.erase(values[0], values[0]);
BOOST_TEST(map.size() == 1);

// Perform test again but this time using handles
map.insert(values[0], values[0]);
BOOST_TEST(map.insert(map.lookup(values[0]), values[0]).second == false);
BOOST_TEST(map.insert(map.lookup(values[0]), "blaat").second == true);

map.erase(map.lookup(values[0]), "blaat");
BOOST_TEST(map.size() != 1);

map.erase(map.lookup(values[0]), values[0]);
BOOST_TEST(map.size() == 1);

for(auto const &i: values) {
map.insert(i, i);
}

// Attempt to remove entry which has no value
BOOST_TEST(map.erase("example", "example") == 0);
BOOST_TEST(map.erase(map.lookup("example"), "example") == 0);
BOOST_TEST(map.erase("example", "example") == 0);
BOOST_TEST(map.erase(map.lookup("example"), "example") == 0);

BOOST_TEST(map.lookup(values[0]).size() == 3);
BOOST_TEST(map.handle_to_subscription(map.lookup(values[0])) == values[0]);

std::vector<std::string> matches;
map.find("example/test/A", [&matches](std::string const &a) {
matches.push_back(a);
});
BOOST_TEST(matches.size() == 4);

matches = {};
map.find("hash_match_only", [&matches](std::string const &a) {
matches.push_back(a);
});
BOOST_TEST(matches.size() == 1);

matches = {};
map.find("example/hash_only", [&matches](std::string const &a) {
matches.push_back(a);
});
BOOST_TEST(matches.size() == 2);

matches = {};
map.find("example/plus/A", [&matches](std::string const &a) {
matches.push_back(a);
});
BOOST_TEST(matches.size() == 3);

BOOST_TEST(map.erase("non-existent", "non-existent") == 0);

for(auto const &i: values) {
BOOST_TEST(map.size() != 0);
BOOST_TEST(map.erase(i, i) == 1);
}

BOOST_TEST(map.size() == 1);
}

BOOST_AUTO_TEST_SUITE_END()
Loading

0 comments on commit 362c5c3

Please sign in to comment.