8366238: Improve RBTree API with stricter comparator semantics and pluggable validation/printing hooks

Reviewed-by: jsjolen, ayang
This commit is contained in:
Casper Norrbin 2025-09-04 09:47:42 +00:00
parent ab9f70dd5a
commit 53d4e928ef
7 changed files with 258 additions and 100 deletions

View File

@ -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;
}

View File

@ -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 {

View File

@ -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;
}
};

View File

@ -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;
}
};

View File

@ -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<K, V>* a, const RBNode<K, V>* b)`
// - RBTreeOrdering::LT when a < b
// - RBTreeOrdering::EQ when a == b
// - RBTreeOrdering::GT when a > b
// A second static function `less_than(const RBNode<K, V>* a, const RBNode<K, V>* 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 <typename K, typename NodeType, typename COMPARATOR>
class AbstractRBTree;
@ -126,10 +124,11 @@ private:
// Returns left child (now parent)
IntrusiveRBNode* rotate_right();
template <typename NodeType, typename NodeVerifier>
template <typename NodeType, typename NODE_VERIFIER, typename USER_VERIFIER>
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, RET, ARG1, ARG2, decltype(static_cast<RET(*)(ARG1, ARG2)>(CMP::cmp), void())> : std::true_type {};
template <typename CMP>
static constexpr bool HasKeyComparator = has_cmp_type<CMP, int, K, K>::value;
static constexpr bool HasKeyComparator = has_cmp_type<CMP, RBTreeOrdering, K, K>::value;
template <typename CMP>
static constexpr bool HasNodeComparator = has_cmp_type<CMP, int, K, const NodeType*>::value;
static constexpr bool HasNodeComparator = has_cmp_type<CMP, RBTreeOrdering, K, const NodeType*>::value;
template <typename CMP, typename RET, typename ARG1, typename ARG2, typename = void>
struct has_less_than_type : std::false_type {};
template <typename CMP, typename RET, typename ARG1, typename ARG2>
struct has_less_than_type<CMP, RET, ARG1, ARG2, decltype(static_cast<RET(*)(ARG1, ARG2)>(CMP::less), void())> : std::true_type {};
template <typename CMP>
static constexpr bool HasNodeVerifier = has_cmp_type<CMP, bool, const NodeType*, const NodeType*>::value;
static constexpr bool HasNodeVerifier = has_less_than_type<CMP, bool, const NodeType*, const NodeType*>::value;
template <typename CMP = COMPARATOR, ENABLE_IF(HasKeyComparator<CMP> && !HasNodeComparator<CMP>)>
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 <typename CMP = COMPARATOR, ENABLE_IF(HasNodeComparator<CMP>)>
int cmp(const K& a, const NodeType* b) const {
RBTreeOrdering cmp(const K& a, const NodeType* b) const {
return COMPARATOR::cmp(a, b);
}
template <typename CMP = COMPARATOR, ENABLE_IF(!HasNodeVerifier<CMP>)>
bool cmp(const NodeType* a, const NodeType* b) const {
bool less_than(const NodeType* a, const NodeType* b) const {
return true;
}
template <typename CMP = COMPARATOR, ENABLE_IF(HasNodeVerifier<CMP>)>
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 <typename CMP = COMPARATOR, ENABLE_IF(HasKeyComparator<CMP>)>
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 <typename NodeVerifier>
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 <typename NODE_VERIFIER, typename USER_VERIFIER>
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 <typename PRINTER>
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 <typename CMP = COMPARATOR, ENABLE_IF(HasNodeVerifier<CMP>)>
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 <typename USER_VERIFIER = empty_verifier, typename CMP = COMPARATOR, ENABLE_IF(HasNodeVerifier<CMP>)>
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 <typename CMP = COMPARATOR, ENABLE_IF(HasKeyComparator<CMP> && !HasNodeVerifier<CMP>)>
void verify_self() const {
verify_self([](const NodeType* a, const NodeType* b){ return COMPARATOR::cmp(a->key(), b->key()) < 0; });
template <typename USER_VERIFIER = empty_verifier, typename CMP = COMPARATOR,
ENABLE_IF(HasKeyComparator<CMP> && !HasNodeVerifier<CMP>)>
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 <typename CMP = COMPARATOR, ENABLE_IF(HasNodeComparator<CMP> && !HasKeyComparator<CMP> && !HasNodeVerifier<CMP>)>
void verify_self() const {
verify_self([](const NodeType*, const NodeType*){ return true;});
template <typename USER_VERIFIER = empty_verifier, typename CMP = COMPARATOR,
ENABLE_IF(HasNodeComparator<CMP> && !HasKeyComparator<CMP> && !HasNodeVerifier<CMP>)>
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 <typename PRINTER = default_printer>
void print_on(outputStream* st, const PRINTER& node_printer = PRINTER()) const;
};
@ -451,6 +476,14 @@ class RBTree : public AbstractRBTree<K, RBNode<K, V>, COMPARATOR> {
public:
RBTree() : BaseType(), _allocator() {}
~RBTree() { remove_all(); }
RBTree(const RBTree& other) : BaseType(), _allocator() {
assert(std::is_copy_constructible<V>(), "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;

View File

@ -123,10 +123,11 @@ inline IntrusiveRBNode* IntrusiveRBNode::next() {
return const_cast<IntrusiveRBNode*>(static_cast<const IntrusiveRBNode*>(this)->next());
}
template <typename NodeType, typename NodeVerifier>
template <typename NodeType, typename NODE_VERIFIER, typename USER_VERIFIER>
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<const NodeType*>(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<NodeType>(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<NodeType>(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<K, NodeType, COMPARATOR>::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<K, NodeType, COMPARATOR>::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<K, NodeType, COMPARATOR>::replace_at_cursor(NodeType*
new_node->_parent = old_node->_parent;
if (new_node->is_left_child()) {
assert(cmp(static_cast<const NodeType*>(new_node), static_cast<const NodeType*>(new_node->parent())), "new node not < parent");
assert(less_than(static_cast<const NodeType*>(new_node), static_cast<const NodeType*>(new_node->parent())), "new node not < parent");
} else if (new_node->is_right_child()) {
assert(cmp(static_cast<const NodeType*>(new_node->parent()), static_cast<const NodeType*>(new_node)), "new node not > parent");
assert(less_than(static_cast<const NodeType*>(new_node->parent()), static_cast<const NodeType*>(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<const NodeType*>(new_node->_left), static_cast<const NodeType*>(new_node)), "left child not < new node");
assert(less_than(static_cast<const NodeType*>(new_node->_left), static_cast<const NodeType*>(new_node)), "left child not < new node");
new_node->_left->set_parent(new_node);
}
if (new_node->_right != nullptr) {
assert(cmp(static_cast<const NodeType*>(new_node), static_cast<const NodeType*>(new_node->_right)), "right child not > new node");
assert(less_than(static_cast<const NodeType*>(new_node), static_cast<const NodeType*>(new_node->_right)), "right child not > new node");
new_node->_right->set_parent(new_node);
}
@ -661,8 +662,8 @@ inline void AbstractRBTree<K, NodeType, COMPARATOR>::visit_range_in_order(const
}
template <typename K, typename NodeType, typename COMPARATOR>
template <typename NodeVerifier>
inline void AbstractRBTree<K, NodeType, COMPARATOR>::verify_self(NodeVerifier verifier) const {
template <typename NODE_VERIFIER, typename USER_VERIFIER>
inline void AbstractRBTree<K, NodeType, COMPARATOR>::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<K, NodeType, COMPARATOR>::verify_self(NodeVerifier ve
bool expected_visited = DEBUG_ONLY(_expected_visited) NOT_DEBUG(false);
_root->verify<NodeType>(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<K, V>::print_on(outputStream* st, int depth) const {
}
template <typename K, typename NodeType, typename COMPARATOR>
void AbstractRBTree<K, NodeType, COMPARATOR>::print_node_on(outputStream* st, int depth, const NodeType* n) const {
n->print_on(st, depth);
template <typename PRINTER>
void AbstractRBTree<K, NodeType, COMPARATOR>::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 <typename K, typename NodeType, typename COMPARATOR>
void AbstractRBTree<K, NodeType, COMPARATOR>::print_on(outputStream* st) const {
template <typename PRINTER>
void AbstractRBTree<K, NodeType, COMPARATOR>::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);
}
}

View File

@ -36,26 +36,30 @@ public:
using RBTreeIntNode = RBNode<int, int>;
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<int, unsigned, IntCmp, RBTreeCHeapAllocator<mtTest> > TreeType;
typedef RBNode<int, unsigned> 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<int, int, IntCmp, mtTest> TreeType;
typedef RBNode<int, int> 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;