xo-ordinaltree: work on gp<String>-key unit test

This commit is contained in:
Roland Conybeare 2025-12-06 19:42:36 -05:00
commit a3a73c3aa3
4 changed files with 334 additions and 29 deletions

View file

@ -152,7 +152,7 @@ namespace xo {
template <typename T>
class ObjectVisitor<gp<T>> {
public:
void forward_children(gp<T> & target,
static void forward_children(gp<T> & target,
IAlloc * gc)
{
Object::_forward_inplace(target, gc);

View file

@ -24,10 +24,14 @@ namespace xo {
template <typename T>
auto operator()(const T& a, const T& b) const -> std::strong_ordering {
if constexpr (std::three_way_comparable<T>) {
return a <=> b;
} else {
if (a < b) return std::strong_ordering::less;
if (b < a) return std::strong_ordering::greater;
return std::strong_ordering::equal;
}
}
};
/**

View file

@ -316,7 +316,7 @@ namespace xo {
} // TEST_CASE(rbtree-gc-1)
TEST_CASE("gp-string-key-allocate", "[gc]")
TEST_CASE("rbnode-gc-string-key", "[gc][redblacktree]")
{
up<GC> gc = GC::make(
{
@ -333,6 +333,7 @@ namespace xo {
REQUIRE(gc->native_gc_statistics().n_gc() == 0);
{
gp<String> s0 = String::copy(gc.get(), "hello");
REQUIRE(s0->length() == 5);
@ -352,22 +353,267 @@ namespace xo {
REQUIRE(s0 <= s1);
REQUIRE((s0 > s1) == false);
REQUIRE((s0 >= s1) == false);
}
/* verify that we can allocate gp<Strings> through gc::allocator template.
*/
{
static_assert(std::is_constructible_v<gp<String>>);
static_assert(std::is_constructible_v<const gp<String>>);
static_assert(std::is_constructible_v<std::pair<const gp<String>, double>>);
}
//using Allocator = xo::gc::allocator<gp<String>>;
//using Allocator1 = xo::gc::allocator<std::pair<gp<String>, double>>;
using RbTree = RedBlackTree<gp<String>,
double,
SumReduce<double>,
xo::tree::DefaultThreeWayCompare,
xo::gc::allocator<std::pair<const gp<String>, double>>>;
//using NodeAllocator = xo::gc::allocator<std::pair<const gp<String>, int>>;
//using RbNode = Node<gp<String>, int, SumReduce<int>, NodeAllocator>;
xo::gc::allocator<RbTree::node_type> allocator(gc.get());
/* 1. verify that tree nodes can be constructed */
{
RbTree::RbNode test_node;
}
/* 1b. verify that tree node can be created via gc allocator */
{
RbTree::RbNode * test_node_ptr = new (MMPtr(gc.get())) RbTree::RbNode();
REQUIRE(test_node_ptr);
}
/* 1c. verify tree node can be constructed via allocator traits.
*
* Reminder: {} expressions won't deduce template arguments
*/
{
RbTree::RbNode test_node;
gp<String> s1 = String::copy(gc.get(), "hello, world!");
RbTree::node_allocator_traits::construct(allocator,
&test_node,
RbTree::RbNode::value_type(s1, 0.0),
RbTree::RbNode::rvpair_type{0.0, 0.0});
}
}
/* test RbTree with gc allocator,
*
* do lots of GC-requesting, to create old->new xgen pointers.
*/
TEST_CASE("rbtree-gc-string-key-1", "[gc][redblacktree][string]")
{
using RbTree = RedBlackTree<gp<String>,
double,
SumReduce<double>,
xo::tree::DefaultThreeWayCompare,
xo::gc::allocator<std::pair<const gp<String>, double>>>;
constexpr bool c_debug_flag = false;
for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) {
const Testcase_RbTree & tc = s_testcase_v[i_tc];
std::uint64_t seed = 8813374093428528487ULL;
auto rgen = xo::rng::xoshiro256ss(seed);
for (std::uint32_t n=0; n<=1024;) {
bool ok_flag = false;
for (std::uint32_t attention = 0; !ok_flag && (attention < 2); ++attention) {
bool debug_flag = c_debug_flag || (attention == 1);
scope log(XO_DEBUG2(debug_flag, "rbtree-gc-1"), xtag("i_tc", i_tc), xtag("n", n));
INFO(tostr(xtag("i_tc", i_tc), xtag("n", n)));
ok_flag = true; // unless contradicted below
up<GC> gc = GC::make(
{
.initial_nursery_z_ = tc.nursery_z_,
.initial_tenured_z_ = tc.tenured_z_,
.incr_gc_threshold_ = tc.incr_gc_threshold_,
.full_gc_threshold_ = tc.full_gc_threshold_,
.debug_flag_ = debug_flag
}
);
REQUIRE(gc.get());
gc->disable_gc();
REQUIRE(gc->native_gc_statistics().n_gc() == 0);
RbTree::key_compare compare;
xo::gc::allocator<RbTree> allocator(gc.get());
gp<RbTree> rbtree = RbTree::make(compare, allocator, c_debug_flag);
gc->add_gc_root_dwim(&rbtree);
/** strings for key values.
* (make a List of these to keep them alive ?)
**/
std::vector<gp<String>> keys(n);
for (std::size_t i = 0; i < n; ++i) {
char stringdata[80];
snprintf(stringdata, sizeof(stringdata), "key %lu yek", i);
gp<String> key_i = String::copy(gc.get(), stringdata);
keys[i] = key_i;
}
REQUIRE(rbtree.get() != nullptr);
REQUIRE(rbtree->verify_ok(debug_flag));
if (tc.do_extra_gc_) {
REQUIRE(gc->gc_in_progress() == false);
gc->request_gc(gc::generation::nursery);
REQUIRE(gc->is_gc_pending());
REQUIRE(gc->enable_gc_once());
REQUIRE(gc->gc_in_progress() == false);
}
REQUIRE(rbtree->verify_ok(debug_flag));
{
INFO("insert phase A - random_inserts(0, n, 2, ..)");
/* insert even integers in [0, n), in random order **/
ok_flag &= TreeUtil<RbTree>::random_inserts(0, n, 2,
debug_flag,
&rgen,
rbtree.get());
if (tc.do_extra_gc_) {
REQUIRE(gc->gc_in_progress() == false);
gc->request_gc(gc::generation::nursery);
REQUIRE(gc->is_gc_pending());
REQUIRE(gc->enable_gc_once());
REQUIRE(gc->gc_in_progress() == false);
}
REQUIRE(rbtree->verify_ok(debug_flag));
}
#ifdef NOT_YET
if (n > 0) {
INFO("insert phase B - random_inserts(1, n+1, ..)");
/* insert odd integers in [1, n+1), in random order **/
ok_flag &= TreeUtil<RbTree>::random_inserts(1, n+1, 2,
debug_flag,
&rgen,
rbtree.get());
if (tc.do_extra_gc_) {
REQUIRE(gc->gc_in_progress() == false);
gc->request_gc(gc::generation::nursery);
REQUIRE(gc->is_gc_pending());
REQUIRE(gc->enable_gc_once());
REQUIRE(gc->gc_in_progress() == false);
}
REQUIRE(rbtree->verify_ok(debug_flag));
}
/* check iterator traverses [0..n-1] in both directions */
ok_flag &= TreeUtil<RbTree>::check_ordinal_lookup(0 /*dvalue*/,
debug_flag,
*rbtree);
/* verify end-to-end iteration */
ok_flag &= TreeUtil<RbTree>::check_bidirectional_iterator(0,
debug_flag,
*rbtree);
/* check reduced sums for each cut */
ok_flag &= TreeUtil<RbTree>::check_reduced_sum(0,
debug_flag,
*rbtree);
/* verify read-only variant of operator[] */
ok_flag &= TreeUtil<RbTree>::random_lookups(debug_flag,
*rbtree,
&rgen);
/* re-verify lookup on (better-be-monotonic) reduced sums
* remove doubt that operator[] changed something.
*/
ok_flag &= TreeUtil<RbTree>::check_ordinal_lookup(0 /*dvalue*/,
debug_flag,
*rbtree);
/* re-verify end-to-end iteration, so we can say we did */
ok_flag &= TreeUtil<RbTree>::check_bidirectional_iterator(0,
debug_flag,
*rbtree);
/* make random updates, along with basic consistency checks */
ok_flag &= TreeUtil<RbTree>::random_updates(10000,
debug_flag,
rbtree.get(),
&rgen);
REQUIRE(rbtree->verify_ok(debug_flag));
if (tc.do_extra_gc_) {
REQUIRE(gc->gc_in_progress() == false);
gc->request_gc(gc::generation::nursery);
REQUIRE(gc->is_gc_pending());
REQUIRE(gc->enable_gc_once());
REQUIRE(gc->gc_in_progress() == false);
}
REQUIRE(rbtree->verify_ok(debug_flag));
/* verify that updates changed tree contents in expected way */
ok_flag &= TreeUtil<RbTree>::check_ordinal_lookup(10000 /*dvalue*/,
debug_flag,
*rbtree);
/* verify end-to-end iteration */
ok_flag &= TreeUtil<RbTree>::check_bidirectional_iterator(10000,
debug_flag,
*rbtree);
/* check reduced sums for each cut */
ok_flag &= TreeUtil<RbTree>::check_reduced_sum(10000,
debug_flag,
*rbtree);
/* verify behavior of read/write variant of operator[] */
ok_flag &= TreeUtil<RbTree>::random_removes(debug_flag,
&rgen,
rbtree.get());
if (tc.do_extra_gc_) {
REQUIRE(gc->gc_in_progress() == false);
gc->request_gc(gc::generation::nursery);
REQUIRE(gc->is_gc_pending());
REQUIRE(gc->enable_gc_once());
REQUIRE(gc->gc_in_progress() == false);
}
/* verify iteration one more time --
* remove doubt w.r.t.
*/
REQUIRE(rbtree->verify_ok(debug_flag));
#endif
}
if (n == 0)
n = 1;
else
n = 2*n;
}
}
} // TEST_CASE(rbtree-gc-string-key-1)
} /*namespace ut*/
} /*namesapce xo*/

View file

@ -80,6 +80,59 @@ namespace utest {
return ok_flag;
} /*test_clear*/
static bool
random_inserts(const std::vector<typename Tree::key_type> & keys,
bool catch_flag,
xo::rng::xoshiro256ss * p_rgen,
Tree * p_tree)
{
bool ok_flag = true;
xo::scope log(XO_DEBUG(catch_flag), xtag("n-keys", keys.size()));
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok(catch_flag));
/* n keys */
std::size_t n = keys.size();
/* permute keys, remembering original position */
std::vector<std::pair<std::size_t, typename Tree::key_type>> permuted_keys(n);
uint32_t i = 0;
for (const auto & x : keys) {
permuted_keys[i] = std::make_pair(i, x);
}
/* shuffle to get unpredictable insert order */
std::shuffle(keys.begin(), keys.end(), *p_rgen);
size_t tree_z0 = p_tree->size();
/* insert keys in permuted order */
uint32_t i = 1;
for(const auto & pr_i : permuted_keys) {
log && log(xtag("i", i), xtag("ord", pr_i.first), xtag("n", n), xtag("key", pr_i.second));
/* .first: iterator @ insert position
* .second: true if insert occurred ( tree size incremented)
*/
auto insert_result = p_tree->insert(typename Tree::value_type(pr_i.second, 10.0 * i));
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok(catch_flag));
REQUIRE_ORFAIL(ok_flag, catch_flag, insert_result.second);
/* verify: iterator returned by Treẹinsert(), refers to inserted key,value pair */
log && log(xtag("iter.node", insert_result.first.node()));
REQUIRE_ORFAIL(ok_flag, catch_flag, insert_result.first->first == pr_i.second);
REQUIRE_ORFAIL(ok_flag, catch_flag, insert_result.first->second == 10.0 * i);
++i;
}
REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->size() == tree_z0 + n);
return ok_flag;
}
/* do
* n = (hi - lo) / k
* random inserts (taken from *p_rgen) into *p_rbtreẹ
@ -94,6 +147,8 @@ namespace utest {
xo::rng::xoshiro256ss * p_rgen,
Tree * p_tree)
{
// TODO: rewrite in terms of 'random_inserts with explicit vector'.
using xo::xtag;
bool ok_flag = true;