blob: 27b61bec1e92a69ba1f50bfbfc40309ecafbc484 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#pragma once
#include <unittest/unittest.h>
#include <fbl/intrusive_wavl_tree.h>
namespace fbl {
namespace tests {
namespace intrusive_containers {
using ::fbl::internal::is_sentinel_ptr;
using ::fbl::internal::valid_sentinel_ptr;
class WAVLTreeChecker {
public:
template <typename TreeType>
static bool VerifyParentBackLinks(const TreeType& tree,
typename TreeType::RawPtrType node) {
BEGIN_TEST;
using NodeTraits = typename TreeType::NodeTraits;
ASSERT_TRUE(valid_sentinel_ptr(node), "");
const auto& ns = NodeTraits::node_state(*node);
if (valid_sentinel_ptr(ns.left_)) {
EXPECT_EQ(node, NodeTraits::node_state(*ns.left_).parent_,
"Corrupt left-side parent back-link!");
}
if (valid_sentinel_ptr(ns.right_)) {
EXPECT_EQ(node, NodeTraits::node_state(*ns.right_).parent_,
"Corrupt right-side parent back-link!");
}
END_TEST;
}
template <typename TreeType>
static bool SanityCheck(const TreeType& tree) {
BEGIN_TEST;
using NodeTraits = typename TreeType::NodeTraits;
using RawPtrType = typename TreeType::RawPtrType;
using Observer = typename TreeType::Observer;
// Enforce the sentinel requirements.
if (tree.is_empty()) {
// If the tree is empty, then the root should be null, and the
// left/right-most members should be set to the sentinel value.
ASSERT_NULL(tree.root_, "");
ASSERT_EQ(tree.sentinel(), tree.left_most_, "");
ASSERT_EQ(tree.sentinel(), tree.right_most_, "");
EXPECT_EQ(0u, tree.size(), "");
} else {
// If the tree is non-empty, then the root, left-most and
// right-most pointers should all be valid. The LR-child of the
// LR-most element should be the sentinel value.
ASSERT_NONNULL(tree.root_, "");
ASSERT_FALSE(is_sentinel_ptr(tree.root_), "");
ASSERT_NONNULL(tree.left_most_, "");
ASSERT_FALSE(is_sentinel_ptr(tree.left_most_), "");
ASSERT_EQ(tree.sentinel(),
NodeTraits::node_state(*tree.left_most_).left_,
"");
ASSERT_NONNULL(tree.right_most_, "");
ASSERT_FALSE(is_sentinel_ptr(tree.right_most_), "");
ASSERT_EQ(tree.sentinel(),
NodeTraits::node_state(*tree.right_most_).right_,
"");
EXPECT_LT(0u, tree.size(), "");
}
// Compute the size and depth of the tree, verifying the parent
// back-links as we go. If this checker is being used as part of the
// balance test, we will also verify the rank rule as we go as well as
// the balance bounds at the end.
uint64_t cur_depth = 0;
uint64_t depth = 0;
size_t size = 0;
RawPtrType node = tree.root_;
// Start by going left until we have determined the depth of the
// left most node of the tree.
while (valid_sentinel_ptr(node)) {
ASSERT_TRUE(VerifyParentBackLinks(tree, node), "");
auto& ns = NodeTraits::node_state(*node);
++cur_depth;
if (!valid_sentinel_ptr(ns.left_))
break;
node = ns.left_;
}
// Now walk through the tree in ascending key order. The basic
// idea is...
//
// 1) "Visit" our current node, recording the new max depth if we
// have discovered one and checking the rank rule.
// 2) If there is a right hand child, advance to the left-most node
// of the right hand sub-tree.
// 3) If there is no right hand child, climb the tree until we
// traverse a left hand link.
// 4) If we don't traverse a left hand link before hitting the root
// of the tree, then we are done.
while (valid_sentinel_ptr(node)) {
// #1: Visit
if (depth < cur_depth)
depth = cur_depth;
++size;
ASSERT_TRUE(VerifyParentBackLinks(tree, node), "");
// Verify the rank rule using the tree's observer's implementation.
// If this sanity check is being run as part of the balance test, it
// will use the implementation above. For all other tests, only the
// rank parity is stored in the node, so we cannot rigorously verify
// the rule. The default VerifyRankRule implementation will be used
// instead (which just returns true).
ASSERT_TRUE(Observer::VerifyRankRule(tree, node), "");
// #2: Can we go right?
const auto& ns = NodeTraits::node_state(*node);
if (valid_sentinel_ptr(ns.right_)) {
cur_depth++;
node = ns.right_;
ASSERT_TRUE(VerifyParentBackLinks(tree, node), "");
// Now go as far left as we can.
while (true) {
auto& ns = NodeTraits::node_state(*node);
if (!valid_sentinel_ptr(ns.left_))
break;
++cur_depth;
node = ns.left_;
ASSERT_TRUE(VerifyParentBackLinks(tree, node), "");
}
// Loop and visit the next node.
continue;
}
// #3: Climb until we traverse a left-hand link.
RawPtrType parent = ns.parent_;
bool keep_going;
while ((keep_going = valid_sentinel_ptr(parent))) {
auto& parent_ns = NodeTraits::node_state(*parent);
bool is_left = (parent_ns.left_ == node);
bool is_right = (parent_ns.right_ == node);
ASSERT_TRUE(is_left != is_right, "");
ASSERT_TRUE(is_left || is_right, "");
node = parent;
--cur_depth;
if (is_left)
break;
parent = parent_ns.parent_;
}
// #4: If we ran out of valid parents to climb past, we are done.
if (!keep_going)
break;
}
// We should have visited all of the nodes by now.
ASSERT_EQ(tree.size(), size, "");
// If this is being called from the balance tests, check the balance
// bounds, and computational complexity bounds.
ASSERT_TRUE(Observer::VerifyBalance(tree, depth), "");
END_TEST;
}
};
} // namespace intrusive_trees
} // namespace tests
} // namespace fbl