sTodo-m5paper-client/libraries/FastLED/tests/test_hashmap.cpp
2025-06-30 20:47:33 +02:00

300 lines
7.5 KiB
C++

// test.cpp
// g++ --std=c++11 test.cpp -o test && ./test
#include <vector>
#include <set>
#include <unordered_map>
#include "fl/hash_map.h"
#include "fl/str.h"
#include "test.h"
using namespace fl;
TEST_CASE("Empty map properties") {
HashMap<int, int> m;
REQUIRE_EQ(m.size(), 0u);
REQUIRE(!m.find_value(42));
// begin()==end() on empty
REQUIRE(m.begin() == m.end());
}
TEST_CASE("Single insert, lookup & operator[]") {
HashMap<int, int> m;
m.insert(10, 20);
REQUIRE_EQ(m.size(), 1u);
auto *v = m.find_value(10);
REQUIRE(v);
REQUIRE_EQ(*v, 20);
// operator[] default-construct & assignment
HashMap<int, Str> ms;
auto &ref = ms[5];
REQUIRE(ref.empty()); // default-constructed
REQUIRE_EQ(ms.size(), 1u);
ref = "hello";
REQUIRE_EQ(*ms.find_value(5), "hello");
// operator[] overwrite existing
ms[5] = "world";
REQUIRE_EQ(ms.size(), 1u);
REQUIRE_EQ(*ms.find_value(5), "world");
}
TEST_CASE("Insert duplicate key overwrites without growing") {
HashMap<int, Str> m;
m.insert(1, "foo");
REQUIRE_EQ(m.size(), 1u);
REQUIRE_EQ(*m.find_value(1), "foo");
m.insert(1, "bar");
REQUIRE_EQ(m.size(), 1u);
REQUIRE_EQ(*m.find_value(1), "bar");
}
TEST_CASE("Multiple distinct inserts & lookups") {
HashMap<char, int> m;
int count = 0;
for (char c = 'a'; c < 'a' + 10; ++c) {
MESSAGE("insert " << count++);
m.insert(c, c - 'a');
}
REQUIRE_EQ(m.size(), 10u);
for (char c = 'a'; c < 'a' + 10; ++c) {
auto *v = m.find_value(c);
REQUIRE(v);
REQUIRE_EQ(*v, static_cast<int>(c - 'a'));
}
REQUIRE(!m.find_value('z'));
}
TEST_CASE("Erase and remove behavior") {
HashMap<int, int> m;
m.insert(5, 55);
m.insert(6, 66);
REQUIRE_EQ(m.size(), 2u);
// erase existing
REQUIRE(m.erase(5));
REQUIRE_EQ(m.size(), 1u);
REQUIRE(!m.find_value(5));
// erase non-existent
REQUIRE(!m.erase(5));
REQUIRE_EQ(m.size(), 1u);
REQUIRE(m.erase(6));
REQUIRE_EQ(m.size(), 0u);
}
TEST_CASE("Re-insert after erase reuses slot") {
HashMap<int, int> m(4);
m.insert(1, 10);
REQUIRE(m.erase(1));
REQUIRE(!m.find_value(1));
REQUIRE_EQ(m.size(), 0u);
m.insert(1, 20);
REQUIRE(m.find_value(1));
REQUIRE_EQ(*m.find_value(1), 20);
REQUIRE_EQ(m.size(), 1u);
}
TEST_CASE("Clear resets map and allows fresh inserts") {
HashMap<int, int> m(4);
for (int i = 0; i < 3; ++i)
m.insert(i, i);
m.remove(1);
REQUIRE_EQ(m.size(), 2u);
m.clear();
REQUIRE_EQ(m.size(), 0u);
REQUIRE(!m.find_value(0));
REQUIRE(!m.find_value(1));
REQUIRE(!m.find_value(2));
m.insert(5, 50);
REQUIRE_EQ(m.size(), 1u);
REQUIRE_EQ(*m.find_value(5), 50);
}
TEST_CASE("Stress collisions & rehash with small initial capacity") {
HashMap<int, int> m(1 /*capacity*/);
const int N = 100;
for (int i = 0; i < N; ++i) {
m.insert(i, i * 3);
// test that size is increasing
REQUIRE_EQ(m.size(), static_cast<std::size_t>(i + 1));
}
REQUIRE_EQ(m.size(), static_cast<std::size_t>(N));
for (int i = 0; i < N; ++i) {
auto *v = m.find_value(i);
REQUIRE(v);
REQUIRE_EQ(*v, i * 3);
}
}
TEST_CASE("Iterator round-trip and const-iteration") {
HashMap<int, int> m;
for (int i = 0; i < 20; ++i) {
m.insert(i, i + 100);
}
// non-const iteration
std::size_t count = 0;
for (auto kv : m) {
REQUIRE_EQ(kv.second, kv.first + 100);
++count;
}
REQUIRE_EQ(count, m.size());
// const iteration
const auto &cm = m;
count = 0;
for (auto it = cm.begin(); it != cm.end(); ++it) {
auto kv = *it;
REQUIRE_EQ(kv.second, kv.first + 100);
++count;
}
REQUIRE_EQ(count, cm.size());
}
TEST_CASE("Remove non-existent returns false, find on const map") {
HashMap<int, int> m;
REQUIRE(!m.remove(999));
const HashMap<int, int> cm;
REQUIRE(!cm.find_value(0));
}
TEST_CASE("Inserting multiple elements while deleting them will trigger inline "
"rehash") {
const static int MAX_CAPACITY = 2;
HashMap<int, int> m(8 /*capacity*/);
REQUIRE_EQ(8, m.capacity());
for (int i = 0; i < 8; ++i) {
m.insert(i, i);
if (m.size() > MAX_CAPACITY) {
m.remove(i);
}
}
size_t new_capacity = m.capacity();
// should still be 8
REQUIRE_EQ(new_capacity, 8u);
std::set<int> found_values;
for (auto it = m.begin(); it != m.end(); ++it) {
auto kv = *it;
auto key = kv.first;
auto value = kv.second;
REQUIRE_EQ(key, value);
found_values.insert(kv.second);
}
std::vector<int> found_values_vec(found_values.begin(), found_values.end());
REQUIRE_EQ(MAX_CAPACITY, found_values_vec.size());
for (int i = 0; i < MAX_CAPACITY; ++i) {
auto value = found_values_vec[i];
REQUIRE_EQ(value, i);
}
}
TEST_CASE("HashMap with standard iterator access") {
HashMap<int, int> m;
m.insert(1, 1);
REQUIRE_EQ(m.size(), 1u);
// standard iterator access
auto it = m.begin();
auto entry = *it;
REQUIRE_EQ(entry.first, 1);
REQUIRE_EQ(entry.second, 1);
++it;
REQUIRE(it == m.end());
auto bad_it = m.find(0);
REQUIRE(bad_it == m.end());
}
TEST_CASE("HashMap equivalence to std::unordered_map") {
// Create both map types with the same operations
HashMap<int, fl::Str> custom_map;
std::unordered_map<int, fl::Str> std_map;
// Test insertion
custom_map.insert(1, "one");
std_map.insert({1, "one"});
custom_map.insert(2, "two");
std_map.insert({2, "two"});
custom_map.insert(3, "three");
std_map.insert({3, "three"});
// Test size
REQUIRE_EQ(custom_map.size(), std_map.size());
// Test lookup
REQUIRE(*custom_map.find_value(1) == std_map.at(1));
REQUIRE(*custom_map.find_value(2) == std_map.at(2));
REQUIRE(*custom_map.find_value(3) == std_map.at(3));
// Test non-existent key
REQUIRE(!custom_map.find_value(99));
bool std_throws = false;
try {
std_map.at(99);
} catch (const std::out_of_range&) {
std_throws = true;
}
REQUIRE(std_throws);
// Test overwrite
custom_map.insert(2, "TWO");
std_map[2] = "TWO";
REQUIRE(*custom_map.find_value(2) == std_map.at(2));
// Test erase
REQUIRE(custom_map.erase(2));
std_map.erase(2);
REQUIRE_EQ(custom_map.size(), std_map.size());
REQUIRE(!custom_map.find_value(2));
// Test clear
custom_map.clear();
std_map.clear();
REQUIRE_EQ(custom_map.size(), std_map.size());
REQUIRE_EQ(custom_map.size(), 0u);
// Test operator[]
custom_map[5] = "five";
std_map[5] = "five";
REQUIRE_EQ(custom_map.size(), std_map.size());
REQUIRE(*custom_map.find_value(5) == std_map.at(5));
// Test iteration (collect all keys and values)
for (int i = 10; i < 20; ++i) {
fl::Str val = "val";
val.append(i);
custom_map.insert(i, val);
std_map.insert({i, val});
}
std::set<int> custom_keys;
std::set<fl::Str> custom_values;
for (auto kv : custom_map) {
custom_keys.insert(kv.first);
custom_values.insert(kv.second);
}
std::set<int> std_keys;
std::set<fl::Str> std_values;
for (auto& kv : std_map) {
std_keys.insert(kv.first);
std_values.insert(kv.second);
}
REQUIRE(custom_keys == std_keys);
REQUIRE(custom_values == std_values);
}