blob: a392e31435772e0e1e51bba2ceccd71c24117473 [file] [log] [blame]
/** @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]") {
}