From a3a73c3aa340a1ad5acb4249499a87365e610003 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 6 Dec 2025 19:42:36 -0500 Subject: [PATCH] xo-ordinaltree: work on gp-key unit test --- xo-alloc/include/xo/alloc/Object.hpp | 4 +- .../include/xo/ordinaltree/rbtree/RbTypes.hpp | 10 +- xo-ordinaltree/utest/RedBlackTree-gc.test.cpp | 290 ++++++++++++++++-- xo-ordinaltree/utest/random_tree_ops.hpp | 55 ++++ 4 files changed, 332 insertions(+), 27 deletions(-) diff --git a/xo-alloc/include/xo/alloc/Object.hpp b/xo-alloc/include/xo/alloc/Object.hpp index dce67c11..9665e66a 100644 --- a/xo-alloc/include/xo/alloc/Object.hpp +++ b/xo-alloc/include/xo/alloc/Object.hpp @@ -152,8 +152,8 @@ namespace xo { template class ObjectVisitor> { public: - void forward_children(gp & target, - IAlloc * gc) + static void forward_children(gp & target, + IAlloc * gc) { Object::_forward_inplace(target, gc); } diff --git a/xo-ordinaltree/include/xo/ordinaltree/rbtree/RbTypes.hpp b/xo-ordinaltree/include/xo/ordinaltree/rbtree/RbTypes.hpp index 5579048f..f03fad6b 100644 --- a/xo-ordinaltree/include/xo/ordinaltree/rbtree/RbTypes.hpp +++ b/xo-ordinaltree/include/xo/ordinaltree/rbtree/RbTypes.hpp @@ -24,9 +24,13 @@ namespace xo { template auto operator()(const T& a, const T& b) const -> std::strong_ordering { - if (a < b) return std::strong_ordering::less; - if (b < a) return std::strong_ordering::greater; - return std::strong_ordering::equal; + if constexpr (std::three_way_comparable) { + 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; + } } }; diff --git a/xo-ordinaltree/utest/RedBlackTree-gc.test.cpp b/xo-ordinaltree/utest/RedBlackTree-gc.test.cpp index 53a94104..5322a97c 100644 --- a/xo-ordinaltree/utest/RedBlackTree-gc.test.cpp +++ b/xo-ordinaltree/utest/RedBlackTree-gc.test.cpp @@ -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::make( { @@ -333,41 +333,287 @@ namespace xo { REQUIRE(gc->native_gc_statistics().n_gc() == 0); - gp s0 = String::copy(gc.get(), "hello"); - REQUIRE(s0->length() == 5); + { + gp s0 = String::copy(gc.get(), "hello"); + REQUIRE(s0->length() == 5); - gp s1 = String::copy(gc.get(), "world!"); + gp s1 = String::copy(gc.get(), "world!"); - /* gp comparison */ - REQUIRE(s0 == s0); - REQUIRE((s0 != s0) == false); - REQUIRE((s0 >= s0) == true); - REQUIRE((s0 > s0) == false); - REQUIRE((s0 < s0) == false); - REQUIRE((s0 <= s0) == true); + /* gp comparison */ + REQUIRE(s0 == s0); + REQUIRE((s0 != s0) == false); + REQUIRE((s0 >= s0) == true); + REQUIRE((s0 > s0) == false); + REQUIRE((s0 < s0) == false); + REQUIRE((s0 <= s0) == true); - REQUIRE((s0 == s1) == false); - REQUIRE(s0 != s1); - REQUIRE(s0 < s1); - REQUIRE(s0 <= s1); - REQUIRE((s0 > s1) == false); - REQUIRE((s0 >= s1) == false); + REQUIRE((s0 == s1) == false); + REQUIRE(s0 != s1); + REQUIRE(s0 < s1); + REQUIRE(s0 <= s1); + REQUIRE((s0 > s1) == false); + REQUIRE((s0 >= s1) == false); + } /* verify that we can allocate gp through gc::allocator template. */ + { + static_assert(std::is_constructible_v>); + static_assert(std::is_constructible_v>); + static_assert(std::is_constructible_v, double>>); + } - static_assert(std::is_constructible_v>); + //using Allocator1 = xo::gc::allocator, double>>; - //using Allocator = xo::gc::allocator>; + using RbTree = RedBlackTree, + double, + SumReduce, + xo::tree::DefaultThreeWayCompare, + xo::gc::allocator, double>>>; + xo::gc::allocator allocator(gc.get()); - //using NodeAllocator = xo::gc::allocator, int>>; - //using RbNode = Node, int, SumReduce, NodeAllocator>; + /* 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 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, + double, + SumReduce, + xo::tree::DefaultThreeWayCompare, + xo::gc::allocator, 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::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 allocator(gc.get()); + + gp 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> keys(n); + for (std::size_t i = 0; i < n; ++i) { + char stringdata[80]; + snprintf(stringdata, sizeof(stringdata), "key %lu yek", i); + + gp 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::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::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::check_ordinal_lookup(0 /*dvalue*/, + debug_flag, + *rbtree); + + /* verify end-to-end iteration */ + ok_flag &= TreeUtil::check_bidirectional_iterator(0, + debug_flag, + *rbtree); + + /* check reduced sums for each cut */ + ok_flag &= TreeUtil::check_reduced_sum(0, + debug_flag, + *rbtree); + + /* verify read-only variant of operator[] */ + ok_flag &= TreeUtil::random_lookups(debug_flag, + *rbtree, + &rgen); + + /* re-verify lookup on (better-be-monotonic) reduced sums + * remove doubt that operator[] changed something. + */ + ok_flag &= TreeUtil::check_ordinal_lookup(0 /*dvalue*/, + debug_flag, + *rbtree); + + /* re-verify end-to-end iteration, so we can say we did */ + ok_flag &= TreeUtil::check_bidirectional_iterator(0, + debug_flag, + *rbtree); + + /* make random updates, along with basic consistency checks */ + ok_flag &= TreeUtil::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::check_ordinal_lookup(10000 /*dvalue*/, + debug_flag, + *rbtree); + + /* verify end-to-end iteration */ + ok_flag &= TreeUtil::check_bidirectional_iterator(10000, + debug_flag, + *rbtree); + + /* check reduced sums for each cut */ + ok_flag &= TreeUtil::check_reduced_sum(10000, + debug_flag, + *rbtree); + + /* verify behavior of read/write variant of operator[] */ + ok_flag &= TreeUtil::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*/ diff --git a/xo-ordinaltree/utest/random_tree_ops.hpp b/xo-ordinaltree/utest/random_tree_ops.hpp index 4cf207fb..c0586655 100644 --- a/xo-ordinaltree/utest/random_tree_ops.hpp +++ b/xo-ordinaltree/utest/random_tree_ops.hpp @@ -80,6 +80,59 @@ namespace utest { return ok_flag; } /*test_clear*/ + static bool + random_inserts(const std::vector & 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> 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;