tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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)