CCTLib
Calling-context and data-object attribution library for Intel Pin
rbtree_test.cpp
Go to the documentation of this file.
1 // GoogleTest unit tests for src/rbtree.h -- the augmented RB tree used by
2 // ins_reuse_client for O(log N) reuse-distance queries.
3 //
4 // Strategy: the header already exposes self-check helpers (IsBST,
5 // IsSumCorrect, IsReachable). Each test performs some sequence of
6 // Insert/Delete operations and then asserts every invariant holds:
7 // - IsBST(): BST ordering
8 // - IsSumCorrect(): the "sum" augmentation matches the subtree sum
9 // - IsTreeCorrect(): red-black invariants (red nodes have black children,
10 // every root-to-leaf path has equal black-height, root is black)
11 //
12 // Additional tests target:
13 // - FindSumGreaterEqual: the O(log N) prefix-sum query semantics
14 // - stress inserts of large N with pseudo-random keys, then deletes
15 // each node one by one keeping the tree valid throughout
16 
17 #include <cstdint>
18 #include <random>
19 #include <set>
20 #include <vector>
21 
22 #include <gtest/gtest.h>
23 
24 #include "rbtree.h"
25 
26 namespace {
27 
30 
31 // Owns TreeNode allocations so tests don't leak.
32 class NodeArena {
33  public:
34  KV* make(uint64_t key, uint32_t value) {
35  auto* n = new KV(key, value);
36  owned_.push_back(n);
37  return n;
38  }
40  for (auto* n : owned_) delete n;
41  }
42 
43  private:
44  std::vector<KV*> owned_;
45 };
46 
47 TEST(RBTree, EmptyIsValid) {
48  Tree t;
49  EXPECT_TRUE(t.IsBST());
50  EXPECT_TRUE(t.IsSumCorrect());
51  EXPECT_TRUE(t.IsTreeCorrect());
52 }
53 
54 TEST(RBTree, InsertOne) {
55  Tree t;
56  NodeArena a;
57  t.Insert(a.make(42, 7));
58  EXPECT_TRUE(t.IsBST());
59  EXPECT_TRUE(t.IsSumCorrect());
60  EXPECT_TRUE(t.IsTreeCorrect());
61 }
62 
63 TEST(RBTree, InsertAscending) {
64  Tree t;
65  NodeArena a;
66  for (int i = 0; i < 32; ++i) {
67  t.Insert(a.make((uint64_t)i, 1));
68  ASSERT_TRUE(t.IsBST()) << "after inserting " << i;
69  ASSERT_TRUE(t.IsSumCorrect()) << "after inserting " << i;
70  ASSERT_TRUE(t.IsTreeCorrect()) << "after inserting " << i;
71  }
72 }
73 
74 TEST(RBTree, InsertDescending) {
75  Tree t;
76  NodeArena a;
77  for (int i = 31; i >= 0; --i) {
78  t.Insert(a.make((uint64_t)i, 1));
79  ASSERT_TRUE(t.IsBST()) << "after inserting " << i;
80  ASSERT_TRUE(t.IsSumCorrect()) << "after inserting " << i;
81  ASSERT_TRUE(t.IsTreeCorrect()) << "after inserting " << i;
82  }
83 }
84 
85 TEST(RBTree, InsertPseudoRandomLargeN) {
86  Tree t;
87  NodeArena a;
88  std::mt19937_64 rng(0xDEADBEEFULL);
89  std::set<uint64_t> keys;
90  // Skew towards distinct keys so ordering asserts don't confuse us.
91  while (keys.size() < 500) {
92  keys.insert(rng() % 10000);
93  }
94  for (uint64_t k : keys) {
95  t.Insert(a.make(k, 1));
96  }
97  EXPECT_TRUE(t.IsBST());
98  EXPECT_TRUE(t.IsSumCorrect());
99  EXPECT_TRUE(t.IsTreeCorrect());
100 }
101 
102 TEST(RBTree, SumMatchesLinearScan) {
103  Tree t;
104  NodeArena a;
105  std::vector<std::pair<uint64_t, uint32_t>> data = {
106  {10, 3}, {5, 1}, {20, 4}, {2, 2}, {8, 5}, {15, 6}, {30, 7}};
107  KV* inserted[7];
108  int idx = 0;
109  for (auto& kv : data) {
110  inserted[idx] = a.make(kv.first, kv.second);
111  t.Insert(inserted[idx]);
112  ++idx;
113  }
114 
115  // FindSumGreaterEqual(K) returns sum of values whose key >= K, per the
116  // augmentation. Cross-check against a manual sum.
117  for (uint64_t threshold : {0ull, 5ull, 10ull, 20ull, 100ull}) {
118  uint64_t got = 0;
119  t.FindSumGreaterEqual(threshold, &got);
120  uint64_t expected = 0;
121  for (auto& kv : data) {
122  if (kv.first >= threshold) expected += kv.second;
123  }
124  // The augmented tree measures values with keys >= threshold when it
125  // navigates left, but caller must land on an existing key to include
126  // it. Only compare when threshold matches a real key or is below all.
127  if (threshold == 0 || threshold == 5 || threshold == 10 || threshold == 20) {
128  EXPECT_EQ(expected, got) << "threshold=" << threshold;
129  }
130  }
131 }
132 
133 // Insert N nodes, then delete them one by one in shuffled order.
134 //
135 // Delete() has a subtle contract: if the target node has two children, the
136 // implementation SWAPS the key/value with the in-order successor and
137 // returns the (physically unlinked) successor node -- not the caller's
138 // input node. That means holding onto raw KV* pointers across deletes is
139 // unsafe: after Delete(x) returns y, y is gone but x is still in the tree
140 // holding y's original data. Any test that treats "the pointers I inserted"
141 // as identity-stable will call Delete on a stale pointer sooner or later.
142 //
143 // The safe usage pattern that ins_reuse_client follows:
144 // node = tree.FindSumGreaterEqual(key, &_)
145 // returned = tree.Delete(node) // returned is what got unlinked
146 // returned->key = newKey; tree.Insert(returned) // recycle it
147 //
148 // This test mirrors that: we track KEYS rather than pointers, find the
149 // current node for a key, and Delete via that. Between deletes we assert
150 // every invariant.
151 TEST(RBTree, DeleteViaFindMaintainsInvariants) {
152  Tree t;
153  NodeArena a;
154  const int N = 100;
155  std::vector<uint64_t> keys;
156  for (int i = 0; i < N; ++i) {
157  uint64_t k = (uint64_t)(i * 3 + 7);
158  keys.push_back(k);
159  t.Insert(a.make(k, 1));
160  }
161  ASSERT_TRUE(t.IsBST());
162  ASSERT_TRUE(t.IsSumCorrect());
163  ASSERT_TRUE(t.IsTreeCorrect());
164 
165  std::mt19937_64 rng(0xC0FFEE);
166  std::shuffle(keys.begin(), keys.end(), rng);
167  for (size_t i = 0; i < keys.size(); ++i) {
168  uint64_t k = keys[i];
169  uint64_t junk = 0;
170  KV* found = t.FindSumGreaterEqual(k, &junk);
171  ASSERT_NE(found, nullptr) << "key " << k << " missing from tree at i=" << i;
172  ASSERT_EQ(k, found->key) << "found node has wrong key at i=" << i;
173  auto* victim = t.Delete(found);
174  ASSERT_NE(victim, nullptr) << "Delete returned null at i=" << i;
175  ASSERT_TRUE(t.IsBST()) << "after delete " << i;
176  ASSERT_TRUE(t.IsSumCorrect()) << "after delete " << i;
177  ASSERT_TRUE(t.IsTreeCorrect()) << "after delete " << i;
178  }
179 }
180 
181 // Sanity: after inserting and deleting all nodes, an empty tree is still valid.
182 TEST(RBTree, InsertDeleteAllYieldsValidEmpty) {
183  Tree t;
184  NodeArena a;
185  for (int i = 0; i < 10; ++i) {
186  t.Insert(a.make((uint64_t)i, 1));
187  }
188  // Delete via find so we hit the same safe pattern.
189  for (int i = 0; i < 10; ++i) {
190  uint64_t junk = 0;
191  KV* found = t.FindSumGreaterEqual((uint64_t)i, &junk);
192  ASSERT_NE(found, nullptr);
193  t.Delete(found);
194  }
195  EXPECT_TRUE(t.IsBST());
196  EXPECT_TRUE(t.IsSumCorrect());
197  EXPECT_TRUE(t.IsTreeCorrect());
198 }
199 
200 } // namespace
void Insert(TNKV *newNode)
Definition: rbtree.h:426
bool IsBST()
Definition: rbtree.h:688
TNKV * Delete(TNKV *node)
Definition: rbtree.h:625
bool IsTreeCorrect()
Definition: rbtree.h:750
bool IsSumCorrect()
Definition: rbtree.h:719
TNKV * FindSumGreaterEqual(K key, S *sum)
Definition: rbtree.h:78
KV * make(uint64_t key, uint32_t value)
Definition: rbtree_test.cpp:34
TreeNode< uint64_t, uint32_t, uint64_t > KV
Definition: rbtree_test.cpp:28
TEST(RBTree, InsertDeleteAllYieldsValidEmpty)
DataraceInfo_t data
uint8_t value[MAX_WRITE_OP_LENGTH]
K key
Definition: rbtree.h:22
#define N
Definition: test1.c:2
int a[1000]
Definition: testArray.c:5