| /** @file |
| |
| IntrusiveHashMap unit tests. |
| |
| @section license License |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more contributor license |
| agreements. See the NOTICE file distributed with this work for additional information regarding |
| copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with the License. You may |
| obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software distributed under the |
| License is distributed on an "AS IS" BASIS, WITHOUT 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 <string_view> |
| #include <string> |
| #include <bitset> |
| #include <random> |
| |
| #include "swoc/IntrusiveHashMap.h" |
| #include "swoc/bwf_base.h" |
| #include "catch.hpp" |
| |
| using swoc::IntrusiveHashMap; |
| |
| // ------------- |
| // --- TESTS --- |
| // ------------- |
| |
| using namespace std::literals; |
| |
| namespace |
| { |
| struct Thing { |
| std::string _payload; |
| int _n{0}; |
| |
| Thing(std::string_view text) : _payload(text) {} |
| Thing(std::string_view text, int x) : _payload(text), _n(x) {} |
| |
| Thing *_next{nullptr}; |
| Thing *_prev{nullptr}; |
| }; |
| |
| struct ThingMapDescriptor { |
| static Thing *& |
| next_ptr(Thing *thing) |
| { |
| return thing->_next; |
| } |
| static Thing *& |
| prev_ptr(Thing *thing) |
| { |
| return thing->_prev; |
| } |
| static std::string_view |
| key_of(Thing *thing) |
| { |
| return thing->_payload; |
| } |
| static constexpr std::hash<std::string_view> hasher{}; |
| static auto |
| hash_of(std::string_view s) -> decltype(hasher(s)) |
| { |
| return hasher(s); |
| } |
| static bool |
| equal(std::string_view const &lhs, std::string_view const &rhs) |
| { |
| return lhs == rhs; |
| } |
| }; |
| |
| using Map = IntrusiveHashMap<ThingMapDescriptor>; |
| |
| } // namespace |
| |
| TEST_CASE("IntrusiveHashMap", "[libts][IntrusiveHashMap]") |
| { |
| Map map; |
| map.insert(new Thing("bob")); |
| REQUIRE(map.count() == 1); |
| map.insert(new Thing("dave")); |
| map.insert(new Thing("persia")); |
| REQUIRE(map.count() == 3); |
| // Need to be bit careful cleaning up, since the link pointers are in the objects and deleting |
| // the object makes it unsafe to use an iterator referencing that object. For a full cleanup, |
| // the best option is to first delete everything, then clean up the map. |
| map.apply([](Thing *thing) { delete thing; }); |
| map.clear(); |
| REQUIRE(map.count() == 0); |
| |
| size_t nb = map.bucket_count(); |
| std::bitset<64> marks; |
| for (size_t i = 1; i <= 63; ++i) { |
| std::string name; |
| swoc::bwprint(name, "{} squared is {}", i, i * i); |
| Thing *thing = new Thing(name); |
| thing->_n = i; |
| map.insert(thing); |
| REQUIRE(map.count() == i); |
| REQUIRE(map.find(name) != map.end()); |
| } |
| REQUIRE(map.count() == 63); |
| REQUIRE(map.bucket_count() > nb); |
| for (auto &thing : map) { |
| REQUIRE(0 == marks[thing._n]); |
| marks[thing._n] = 1; |
| } |
| marks[0] = 1; |
| REQUIRE(marks.all()); |
| map.insert(new Thing("dup"sv, 79)); |
| map.insert(new Thing("dup"sv, 80)); |
| map.insert(new Thing("dup"sv, 81)); |
| |
| auto r = map.equal_range("dup"sv); |
| REQUIRE(r.first != r.second); |
| REQUIRE(r.first->_payload == "dup"sv); |
| REQUIRE(r.first->_n == 79); |
| |
| Map::iterator idx; |
| |
| // Erase all the non-"dup" and see if the range is still correct. |
| map.apply([&map](Thing &thing) { |
| if (thing._payload != "dup"sv) |
| map.erase(map.iterator_for(&thing)); |
| }); |
| r = map.equal_range("dup"sv); |
| REQUIRE(r.first != r.second); |
| idx = r.first; |
| REQUIRE(idx->_payload == "dup"sv); |
| REQUIRE(idx->_n == 79); |
| REQUIRE((++idx)->_payload == "dup"sv); |
| REQUIRE(idx->_n != r.first->_n); |
| REQUIRE(idx->_n == 80); |
| REQUIRE((++idx)->_payload == "dup"sv); |
| REQUIRE(idx->_n != r.first->_n); |
| REQUIRE(idx->_n == 81); |
| REQUIRE(++idx == map.end()); |
| // Verify only the "dup" items are left. |
| for (auto &&elt : map) { |
| REQUIRE(elt._payload == "dup"sv); |
| } |
| // clean up the last bits. |
| map.apply([](Thing *thing) { delete thing; }); |
| }; |
| |
| // Some more involved tests. |
| TEST_CASE("IntrusiveHashMapManyStrings", "[IntrusiveHashMap]") |
| { |
| std::vector<std::string_view> strings; |
| |
| std::uniform_int_distribution<short> char_gen{'a', 'z'}; |
| std::uniform_int_distribution<short> length_gen{20, 40}; |
| std::minstd_rand randu; |
| constexpr int N = 1009; |
| |
| Map ihm; |
| |
| strings.reserve(N); |
| for (int i = 0; i < N; ++i) { |
| auto len = length_gen(randu); |
| char *s = static_cast<char *>(malloc(len + 1)); |
| for (decltype(len) j = 0; j < len; ++j) { |
| s[j] = char_gen(randu); |
| } |
| s[len] = 0; |
| strings.push_back({s, size_t(len)}); |
| } |
| |
| // Fill the IntrusiveHashMap |
| for (int i = 0; i < N; ++i) { |
| ihm.insert(new Thing{strings[i], i}); |
| } |
| |
| REQUIRE(ihm.count() == N); |
| |
| // Do some lookups - just require the whole loop, don't artificially inflate the test count. |
| bool miss_p = false; |
| for (int j = 0, idx = 17; j < N; ++j, idx = (idx + 17) % N) { |
| if (auto spot = ihm.find(strings[idx]); spot == ihm.end() || spot->_n != idx) { |
| miss_p = true; |
| } |
| } |
| REQUIRE(miss_p == false); |
| |
| // Let's try some duplicates when there's a lot of data in the map. |
| miss_p = false; |
| for (int idx = 23; idx < N; idx += 23) { |
| ihm.insert(new Thing(strings[idx], 2000 + idx)); |
| } |
| for (int idx = 23; idx < N; idx += 23) { |
| auto spot = ihm.find(strings[idx]); |
| if (spot == ihm.end() || spot->_n != idx || ++spot == ihm.end() || spot->_n != 2000 + idx) { |
| miss_p = true; |
| } |
| } |
| REQUIRE(miss_p == false); |
| |
| // Do a different stepping, special cases the intersection with the previous stepping. |
| miss_p = false; |
| for (int idx = 31; idx < N; idx += 31) { |
| ihm.insert(new Thing(strings[idx], 3000 + idx)); |
| } |
| for (int idx = 31; idx < N; idx += 31) { |
| auto spot = ihm.find(strings[idx]); |
| if (spot == ihm.end() || spot->_n != idx || ++spot == ihm.end() || (idx != (23 * 31) && spot->_n != 3000 + idx) || |
| (idx == (23 * 31) && spot->_n != 2000 + idx)) { |
| miss_p = true; |
| } |
| } |
| REQUIRE(miss_p == false); |
| |
| // Check for misses. |
| miss_p = false; |
| for (int i = 0; i < 99; ++i) { |
| char s[41]; |
| auto len = length_gen(randu); |
| for (decltype(len) j = 0; j < len; ++j) { |
| s[j] = char_gen(randu); |
| } |
| std::string_view name(s, len); |
| auto spot = ihm.find(name); |
| if (spot != ihm.end() && name != spot->_payload) { |
| miss_p = true; |
| } |
| } |
| REQUIRE(miss_p == false); |
| }; |
| |
| TEST_CASE("IntrusiveHashMap Utilities", "[IntrusiveHashMap]") { |
| } |