Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testing and bugfixes for subscription map and retained map #689

Merged
merged 2 commits into from
Oct 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()

30 changes: 15 additions & 15 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 @@ -229,14 +229,10 @@ 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;
}

template <typename V>
void insert_or_update(MQTT_NS::string_view topic, V&& value) {
this->create_topic(topic)->second.value = value;
}

// Find all stored topics that math the specified subscription
Expand All @@ -245,9 +241,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