testAvlTree.cpp (12098B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #include <set> 6 #include <stdio.h> 7 8 #include "ds/AvlTree.h" 9 10 #include "jsapi-tests/tests.h" 11 12 using namespace js; 13 14 //////////////////////////////////////////////////////////////////////// 15 // // 16 // AvlTree testing interface. // 17 // // 18 //////////////////////////////////////////////////////////////////////// 19 20 // The "standard" AVL interface, `class AvlTree` at the end of 21 // js/src/ds/AvlTree.h, is too restrictive to allow proper testing of the AVL 22 // tree internals. In particular it disallows insertion of duplicate values 23 // and removal of non-present values, and lacks various secondary functions 24 // such as for counting the number of nodes. 25 // 26 // So, for testing, we wrap an alternative interface `AvlTreeTestIF` around 27 // the core implementation. 28 29 template <class T, class C> 30 class AvlTreeTestIF : public AvlTreeImpl<T, C> { 31 // Shorthands for names in the implementation (parent) class. 32 using Impl = AvlTreeImpl<T, C>; 33 using ImplTag = typename AvlTreeImpl<T, C>::Tag; 34 using ImplNode = typename AvlTreeImpl<T, C>::Node; 35 using ImplResult = typename AvlTreeImpl<T, C>::Result; 36 using ImplNodeAndResult = typename AvlTreeImpl<T, C>::NodeAndResult; 37 38 public: 39 explicit AvlTreeTestIF(LifoAlloc* alloc = nullptr) : Impl(alloc) {} 40 41 // Insert `v` if it isn't already there, else leave the tree unchanged. 42 // Returns true iff an insertion happened. 43 bool testInsert(const T& v) { 44 ImplNode* new_root = Impl::insert_worker(v); 45 if (!new_root) { 46 // OOM 47 MOZ_CRASH(); 48 } 49 if (uintptr_t(new_root) == uintptr_t(1)) { 50 // Already present 51 return false; 52 } 53 Impl::root_ = new_root; 54 return true; 55 } 56 57 // Remove `v` if it is present. Returns true iff a removal happened. 58 bool testRemove(const T& v) { 59 ImplNodeAndResult pair = Impl::delete_worker(Impl::root_, v); 60 ImplNode* new_root = pair.first; 61 ImplResult res = pair.second; 62 if (res == ImplResult::Error) { 63 // `v` isn't in the tree. 64 return false; 65 } else { 66 Impl::root_ = new_root; 67 return true; 68 } 69 } 70 71 // Count number of elements 72 size_t testSize_worker(ImplNode* n) const { 73 if (n) { 74 return 1 + testSize_worker(n->left) + testSize_worker(n->getRight()); 75 } 76 return 0; 77 } 78 size_t testSize() const { return testSize_worker(Impl::root_); } 79 80 size_t testDepth_worker(ImplNode* n) const { 81 if (n) { 82 size_t depthL = testDepth_worker(n->left); 83 size_t depthR = testDepth_worker(n->getRight()); 84 return 1 + (depthL > depthR ? depthL : depthR); 85 } 86 return 0; 87 } 88 size_t testDepth() const { return testDepth_worker(Impl::root_); } 89 90 bool testContains(const T& v) const { 91 ImplNode* node = Impl::find_worker(v); 92 return node != nullptr; 93 } 94 95 ImplNode* testGetRoot() const { return Impl::root_; } 96 ImplNode* testGetFreeList() const { return Impl::freeList_; } 97 98 bool testFreeListLooksValid(size_t maxElems) { 99 size_t numElems = 0; 100 ImplNode* node = Impl::freeList_; 101 while (node) { 102 numElems++; 103 if (numElems > maxElems) { 104 return false; 105 } 106 if (node->getTag() != ImplTag::Free || node->getRight() != nullptr) { 107 return false; 108 } 109 node = node->left; 110 } 111 return true; 112 } 113 114 // For debugging only 115 private: 116 void testShow_worker(int depth, const ImplNode* node) const { 117 if (node) { 118 testShow_worker(depth + 1, node->getRight()); 119 for (int i = 0; i < depth; i++) { 120 printf(" "); 121 } 122 char* str = node->item.show(); 123 printf("%s\n", str); 124 free(str); 125 testShow_worker(depth + 1, node->left); 126 } 127 } 128 129 public: 130 // For debugging only 131 void testShow() const { testShow_worker(0, Impl::root_); } 132 133 // AvlTree::Iter is also public; it's just pass-through from AvlTreeImpl. 134 }; 135 136 //////////////////////////////////////////////////////////////////////// 137 // // 138 // AvlTree test cases. // 139 // // 140 //////////////////////////////////////////////////////////////////////// 141 142 class CmpInt { 143 int x_; 144 145 public: 146 explicit CmpInt(int x) : x_(x) {} 147 ~CmpInt() {} 148 static int compare(const CmpInt& me, const CmpInt& other) { 149 if (me.x_ < other.x_) return -1; 150 if (me.x_ > other.x_) return 1; 151 return 0; 152 } 153 int get() const { return x_; } 154 char* show() const { 155 const size_t length = 16; 156 char* str = (char*)calloc(length, 1); 157 snprintf(str, length, "%d", x_); 158 return str; 159 } 160 }; 161 162 bool TreeIsPlausible(const AvlTreeTestIF<CmpInt, CmpInt>& tree, 163 const std::set<int>& should_be_in_tree, int UNIV_MIN, 164 int UNIV_MAX) { 165 // Same cardinality 166 size_t n_in_set = should_be_in_tree.size(); 167 size_t n_in_tree = tree.testSize(); 168 if (n_in_set != n_in_tree) { 169 return false; 170 } 171 172 // Tree is not wildly out of balance. Depth should not exceed 1.44 * 173 // log2(size). 174 size_t tree_depth = tree.testDepth(); 175 size_t log2_size = 0; 176 { 177 size_t n = n_in_tree; 178 while (n > 0) { 179 n = n >> 1; 180 log2_size += 1; 181 } 182 } 183 // Actually a tighter limit than stated above. For these test cases, the 184 // tree is either perfectly balanced or within one level of being so (hence 185 // the +1). 186 if (tree_depth > log2_size + 1) { 187 return false; 188 } 189 190 // Check that everything that should be in the tree is in it, and vice 191 // versa. 192 for (int i = UNIV_MIN; i < UNIV_MAX; i++) { 193 bool should_be_in = should_be_in_tree.find(i) != should_be_in_tree.end(); 194 195 // Look it up with a null comparator (so `contains` compares 196 // directly) 197 bool is_in = tree.testContains(CmpInt(i)); 198 if (is_in != should_be_in) { 199 return false; 200 } 201 } 202 203 return true; 204 } 205 206 template <typename T> 207 bool SetContains(std::set<T> s, const T& v) { 208 return s.find(v) != s.end(); 209 } 210 211 BEGIN_TEST(testAvlTree_main) { 212 static const int UNIV_MIN = 5000; 213 static const int UNIV_MAX = 5999; 214 static const int UNIV_SIZE = UNIV_MAX - UNIV_MIN + 1; 215 216 LifoAlloc alloc(4096, js::MallocArena); 217 AvlTreeTestIF<CmpInt, CmpInt> tree(&alloc); 218 std::set<int> should_be_in_tree; 219 220 // Add numbers to the tree, checking as we go. 221 for (int i = UNIV_MIN; i < UNIV_MAX; i++) { 222 // Idiotic but simple 223 if (i % 3 != 0) { 224 continue; 225 } 226 bool was_added = tree.testInsert(CmpInt(i)); 227 should_be_in_tree.insert(i); 228 CHECK(was_added); 229 CHECK(TreeIsPlausible(tree, should_be_in_tree, UNIV_MIN, UNIV_MAX)); 230 } 231 232 // Then remove the middle half of the tree, also checking. 233 for (int i = UNIV_MIN + UNIV_SIZE / 4; i < UNIV_MIN + 3 * (UNIV_SIZE / 4); 234 i++) { 235 // Note that here, we're asking to delete a bunch of numbers that aren't 236 // in the tree. It should remain valid throughout. 237 bool was_removed = tree.testRemove(CmpInt(i)); 238 bool should_have_been_removed = SetContains(should_be_in_tree, i); 239 CHECK(was_removed == should_have_been_removed); 240 should_be_in_tree.erase(i); 241 CHECK(TreeIsPlausible(tree, should_be_in_tree, UNIV_MIN, UNIV_MAX)); 242 } 243 244 // Now add some numbers which are already in the tree. 245 for (int i = UNIV_MIN; i < UNIV_MIN + UNIV_SIZE / 4; i++) { 246 if (i % 3 != 0) { 247 continue; 248 } 249 bool was_added = tree.testInsert(CmpInt(i)); 250 bool should_have_been_added = !SetContains(should_be_in_tree, i); 251 CHECK(was_added == should_have_been_added); 252 should_be_in_tree.insert(i); 253 CHECK(TreeIsPlausible(tree, should_be_in_tree, UNIV_MIN, UNIV_MAX)); 254 } 255 256 // Then remove all numbers from the tree, in reverse order. 257 for (int ir = UNIV_MIN; ir < UNIV_MAX; ir++) { 258 int i = UNIV_MIN + (UNIV_MAX - ir); 259 bool was_removed = tree.testRemove(CmpInt(i)); 260 bool should_have_been_removed = SetContains(should_be_in_tree, i); 261 CHECK(was_removed == should_have_been_removed); 262 should_be_in_tree.erase(i); 263 CHECK(TreeIsPlausible(tree, should_be_in_tree, UNIV_MIN, UNIV_MAX)); 264 } 265 266 // Now the tree should be empty. 267 CHECK(should_be_in_tree.empty()); 268 CHECK(tree.testSize() == 0); 269 270 // Now delete some more stuff. Tree should still be empty :-) 271 for (int i = UNIV_MIN + 10; i < UNIV_MIN + 100; i++) { 272 CHECK(should_be_in_tree.empty()); 273 CHECK(tree.testSize() == 0); 274 bool was_removed = tree.testRemove(CmpInt(i)); 275 CHECK(!was_removed); 276 CHECK(TreeIsPlausible(tree, should_be_in_tree, UNIV_MIN, UNIV_MAX)); 277 } 278 279 // The tree root should be NULL. 280 CHECK(tree.testGetRoot() == nullptr); 281 CHECK(tree.testGetFreeList() != nullptr); 282 283 // Check the freelist to the extent we can: it's non-circular, and the 284 // elements look plausible. If it's not shorter than the specified length 285 // then it must have a cycle, since the tests above won't have resulted in 286 // more than 400 free nodes at the end. 287 CHECK(tree.testFreeListLooksValid(400 /*arbitrary*/)); 288 289 // Test iteration, first in a tree with 9 nodes. This tests the general 290 // case. 291 { 292 CHECK(tree.testSize() == 0); 293 for (int i = 10; i < 100; i += 10) { 294 bool was_inserted = tree.testInsert(CmpInt(i)); 295 CHECK(was_inserted); 296 } 297 298 // Test iteration across the whole tree. 299 AvlTreeTestIF<CmpInt, CmpInt>::Iter iter(&tree); 300 // `expect` produces (independently) the next expected number. `remaining` 301 // counts the number items of items remaining. 302 int expect = 10; 303 int remaining = 9; 304 while (iter.hasMore()) { 305 CmpInt ci = iter.next(); 306 CHECK(ci.get() == expect); 307 expect += 10; 308 remaining--; 309 } 310 CHECK(remaining == 0); 311 312 // Test iteration from a specified start point. 313 for (int i = 10; i < 100; i += 10) { 314 for (int j = i - 1; j <= i + 1; j++) { 315 // Set up `expect` and `remaining`. 316 remaining = (100 - i) / 10; 317 switch (j % 10) { 318 case 0: 319 expect = j; 320 break; 321 case 1: 322 expect = j + 9; 323 remaining--; 324 break; 325 case 9: 326 expect = j + 1; 327 break; 328 default: 329 MOZ_CRASH(); 330 } 331 AvlTreeTestIF<CmpInt, CmpInt>::Iter iter(&tree, CmpInt(j)); 332 while (iter.hasMore()) { 333 CmpInt ci = iter.next(); 334 CHECK(ci.get() == expect); 335 expect += 10; 336 remaining--; 337 } 338 CHECK(remaining == 0); 339 } 340 } 341 } 342 343 // Now with a completely empty tree. 344 { 345 AvlTreeTestIF<CmpInt, CmpInt> emptyTree(&alloc); 346 CHECK(emptyTree.testSize() == 0); 347 // Full tree iteration gets us nothing. 348 AvlTreeTestIF<CmpInt, CmpInt>::Iter iter1(&emptyTree); 349 CHECK(!iter1.hasMore()); 350 // Starting iteration with any number gets us nothing. 351 AvlTreeTestIF<CmpInt, CmpInt>::Iter iter2(&emptyTree, CmpInt(42)); 352 CHECK(!iter2.hasMore()); 353 } 354 355 // Finally with a one-element tree. 356 { 357 AvlTreeTestIF<CmpInt, CmpInt> unitTree(&alloc); 358 bool was_inserted = unitTree.testInsert(CmpInt(1337)); 359 CHECK(was_inserted); 360 CHECK(unitTree.testSize() == 1); 361 // Try full tree iteration. 362 AvlTreeTestIF<CmpInt, CmpInt>::Iter iter3(&unitTree); 363 CHECK(iter3.hasMore()); 364 CmpInt ci = iter3.next(); 365 CHECK(ci.get() == 1337); 366 CHECK(!iter3.hasMore()); 367 for (int i = 1336; i <= 1338; i++) { 368 int remaining = i < 1338 ? 1 : 0; 369 int expect = i < 1338 ? 1337 : 99999 /*we'll never use this*/; 370 AvlTreeTestIF<CmpInt, CmpInt>::Iter iter4(&unitTree, CmpInt(i)); 371 while (iter4.hasMore()) { 372 CmpInt ci = iter4.next(); 373 CHECK(ci.get() == expect); 374 remaining--; 375 // expect doesn't change, we only expect it (or nothing) 376 } 377 CHECK(remaining == 0); 378 } 379 } 380 381 return true; 382 } 383 END_TEST(testAvlTree_main)