xo-expression2/xo-arena/utest/DArenaHashMap.test.cpp

282 lines
9 KiB
C++

/** @file DArenaHashMap.test.cpp
*
* @author Roland Conybeare, Jan 2026
**/
#include "DArenaHashMap.hpp"
#include "random_hash_ops.hpp"
#include <xo/randomgen/random_seed.hpp>
#include <xo/indentlog/scope.hpp>
#include <xo/indentlog/print/tag.hpp>
#include <catch2/catch.hpp>
namespace xo {
using xo::map::DArenaHashMapUtil;
using xo::map::DArenaHashMap;
using xo::rng::random_seed;
using xo::rng::xoshiro256ss;
using utest::UtestTools;
using utest::HashMapUtil;
namespace ut {
TEST_CASE("DArenaHashMap-ctor", "[arena][DArenaHashMap]")
{
using HashMap = DArenaHashMap<int, int>;
HashMap map;
REQUIRE(map.empty());
REQUIRE(map.size() == 0);
REQUIRE(map.groups() == 1);
REQUIRE(map.capacity() == DArenaHashMapUtil::c_group_size);
}
TEST_CASE("DArenaHashMap-ctor2", "[arena][DArenaHashMap]")
{
using HashMap = DArenaHashMap<int, int>;
HashMap map(257);
REQUIRE(map.empty());
REQUIRE(map.size() == 0);
REQUIRE(map.capacity() == map.groups() * DArenaHashMapUtil::c_group_size);
REQUIRE(map.capacity() == std::max(512ul,
DArenaHashMapUtil::c_group_size));
}
TEST_CASE("DArenaHashMap-try-insert", "[arena][DArenaHashMap]")
{
using HashMap = DArenaHashMap<int, int>;
HashMap map;
REQUIRE(map.empty());
REQUIRE(map.size() == 0);
REQUIRE(map.groups() == 1);
REQUIRE(map.capacity() == DArenaHashMapUtil::c_group_size);
{
auto x = map.try_insert(std::make_pair(1, 11));
REQUIRE(x.first);
REQUIRE(x.second);
REQUIRE(!map.empty());
REQUIRE(map.size() == 1);
REQUIRE(map.groups() == 1);
REQUIRE(map.capacity() == DArenaHashMapUtil::c_group_size);
REQUIRE(map.load_factor() == 1/16.0);
/* verify iteration */
{
size_t n = 0;
for (auto & ix : map) {
REQUIRE(ix.first == 1);
REQUIRE(ix.second == 11);
++n;
}
REQUIRE(n == map.size());
}
}
{
auto x = map.try_insert(std::make_pair(2, 9));
REQUIRE(x.first);
REQUIRE(x.second);
REQUIRE(!map.empty());
REQUIRE(map.size() == 2);
REQUIRE(map.groups() == 1);
REQUIRE(map.capacity() == DArenaHashMapUtil::c_group_size);
REQUIRE(map.load_factor() == 2/16.0);
/* verify iteration */
{
size_t n = 0;
for (auto & ix : map) {
++n;
}
REQUIRE(n == map.size());
}
}
{
auto x = map.try_insert(std::make_pair(259, 12));
REQUIRE(x.first);
REQUIRE(x.second);
REQUIRE(!map.empty());
REQUIRE(map.size() == 3);
REQUIRE(map.groups() == 1);
REQUIRE(map.capacity() == DArenaHashMapUtil::c_group_size);
REQUIRE(map.load_factor() == 3/16.0);
/* verify iteration */
{
size_t n = 0;
for (auto & ix : map) {
switch (ix.first) {
case 1:
REQUIRE(ix.second == 11);
break;
case 2:
REQUIRE(ix.second == 9);
break;
case 259:
REQUIRE(ix.second == 12);
break;
default:
REQUIRE(false);
}
++n;
}
REQUIRE(n == map.size());
}
}
{
map.clear();
REQUIRE(map.empty());
REQUIRE(map.size() == 0);
REQUIRE(map.groups() == 0);
REQUIRE(map.capacity() == 0);
}
/* slightly different starting point, 0 capacity! */
{
auto x = map.try_insert(std::make_pair(1, 11));
/* try_insert should fail - no capacity */
REQUIRE(!x.first);
REQUIRE(!x.second);
}
{
/* insert will grow hash table */
auto x = map.insert(std::make_pair(1, 11));
CHECK(x);
REQUIRE(!map.empty());
REQUIRE(map.size() == 1);
REQUIRE(map.groups() == 1);
REQUIRE(map.capacity() == DArenaHashMapUtil::c_group_size);
REQUIRE(map.load_factor() == 1/16.0);
/* verify iteration */
{
size_t n = 0;
for (auto & ix : map) {
REQUIRE(ix.first == 1);
REQUIRE(ix.second == 11);
++n;
}
REQUIRE(n == map.size());
}
}
}
TEST_CASE("DArenaHashMap-try-insert2", "[arena][DArenaHashMap]")
{
using HashMap = DArenaHashMap<int, int>;
std::uint64_t seed = 17747889312058974961ul;
//random_seed(&seed); // to get new random seed
//log && log(xtag("seed", seed));
auto rgen = xoshiro256ss(seed);
/* 1. Perform series of tests with increasing scale
* 2. Each test may run in two modes:
* a. silent fast fail. just report success.
* In this mode avoid catch2 REQUIRE
* b. noisy. run with logging enabled
* This mode automatically invoked when silent mode
* observes test failure
*/
for (std::uint32_t n = 0; n <= 8; ) {
HashMap hash_map;
auto test_fn = [&rgen, &hash_map](bool dbg_flag,
std::uint32_t n)
{
bool ok_flag = true;
ok_flag &= HashMapUtil<HashMap>::random_inserts(n, dbg_flag, &rgen, &hash_map);
ok_flag &= HashMapUtil<HashMap>::check_forward_iterator(0.0 /*dvalue*/,
dbg_flag, hash_map);
/* regular forward iterator, but start at hash_map.end() and use operator-- */
ok_flag &= HashMapUtil<HashMap>::check_backward_iterator(0.0 /*dvalue*/,
dbg_flag, hash_map);
ok_flag &= HashMapUtil<HashMap>::random_lookups(0.0 /*dvalue*/,
dbg_flag, &rgen, hash_map);
return ok_flag;
};
bool ok_flag = UtestTools::bimodal_test("DArenaHashMap-try-insert2", test_fn, n);
if (n == 0)
n = 1;
else
n = 2*n;
}
}
TEST_CASE("DArenaHashMap-operator-bracket", "[arena][DArenaHashMap]")
{
using HashMap = DArenaHashMap<int, int>;
HashMap map;
// insert via operator[]
map[1] = 100;
map[2] = 200;
map[3] = 300;
REQUIRE(map.size() == 3);
// read back via operator[]
REQUIRE(map[1] == 100);
REQUIRE(map[2] == 200);
REQUIRE(map[3] == 300);
// update via operator[]
map[2] = 250;
REQUIRE(map[2] == 250);
REQUIRE(map.size() == 3); // size unchanged
// verify via find
{
auto it = map.find(1);
REQUIRE(it != map.end());
REQUIRE(it->second == 100);
}
{
auto it = map.find(2);
REQUIRE(it != map.end());
REQUIRE(it->second == 250);
}
{
auto it = map.find(3);
REQUIRE(it != map.end());
REQUIRE(it->second == 300);
}
// operator[] on non-existent key creates default entry
int & val = map[999];
REQUIRE(map.size() == 4);
REQUIRE(val == 0); // default-initialized
val = 999;
REQUIRE(map[999] == 999);
}
// TODO:
// - let's try getting lcov to work in xo-umbrella2
}
}
/* end DArenaHashMap.test.cpp */