first commit
This commit is contained in:
commit
5893b00dd2
1669 changed files with 1982740 additions and 0 deletions
300
libraries/FastLED/tests/test_hashmap.cpp
Normal file
300
libraries/FastLED/tests/test_hashmap.cpp
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
// 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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue