From 53d4e928ef2851f3e16d1d200b5c3fb036e15e00 Mon Sep 17 00:00:00 2001 From: Casper Norrbin Date: Thu, 4 Sep 2025 09:47:42 +0000 Subject: [PATCH] 8366238: Improve RBTree API with stricter comparator semantics and pluggable validation/printing hooks Reviewed-by: jsjolen, ayang --- src/hotspot/share/gc/z/zMappedCache.cpp | 14 +- src/hotspot/share/gc/z/zMappedCache.hpp | 4 +- src/hotspot/share/nmt/vmatree.hpp | 9 +- src/hotspot/share/opto/printinlining.hpp | 6 +- src/hotspot/share/utilities/rbTree.hpp | 111 +++++++----- src/hotspot/share/utilities/rbTree.inline.hpp | 55 +++--- test/hotspot/gtest/utilities/test_rbtree.cpp | 159 +++++++++++++++--- 7 files changed, 258 insertions(+), 100 deletions(-) diff --git a/src/hotspot/share/gc/z/zMappedCache.cpp b/src/hotspot/share/gc/z/zMappedCache.cpp index ad02d265e93..394c5649c14 100644 --- a/src/hotspot/share/gc/z/zMappedCache.cpp +++ b/src/hotspot/share/gc/z/zMappedCache.cpp @@ -118,20 +118,20 @@ static ZMappedCacheEntry* create_entry(const ZVirtualMemory& vmem) { return entry; } -bool ZMappedCache::EntryCompare::cmp(const IntrusiveRBNode* a, const IntrusiveRBNode* b) { +bool ZMappedCache::EntryCompare::less_than(const IntrusiveRBNode* a, const IntrusiveRBNode* b) { const ZVirtualMemory vmem_a = ZMappedCacheEntry::cast_to_entry(a)->vmem(); const ZVirtualMemory vmem_b = ZMappedCacheEntry::cast_to_entry(b)->vmem(); return vmem_a.end() < vmem_b.start(); } -int ZMappedCache::EntryCompare::cmp(zoffset key, const IntrusiveRBNode* node) { +RBTreeOrdering ZMappedCache::EntryCompare::cmp(zoffset key, const IntrusiveRBNode* node) { const ZVirtualMemory vmem = ZMappedCacheEntry::cast_to_entry(node)->vmem(); - if (key < vmem.start()) { return -1; } - if (key > vmem.end()) { return 1; } + if (key < vmem.start()) { return RBTreeOrdering::LT; } + if (key > vmem.end()) { return RBTreeOrdering::GT; } - return 0; // Containing + return RBTreeOrdering::EQ; // Containing } void ZMappedCache::Tree::verify() const { @@ -168,12 +168,12 @@ void ZMappedCache::Tree::insert(TreeNode* node, const TreeCursor& cursor) { // Insert in tree TreeImpl::insert_at_cursor(node, cursor); - if (_left_most == nullptr || EntryCompare::cmp(node, _left_most)) { + if (_left_most == nullptr || EntryCompare::less_than(node, _left_most)) { // Keep track of left most node _left_most = node; } - if (_right_most == nullptr || EntryCompare::cmp(_right_most, node)) { + if (_right_most == nullptr || EntryCompare::less_than(_right_most, node)) { // Keep track of right most node _right_most = node; } diff --git a/src/hotspot/share/gc/z/zMappedCache.hpp b/src/hotspot/share/gc/z/zMappedCache.hpp index 28f37de0de1..8688e047ae1 100644 --- a/src/hotspot/share/gc/z/zMappedCache.hpp +++ b/src/hotspot/share/gc/z/zMappedCache.hpp @@ -40,8 +40,8 @@ class ZMappedCache { private: struct EntryCompare { - static int cmp(zoffset a, const IntrusiveRBNode* b); - static bool cmp(const IntrusiveRBNode* a, const IntrusiveRBNode* b); + static RBTreeOrdering cmp(zoffset a, const IntrusiveRBNode* b); + static bool less_than(const IntrusiveRBNode* a, const IntrusiveRBNode* b); }; struct ZSizeClassListNode { diff --git a/src/hotspot/share/nmt/vmatree.hpp b/src/hotspot/share/nmt/vmatree.hpp index 01f0e107a56..1b5729054e4 100644 --- a/src/hotspot/share/nmt/vmatree.hpp +++ b/src/hotspot/share/nmt/vmatree.hpp @@ -50,11 +50,10 @@ public: class PositionComparator { public: - static int cmp(position a, position b) { - if (a < b) return -1; - if (a == b) return 0; - if (a > b) return 1; - ShouldNotReachHere(); + static RBTreeOrdering cmp(position a, position b) { + if (a < b) return RBTreeOrdering::LT; + if (a > b) return RBTreeOrdering::GT; + return RBTreeOrdering::EQ; } }; diff --git a/src/hotspot/share/opto/printinlining.hpp b/src/hotspot/share/opto/printinlining.hpp index 3bf09bc921f..e331593ec0e 100644 --- a/src/hotspot/share/opto/printinlining.hpp +++ b/src/hotspot/share/opto/printinlining.hpp @@ -67,8 +67,10 @@ private: }; struct Cmp { - static int cmp(int a, int b) { - return a - b; + static RBTreeOrdering cmp(int a, int b) { + if (a < b) return RBTreeOrdering::LT; + if (a > b) return RBTreeOrdering::GT; + return RBTreeOrdering::EQ; } }; diff --git a/src/hotspot/share/utilities/rbTree.hpp b/src/hotspot/share/utilities/rbTree.hpp index cc52cec3fe0..4c358b53ff0 100644 --- a/src/hotspot/share/utilities/rbTree.hpp +++ b/src/hotspot/share/utilities/rbTree.hpp @@ -35,17 +35,13 @@ // An intrusive red-black tree is constructed with two template parameters: // K is the key type used. // COMPARATOR must have a static function `cmp(K a, const IntrusiveRBNode* b)` which returns: -// - an int < 0 when a < b -// - an int == 0 when a == b -// - an int > 0 when a > b -// Additional static functions used for extra validation can optionally be provided: -// `cmp(K a, K b)` which returns: -// - an int < 0 when a < b -// - an int == 0 when a == b -// - an int > 0 when a > b -// `cmp(const IntrusiveRBNode* a, const IntrusiveRBNode* b)` which returns: -// - true if a < b -// - false otherwise +// - RBTreeOrdering::LT when a < b +// - RBTreeOrdering::EQ when a == b +// - RBTreeOrdering::GT when a > b +// A second static function `less_than(const IntrusiveRBNode* a, const IntrusiveRBNode* b)` +// used for extra validation can optionally be provided. This should return: +// - true if a < b +// - false otherwise // K needs to be of a type that is trivially destructible. // K needs to be stored by the user and is not stored inside the tree. // Nodes are address stable and will not change during its lifetime. @@ -54,10 +50,10 @@ // K is the key type stored in the tree nodes. // V is the value type stored in the tree nodes. // COMPARATOR must have a static function `cmp(K a, K b)` which returns: -// - an int < 0 when a < b -// - an int == 0 when a == b -// - an int > 0 when a > b -// A second static function `cmp(const RBNode* a, const RBNode* b)` +// - RBTreeOrdering::LT when a < b +// - RBTreeOrdering::EQ when a == b +// - RBTreeOrdering::GT when a > b +// A second static function `less_than(const RBNode* a, const RBNode* b)` // used for extra validation can optionally be provided. This should return: // - true if a < b // - false otherwise @@ -65,6 +61,8 @@ // The tree will call a value's destructor when its node is removed. // Nodes are address stable and will not change during its lifetime. +enum class RBTreeOrdering : int { LT, EQ, GT }; + template class AbstractRBTree; @@ -126,10 +124,11 @@ private: // Returns left child (now parent) IntrusiveRBNode* rotate_right(); - template + template void verify(size_t& num_nodes, size_t& black_nodes_until_leaf, size_t& shortest_leaf_path, size_t& longest_leaf_path, - size_t& tree_depth, bool expect_visited, NodeVerifier verifier) const; + size_t& tree_depth, bool expect_visited, NODE_VERIFIER verifier, + const USER_VERIFIER& extra_verifier) const; }; @@ -203,32 +202,37 @@ private: struct has_cmp_type(CMP::cmp), void())> : std::true_type {}; template - static constexpr bool HasKeyComparator = has_cmp_type::value; + static constexpr bool HasKeyComparator = has_cmp_type::value; template - static constexpr bool HasNodeComparator = has_cmp_type::value; + static constexpr bool HasNodeComparator = has_cmp_type::value; + + template + struct has_less_than_type : std::false_type {}; + template + struct has_less_than_type(CMP::less), void())> : std::true_type {}; template - static constexpr bool HasNodeVerifier = has_cmp_type::value; + static constexpr bool HasNodeVerifier = has_less_than_type::value; template && !HasNodeComparator)> - int cmp(const K& a, const NodeType* b) const { + RBTreeOrdering cmp(const K& a, const NodeType* b) const { return COMPARATOR::cmp(a, b->key()); } template )> - int cmp(const K& a, const NodeType* b) const { + RBTreeOrdering cmp(const K& a, const NodeType* b) const { return COMPARATOR::cmp(a, b); } template )> - bool cmp(const NodeType* a, const NodeType* b) const { + bool less_than(const NodeType* a, const NodeType* b) const { return true; } template )> - bool cmp(const NodeType* a, const NodeType* b) const { - return COMPARATOR::cmp(a, b); + bool less_than(const NodeType* a, const NodeType* b) const { + return COMPARATOR::less_than(a, b); } // Cannot assert if no key comparator exist. @@ -237,7 +241,7 @@ private: template )> void assert_key_leq(K a, K b) const { - assert(COMPARATOR::cmp(a, b) <= 0, "key a must be less or equal to key b"); + assert(COMPARATOR::cmp(a, b) != RBTreeOrdering::GT, "key a must be less or equal to key b"); } // True if node is black (nil nodes count as black) @@ -256,10 +260,23 @@ private: // Assumption: node has at most one child. Two children is handled in `remove_at_cursor()` void remove_from_tree(IntrusiveRBNode* node); - template - void verify_self(NodeVerifier verifier) const; + struct empty_verifier { + bool operator()(const NodeType* n) const { + return true; + } + }; - void print_node_on(outputStream* st, int depth, const NodeType* n) const; + template + void verify_self(NODE_VERIFIER verifier, const USER_VERIFIER& extra_verifier) const; + + struct default_printer { + void operator()(outputStream* st, const NodeType* n, int depth) const { + n->print_on(st, depth); + } + }; + + template + void print_node_on(outputStream* st, int depth, const NodeType* n, const PRINTER& node_printer) const; public: NONCOPYABLE(AbstractRBTree); @@ -422,22 +439,30 @@ public: // Verifies that the tree is correct and holds rb-properties // If not using a key comparator (when using IntrusiveRBTree for example), // A second `cmp` must exist in COMPARATOR (see top of file). - template )> - void verify_self() const { - verify_self([](const NodeType* a, const NodeType* b){ return COMPARATOR::cmp(a, b);}); + // Accepts an optional callable `bool extra_verifier(const Node* n)`. + // This should return true if the node is valid. + // If provided, each node is also verified through this callable. + template )> + void verify_self(const USER_VERIFIER& extra_verifier = USER_VERIFIER()) const { + verify_self([](const NodeType* a, const NodeType* b){ return COMPARATOR::less_than(a, b);}, extra_verifier); } - template && !HasNodeVerifier)> - void verify_self() const { - verify_self([](const NodeType* a, const NodeType* b){ return COMPARATOR::cmp(a->key(), b->key()) < 0; }); + template && !HasNodeVerifier)> + void verify_self(const USER_VERIFIER& extra_verifier = USER_VERIFIER()) const { + verify_self([](const NodeType* a, const NodeType* b){ return COMPARATOR::cmp(a->key(), b->key()) == RBTreeOrdering::LT; }, extra_verifier); } - template && !HasKeyComparator && !HasNodeVerifier)> - void verify_self() const { - verify_self([](const NodeType*, const NodeType*){ return true;}); + template && !HasKeyComparator && !HasNodeVerifier)> + void verify_self(const USER_VERIFIER& extra_verifier = USER_VERIFIER()) const { + verify_self([](const NodeType*, const NodeType*){ return true;}, extra_verifier); } - void print_on(outputStream* st) const; + // Accepts an optional printing callable `void node_printer(outputStream* st, const Node* n, int depth)`. + // If provided, each node is printed through this callable rather than the default `print_on`. + template + void print_on(outputStream* st, const PRINTER& node_printer = PRINTER()) const; }; @@ -451,6 +476,14 @@ class RBTree : public AbstractRBTree, COMPARATOR> { public: RBTree() : BaseType(), _allocator() {} ~RBTree() { remove_all(); } + RBTree(const RBTree& other) : BaseType(), _allocator() { + assert(std::is_copy_constructible(), "Value type must be copy-constructible"); + other.visit_in_order([&](auto node) { + this->upsert(node->key(), node->val()); + return true; + }); + } + RBTree& operator=(const RBTree& other) = delete; typedef typename BaseType::Cursor Cursor; using BaseType::cursor; diff --git a/src/hotspot/share/utilities/rbTree.inline.hpp b/src/hotspot/share/utilities/rbTree.inline.hpp index 365786f7fc1..16150e41be8 100644 --- a/src/hotspot/share/utilities/rbTree.inline.hpp +++ b/src/hotspot/share/utilities/rbTree.inline.hpp @@ -123,10 +123,11 @@ inline IntrusiveRBNode* IntrusiveRBNode::next() { return const_cast(static_cast(this)->next()); } -template +template inline void IntrusiveRBNode::verify( size_t& num_nodes, size_t& black_nodes_until_leaf, size_t& shortest_leaf_path, size_t& longest_leaf_path, - size_t& tree_depth, bool expect_visited, NodeVerifier verifier) const { + size_t& tree_depth, bool expect_visited, NODE_VERIFIER verifier, const USER_VERIFIER& extra_verifier) const { + assert(extra_verifier(static_cast(this)), "user provided verifier failed"); assert(expect_visited != _visited, "node already visited"); DEBUG_ONLY(_visited = !_visited); @@ -143,7 +144,7 @@ inline void IntrusiveRBNode::verify( assert(is_black() || _left->is_black(), "2 red nodes in a row"); assert(_left->parent() == this, "pointer mismatch"); _left->verify(num_nodes, num_black_nodes_left, shortest_leaf_path_left, - longest_leaf_path_left, tree_depth_left, expect_visited, verifier); + longest_leaf_path_left, tree_depth_left, expect_visited, verifier, extra_verifier); } size_t num_black_nodes_right = 0; @@ -159,7 +160,7 @@ inline void IntrusiveRBNode::verify( assert(is_black() || _left->is_black(), "2 red nodes in a row"); assert(_right->parent() == this, "pointer mismatch"); _right->verify(num_nodes, num_black_nodes_right, shortest_leaf_path_right, - longest_leaf_path_right, tree_depth_right, expect_visited, verifier); + longest_leaf_path_right, tree_depth_right, expect_visited, verifier, extra_verifier); } shortest_leaf_path = MAX2(longest_leaf_path_left, longest_leaf_path_right); @@ -190,13 +191,13 @@ AbstractRBTree::cursor(const K& key, const NodeType* hi IntrusiveRBNode* const* insert_location = &_root; if (hint_node != nullptr) { - const int hint_cmp = cmp(key, hint_node); + const RBTreeOrdering hint_cmp = cmp(key, hint_node); while (hint_node->parent() != nullptr) { - const int parent_cmp = cmp(key, (NodeType*)hint_node->parent()); + const RBTreeOrdering parent_cmp = cmp(key, (NodeType*)hint_node->parent()); // Move up until the parent would put us on the other side of the key. // Meaning we are in the correct subtree. - if ((parent_cmp <= 0 && hint_cmp < 0) || - (parent_cmp >= 0 && hint_cmp > 0)) { + if ((parent_cmp != RBTreeOrdering::GT && hint_cmp == RBTreeOrdering::LT) || + (parent_cmp != RBTreeOrdering::LT && hint_cmp == RBTreeOrdering::GT)) { hint_node = (NodeType*)hint_node->parent(); } else { break; @@ -212,14 +213,14 @@ AbstractRBTree::cursor(const K& key, const NodeType* hi while (*insert_location != nullptr) { NodeType* curr = (NodeType*)*insert_location; - const int key_cmp_k = cmp(key, curr); + const RBTreeOrdering key_cmp_k = cmp(key, curr); - if (key_cmp_k == 0) { + if (key_cmp_k == RBTreeOrdering::EQ) { break; } parent = *insert_location; - if (key_cmp_k < 0) { + if (key_cmp_k == RBTreeOrdering::LT) { insert_location = &curr->_left; } else { insert_location = &curr->_right; @@ -551,19 +552,19 @@ inline void AbstractRBTree::replace_at_cursor(NodeType* new_node->_parent = old_node->_parent; if (new_node->is_left_child()) { - assert(cmp(static_cast(new_node), static_cast(new_node->parent())), "new node not < parent"); + assert(less_than(static_cast(new_node), static_cast(new_node->parent())), "new node not < parent"); } else if (new_node->is_right_child()) { - assert(cmp(static_cast(new_node->parent()), static_cast(new_node)), "new node not > parent"); + assert(less_than(static_cast(new_node->parent()), static_cast(new_node)), "new node not > parent"); } new_node->_left = old_node->_left; new_node->_right = old_node->_right; if (new_node->_left != nullptr) { - assert(cmp(static_cast(new_node->_left), static_cast(new_node)), "left child not < new node"); + assert(less_than(static_cast(new_node->_left), static_cast(new_node)), "left child not < new node"); new_node->_left->set_parent(new_node); } if (new_node->_right != nullptr) { - assert(cmp(static_cast(new_node), static_cast(new_node->_right)), "right child not > new node"); + assert(less_than(static_cast(new_node), static_cast(new_node->_right)), "right child not > new node"); new_node->_right->set_parent(new_node); } @@ -661,8 +662,8 @@ inline void AbstractRBTree::visit_range_in_order(const } template -template -inline void AbstractRBTree::verify_self(NodeVerifier verifier) const { +template +inline void AbstractRBTree::verify_self(NODE_VERIFIER verifier, const USER_VERIFIER& extra_verifier) const { if (_root == nullptr) { assert(_num_nodes == 0, "rbtree has %zu nodes but no root", _num_nodes); return; @@ -679,7 +680,7 @@ inline void AbstractRBTree::verify_self(NodeVerifier ve bool expected_visited = DEBUG_ONLY(_expected_visited) NOT_DEBUG(false); _root->verify(num_nodes, black_depth, shortest_leaf_path, longest_leaf_path, - tree_depth, expected_visited, verifier); + tree_depth, expected_visited, verifier, extra_verifier); const unsigned int maximum_depth = log2i(size() + 1) * 2; @@ -731,21 +732,23 @@ inline void RBNode::print_on(outputStream* st, int depth) const { } template -void AbstractRBTree::print_node_on(outputStream* st, int depth, const NodeType* n) const { - n->print_on(st, depth); +template +void AbstractRBTree::print_node_on(outputStream* st, int depth, const NodeType* n, const PRINTER& node_printer) const { + node_printer(st, n, depth); depth++; - if (n->_right != nullptr) { - print_node_on(st, depth, (NodeType*)n->_right); - } if (n->_left != nullptr) { - print_node_on(st, depth, (NodeType*)n->_left); + print_node_on(st, depth, (NodeType*)n->_left, node_printer); + } + if (n->_right != nullptr) { + print_node_on(st, depth, (NodeType*)n->_right, node_printer); } } template -void AbstractRBTree::print_on(outputStream* st) const { +template +void AbstractRBTree::print_on(outputStream* st, const PRINTER& node_printer) const { if (_root != nullptr) { - print_node_on(st, 0, (NodeType*)_root); + print_node_on(st, 0, (NodeType*)_root, node_printer); } } diff --git a/test/hotspot/gtest/utilities/test_rbtree.cpp b/test/hotspot/gtest/utilities/test_rbtree.cpp index d8394de017e..609fb43e59e 100644 --- a/test/hotspot/gtest/utilities/test_rbtree.cpp +++ b/test/hotspot/gtest/utilities/test_rbtree.cpp @@ -36,26 +36,30 @@ public: using RBTreeIntNode = RBNode; struct Cmp { - static int cmp(int a, int b) { - return a - b; + static RBTreeOrdering cmp(int a, int b) { + if (a < b) return RBTreeOrdering::LT; + if (a > b) return RBTreeOrdering::GT; + return RBTreeOrdering::EQ; } - static bool cmp(const RBTreeIntNode* a, const RBTreeIntNode* b) { + static bool less_than(const RBTreeIntNode* a, const RBTreeIntNode* b) { return a->key() < b->key(); } }; struct CmpInverse { - static int cmp(int a, int b) { - return b - a; + static RBTreeOrdering cmp(int a, int b) { + if (a < b) return RBTreeOrdering::GT; + if (a > b) return RBTreeOrdering::LT; + return RBTreeOrdering::EQ; } }; struct FCmp { - static int cmp(float a, float b) { - if (a < b) return -1; - if (a == b) return 0; - return 1; + static RBTreeOrdering cmp(float a, float b) { + if (a < b) return RBTreeOrdering::LT; + if (a > b) return RBTreeOrdering::GT; + return RBTreeOrdering::EQ; } }; @@ -94,16 +98,15 @@ struct ArrayAllocator { }; struct IntrusiveCmp { - static int cmp(int a, const IntrusiveTreeNode* b) { - return a - IntrusiveHolder::cast_to_self(b)->key; - } - - static int cmp(int a, int b) { - return a - b; + static RBTreeOrdering cmp(int a, const IntrusiveTreeNode* b_node) { + int b = IntrusiveHolder::cast_to_self(b_node)->key; + if (a < b) return RBTreeOrdering::LT; + if (a > b) return RBTreeOrdering::GT; + return RBTreeOrdering::EQ; } // true if a < b - static bool cmp(const IntrusiveTreeNode* a, const IntrusiveTreeNode* b) { + static bool less_than(const IntrusiveTreeNode* a, const IntrusiveTreeNode* b) { return (IntrusiveHolder::cast_to_self(a)->key - IntrusiveHolder::cast_to_self(b)->key) < 0; } @@ -837,6 +840,50 @@ public: } } + static bool custom_validator(const IntrusiveRBNode* n) { + IntrusiveHolder* holder = IntrusiveHolder::cast_to_self(n); + assert(holder->key == holder->data, "must be"); + + return true; + } + + void test_custom_verify_intrusive() { + IntrusiveTreeInt intrusive_tree; + int num_nodes = 100; + + // Insert values + for (int n = 0; n < num_nodes; n++) { + IntrusiveCursor cursor = intrusive_tree.cursor(n); + EXPECT_NULL(cursor.node()); + + // Custom allocation here is just malloc + IntrusiveHolder* place = (IntrusiveHolder*)os::malloc(sizeof(IntrusiveHolder), mtTest); + new (place) IntrusiveHolder(n, n); + + intrusive_tree.insert_at_cursor(place->get_node(), cursor); + IntrusiveCursor cursor2 = intrusive_tree.cursor(n); + + EXPECT_NOT_NULL(cursor2.node()); + } + + intrusive_tree.verify_self(RBTreeTest::custom_validator); + + int node_count = 0; + intrusive_tree.verify_self([&](const IntrusiveRBNode* n) { + node_count++; + + IntrusiveHolder* holder = IntrusiveHolder::cast_to_self(n); + assert(holder->key >= 0, "must be"); + assert(holder->data >= 0, "must be"); + assert(holder->key < num_nodes, "must be"); + assert(holder->data < num_nodes, "must be"); + + return true; + }); + + EXPECT_EQ(node_count, num_nodes); + } + #ifdef ASSERT void test_nodes_visited_once() { constexpr size_t memory_size = 65536; @@ -945,10 +992,13 @@ TEST_VM_F(RBTreeTest, LeftMostRightMost) { } struct PtrCmp { - static int cmp(const void* a, const void* b) { + static RBTreeOrdering cmp(const void* a, const void* b) { const uintptr_t ai = p2u(a); const uintptr_t bi = p2u(b); - return ai == bi ? 0 : (ai > bi ? 1 : -1); + + if (ai < bi) return RBTreeOrdering::LT; + if (ai > bi) return RBTreeOrdering::GT; + return RBTreeOrdering::EQ; } }; @@ -982,7 +1032,11 @@ TEST_VM(RBTreeTestNonFixture, TestPrintPointerTree) { } struct IntCmp { - static int cmp(int a, int b) { return a == b ? 0 : (a > b ? 1 : -1); } + static RBTreeOrdering cmp(int a, int b) { + if (a < b) return RBTreeOrdering::LT; + if (a > b) return RBTreeOrdering::GT; + return RBTreeOrdering::EQ; + } }; TEST_VM(RBTreeTestNonFixture, TestPrintIntegerTree) { @@ -1005,10 +1059,42 @@ TEST_VM(RBTreeTestNonFixture, TestPrintIntegerTree) { ASSERT_NE(strstr(ss.base(), s3), N); } +TEST_VM(RBTreeTestNonFixture, TestPrintCustomPrinter) { + typedef RBTree > TreeType; + typedef RBNode NodeType; + + TreeType tree; + const int i1 = -13591; + const int i2 = 0; + const int i3 = 82924; + tree.upsert(i1, 1U); + tree.upsert(i2, 2U); + tree.upsert(i3, 3U); + + stringStream ss; + int print_count = 0; + tree.print_on(&ss, [&](outputStream* st, const NodeType* n, int depth) { + st->print_cr("[%d] (%d): %d", depth, n->val(), n->key()); + print_count++; + }); + +const char* const expected = + "[0] (2): 0\n" + "[1] (1): -13591\n" + "[1] (3): 82924\n"; + + ASSERT_EQ(print_count, 3); + ASSERT_STREQ(ss.base(), expected); +} + TEST_VM_F(RBTreeTest, IntrusiveTest) { this->test_intrusive(); } +TEST_VM_F(RBTreeTest, IntrusiveCustomVerifyTest) { + this->test_custom_verify_intrusive(); +} + TEST_VM_F(RBTreeTest, FillAndVerify) { this->test_fill_verify(); } @@ -1021,6 +1107,22 @@ TEST_VM_F(RBTreeTest, CursorReplace) { TEST_VM_F(RBTreeTest, NodesVisitedOnce) { this->test_nodes_visited_once(); } + +TEST_VM_ASSERT_MSG(RBTreeTestNonFixture, CustomVerifyAssert, ".*failed on key = 7") { + typedef RBTreeCHeap TreeType; + typedef RBNode NodeType; + + TreeType tree; + for (int i = 0; i < 10; i++) { + tree.upsert(i, i); + } + + tree.verify_self([&](const NodeType* n) { + assert(n->key() != 7, "failed on key = %d", n->key()); + return true; + }); +} + #endif // ASSERT TEST_VM_F(RBTreeTest, InsertRemoveVerify) { @@ -1039,6 +1141,25 @@ TEST_VM_F(RBTreeTest, InsertRemoveVerify) { } } +TEST_VM_F(RBTreeTest, CustomVerify) { + constexpr int num_nodes = 1000; + RBTreeInt tree; + for (int i = 0; i < num_nodes; i++) { + tree.upsert(i, i); + } + + int node_count = 0; + tree.verify_self([&](const RBTreeIntNode* n) { + node_count++; + + assert(n->key() >= 0, "must be"); + assert(n->key() < num_nodes, "must be"); + return true; + }); + + EXPECT_EQ(node_count, num_nodes); +} + TEST_VM_F(RBTreeTest, VerifyItThroughStressTest) { { // Repeatedly verify a tree of moderate size RBTreeInt rbtree;