// test.cpp // g++ --std=c++11 test.cpp -o test && ./test #include #include #include #include "fl/hash_map.h" #include "fl/str.h" #include "test.h" using namespace fl; TEST_CASE("Empty map properties") { HashMap 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 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 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 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 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(c - 'a')); } REQUIRE(!m.find_value('z')); } TEST_CASE("Erase and remove behavior") { HashMap 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 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 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 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(i + 1)); } REQUIRE_EQ(m.size(), static_cast(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 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 m; REQUIRE(!m.remove(999)); const HashMap 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 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 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 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 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 custom_map; std::unordered_map 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 custom_keys; std::set custom_values; for (auto kv : custom_map) { custom_keys.insert(kv.first); custom_values.insert(kv.second); } std::set std_keys; std::set 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); }