blob: 5a24b485a626c4ec4d54f0f4f9edce0d4d0db085 [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.
#include <math.h>
#include <algorithm>
#include <iterator>
#include <fbl/intrusive_pointer_traits.h>
#include <fbl/intrusive_wavl_tree.h>
#include <fbl/tests/intrusive_containers/intrusive_wavl_tree_checker.h>
#include <fbl/tests/intrusive_containers/ordered_associative_container_test_environment.h>
#include <fbl/tests/intrusive_containers/test_thunks.h>
#include <zxtest/zxtest.h>
namespace fbl {
namespace tests {
namespace intrusive_containers {
using ::fbl::internal::valid_sentinel_ptr;
template <typename ContainerStateType>
struct OtherTreeTraits {
using KeyType = typename ContainerStateType::KeyType;
using PtrType = typename ContainerStateType::PtrType;
using PtrTraits = ::fbl::internal::ContainerPtrTraits<PtrType>;
// Node Traits
static WAVLTreeNodeState<PtrType>& node_state(typename PtrTraits::RefType obj) {
return obj.other_container_state_.node_state_;
}
// Key Traits
static KeyType GetKey(typename PtrTraits::ConstRefType obj) {
return obj.other_container_state_.key_;
}
static bool LessThan(const KeyType& key1, const KeyType& key2) { return key1 < key2; }
static bool EqualTo(const KeyType& key1, const KeyType& key2) { return key1 == key2; }
// Set key is a trait which is only used by the tests, not by the containers
// themselves.
static void SetKey(typename PtrTraits::RefType obj, KeyType key) {
obj.other_container_state_.key_ = key;
}
};
template <typename _KeyType, typename _PtrType>
class OtherTreeNodeState {
public:
using KeyType = _KeyType;
using PtrType = _PtrType;
private:
friend struct OtherTreeTraits<OtherTreeNodeState<KeyType, PtrType>>;
WAVLTreeNodeState<PtrType> node_state_;
KeyType key_ = 0;
};
template <typename PtrType, NodeOptions kNodeOptions = NodeOptions::None>
class WAVLTraits {
public:
// clang-format off
using KeyType = size_t;
using TestObjBaseType = KeyedTestObjBase<KeyType>;
using ContainerType = WAVLTree<KeyType, PtrType>;
using ContainableBaseClass = WAVLTreeContainable<PtrType, kNodeOptions>;
using ContainerStateType = WAVLTreeNodeState<PtrType, kNodeOptions>;
using OtherContainerStateType = OtherTreeNodeState<KeyType, PtrType>;
using OtherContainerTraits = OtherTreeTraits<OtherContainerStateType>;
using OtherContainerType = WAVLTree<KeyType,
PtrType,
OtherContainerTraits,
DefaultObjectTag,
OtherContainerTraits>;
struct Tag1 {};
struct Tag2 {};
struct Tag3 {};
using TaggedContainableBaseClasses =
fbl::ContainableBaseClasses<TaggedWAVLTreeContainable<PtrType, Tag1>,
TaggedWAVLTreeContainable<PtrType, Tag2>,
TaggedWAVLTreeContainable<PtrType, Tag3>>;
using TaggedType1 = TaggedWAVLTree<KeyType, PtrType, Tag1>;
using TaggedType2 = TaggedWAVLTree<KeyType, PtrType, Tag2>;
using TaggedType3 = TaggedWAVLTree<KeyType, PtrType, Tag3>;
// clang-format on
};
// Just a sanity check so we know our metaprogramming nonsense is
// doing what we expect:
static_assert(
std::is_same_v<typename WAVLTraits<int*>::TaggedContainableBaseClasses::TagTypes,
std::tuple<typename WAVLTraits<int*>::Tag1, typename WAVLTraits<int*>::Tag2,
typename WAVLTraits<int*>::Tag3>>);
// Generate all of the standard tests.
// clang-format off
DEFINE_TEST_OBJECTS(WAVL);
using UMTE = DEFINE_TEST_THUNK(OrderedAssociative, WAVL, Unmanaged);
using UPDDTE = DEFINE_TEST_THUNK(OrderedAssociative, WAVL, UniquePtrDefaultDeleter);
using UPCDTE = DEFINE_TEST_THUNK(OrderedAssociative, WAVL, UniquePtrCustomDeleter);
using RPTE = DEFINE_TEST_THUNK(OrderedAssociative, WAVL, RefPtr);
VERIFY_CONTAINER_SIZES(WAVL, sizeof(void*) * 4);
// Versions of the test objects which support clear_unsafe.
template <typename PtrType>
using CU_WAVLTraits = WAVLTraits<PtrType, fbl::NodeOptions::AllowClearUnsafe>;
DEFINE_TEST_OBJECTS(CU_WAVL);
using CU_UMTE = DEFINE_TEST_THUNK(OrderedAssociative, CU_WAVL, Unmanaged);
using CU_UPDDTE = DEFINE_TEST_THUNK(OrderedAssociative, CU_WAVL, UniquePtrDefaultDeleter);
// clang-format on
// WAVLBalanceTestObserver
//
// An implementation of a WAVLTree Observer which collects stats on the number
// of balance operations (inserts, erases, rank promotions, rank demotions and
// rotatations) which have taken place. It is used by the BalanceTest to verify
// that...
//
// 1) The computation costs of rebalancing after insert and erase are amortized
// constant and obey their specific worst-case constant bounds.
// 2) The maximum depth bounds for trees with just insert operations, and with
// both insert and erase operations, are obeyed.
// 3) Sufficient code coverage has been achieved during testing (eg. all of the
// rebalancing edge cases have been run over the length of the test).
class WAVLBalanceTestObserver {
public:
struct OpCounts {
OpCounts() = default;
void reset() { *this = OpCounts{}; }
void accumulate(OpCounts& target) {
target.insert_ops_ += insert_ops_;
target.insert_promotes_ += insert_promotes_;
target.insert_rotations_ += insert_rotations_;
target.insert_double_rotations_ += insert_double_rotations_;
target.insert_collisions_ += insert_collisions_;
target.insert_replacements_ += insert_replacements_;
target.insert_traversals_ += insert_traversals_;
target.inspected_rotations_ += inspected_rotations_;
target.erase_ops_ += erase_ops_;
target.erase_demotes_ += erase_demotes_;
target.erase_rotations_ += erase_rotations_;
target.erase_double_rotations_ += erase_double_rotations_;
}
size_t insert_ops_{0};
size_t insert_promotes_{0};
size_t insert_rotations_{0};
size_t insert_double_rotations_{0};
size_t insert_collisions_{0};
size_t insert_replacements_{0};
size_t insert_traversals_{0};
size_t inspected_rotations_{0};
size_t erase_ops_{0};
size_t erase_demotes_{0};
size_t erase_rotations_{0};
size_t erase_double_rotations_{0};
};
static void ResetObserverOpCounts() { op_counts_.reset(); }
static void AccumulateObserverOpCounts(OpCounts& target) { op_counts_.accumulate(target); }
template <typename Iter>
static void RecordInsert(Iter node) {
++op_counts_.insert_ops_;
// Set the subtree min/max values to the node's key, as it is a leaf when
// first inserted, before rebalancing.
node->min_subtree_key_ = node->max_subtree_key_ = node->key_;
}
template <typename T, typename Iter>
static void RecordInsertCollision(T* node, Iter collision) {
++op_counts_.insert_collisions_;
// A collision doesn't affect the subtree min/max values of any ancestor
// during traversal.
}
template <typename Iter, typename T>
static void RecordInsertReplace(Iter node, T* replacement) {
++op_counts_.insert_replacements_;
// Copy the subtree min/max values to the replacement node.
replacement->min_subtree_key_ = node->min_subtree_key_;
replacement->max_subtree_key_ = node->max_subtree_key_;
}
template <typename T, typename Iter>
static void RecordInsertTraverse(T* node, Iter ancestor) {
++op_counts_.insert_traversals_;
// Update each ancestor's subtree min/max values as the insertion traverses
// them to find the insertion point of the new node.
ancestor->min_subtree_key_ = std::min(ancestor->min_subtree_key_, node->key_);
ancestor->max_subtree_key_ = std::max(ancestor->max_subtree_key_, node->key_);
}
static void RecordInsertPromote() { ++op_counts_.insert_promotes_; }
static void RecordInsertRotation() { ++op_counts_.insert_rotations_; }
static void RecordInsertDoubleRotation() { ++op_counts_.insert_double_rotations_; }
template <typename Iter>
static void RecordRotation(Iter pivot, Iter lr_child, Iter rl_child, Iter parent, Iter sibling) {
++op_counts_.inspected_rotations_;
// Update the subtree min/max values given the nodes that are about to be
// rotated.
// The overall subtree maintains the same max/min. The pivot replaces the
// parent at the head of the subtree.
pivot->min_subtree_key_ = parent->min_subtree_key_;
pivot->max_subtree_key_ = parent->max_subtree_key_;
// Compute the new subtree min/max of the original parent, which may now
// include a node adopted from the pivot.
parent->min_subtree_key_ = parent->key_;
parent->max_subtree_key_ = parent->key_;
if (sibling) {
parent->min_subtree_key_ = std::min(parent->min_subtree_key_, sibling->min_subtree_key_);
parent->max_subtree_key_ = std::max(parent->max_subtree_key_, sibling->max_subtree_key_);
}
if (lr_child) {
parent->min_subtree_key_ = std::min(parent->min_subtree_key_, lr_child->min_subtree_key_);
parent->max_subtree_key_ = std::max(parent->max_subtree_key_, lr_child->max_subtree_key_);
}
}
template <typename T, typename Iter>
static void RecordErase(T* node, Iter invalidated) {
++op_counts_.erase_ops_;
// Erasing a node may invalidate each ancestor's subtree min/max along the
// path to the root: re-compute the min/max values for each ancestor.
// Note that this process could be terminated early when updating an
// ancestor has no effect, but this optimization is not necessary to
// demonstrate correctness.
Iter current = invalidated;
while (current) {
current->min_subtree_key_ = current->key_;
current->max_subtree_key_ = current->key_;
if (Iter left = current.left(); left) {
current->min_subtree_key_ = std::min(current->min_subtree_key_, left->min_subtree_key_);
current->max_subtree_key_ = std::max(current->max_subtree_key_, left->max_subtree_key_);
}
if (Iter right = current.right(); right) {
current->min_subtree_key_ = std::min(current->min_subtree_key_, right->min_subtree_key_);
current->max_subtree_key_ = std::max(current->max_subtree_key_, right->max_subtree_key_);
}
current = current.parent();
}
}
static void RecordEraseDemote() { ++op_counts_.erase_demotes_; }
static void RecordEraseRotation() { ++op_counts_.erase_rotations_; }
static void RecordEraseDoubleRotation() { ++op_counts_.erase_double_rotations_; }
template <typename TreeType>
static void VerifyRankRule(const TreeType& tree, typename TreeType::RawPtrType node) {
using NodeTraits = typename TreeType::NodeTraits;
// Check the rank rule. The rules for a WAVL tree are...
// 1) All rank differences are either 1 or 2
// 2) All leaf nodes have rank 0 (by implication, all rank
// differences are non-negative)
const auto& ns = NodeTraits::node_state(*node);
ASSERT_LE(0, ns.rank_, "All ranks must be non-negative.");
if (!valid_sentinel_ptr(ns.left_) && !valid_sentinel_ptr(ns.right_)) {
ASSERT_EQ(0, ns.rank_, "Leaf nodes must have rank 0!");
} else {
if (valid_sentinel_ptr(ns.left_)) {
auto& left_ns = NodeTraits::node_state(*ns.left_);
auto delta = ns.rank_ - left_ns.rank_;
ASSERT_LE(1, delta, "Left hand rank difference not on range [1, 2]");
ASSERT_GE(2, delta, "Left hand rank difference not on range [1, 2]");
}
if (valid_sentinel_ptr(ns.right_)) {
auto& right_ns = NodeTraits::node_state(*ns.right_);
auto delta = ns.rank_ - right_ns.rank_;
ASSERT_LE(1, delta, "Right hand rank difference not on range [1, 2]");
ASSERT_GE(2, delta, "Right hand rank difference not on range [1, 2]");
}
}
}
template <typename TreeType>
static void VerifyBalance(const TreeType& tree, uint64_t depth) {
// Compute the maximum expected depth.
uint64_t max_depth = 0;
if (tree.size()) {
// If we have performed erase operations, the max depth should be
// rounddown(2 * log_2(N)) + 1.
//
// If we have not performed any erases, then the max depth should be
// rounddown(log_phi(N)) + 1. We know that...
//
// phi = (1 + sqrt(5)) / 2
// log_phi(N) = log_2(N) / log_2(phi)
//
// Start by computing log_2(N), then scale by either 2.0, or
// (1/log_2(phi)).
constexpr double one_over_log2_phi = 1.4404200904125563642566021371749229729175567626953125;
double log2N = log2(static_cast<double>(tree.size()));
double scale = op_counts_.erase_ops_ ? 2.0 : one_over_log2_phi;
max_depth = static_cast<uint64_t>(log2N * scale) + 1;
}
const size_t total_insert_rotations =
op_counts_.insert_rotations_ + op_counts_.insert_double_rotations_;
EXPECT_LE(op_counts_.insert_promotes_,
(3 * op_counts_.insert_ops_) + (2 * op_counts_.erase_ops_),
"#insert promotes must be <= (3 * #inserts) + (2 * #erases)");
EXPECT_LE(total_insert_rotations, op_counts_.insert_ops_,
"#insert_rotations must be <= #inserts");
const size_t total_erase_rotations =
op_counts_.erase_rotations_ + op_counts_.erase_double_rotations_;
EXPECT_LE(op_counts_.erase_demotes_, op_counts_.erase_ops_,
"#erase demotes must be <= #erases");
EXPECT_LE(total_erase_rotations, op_counts_.erase_ops_, "#erase_rotations must be <= #erases");
const size_t total_inspected_rotations =
op_counts_.insert_rotations_ + op_counts_.erase_rotations_ +
2 * op_counts_.insert_double_rotations_ + 2 * op_counts_.erase_double_rotations_;
EXPECT_EQ(total_inspected_rotations, op_counts_.inspected_rotations_,
"#inspected rotations must be == #rotations");
EXPECT_GE(max_depth, depth);
}
private:
static OpCounts op_counts_;
};
// Static storage for the observer.
WAVLBalanceTestObserver::OpCounts WAVLBalanceTestObserver::op_counts_;
// Test objects during the balance test will be allocated as a block all at once
// and cleaned up at the end of the test. Our test containers, however, are
// containers of unique pointers to objects with a no-op delete. This allows
// the containers to go out of scope with elements still in them (in case of a
// REQUIRE failure) without triggering the container assert for destroying a
// container of unmanaged pointer with elements still in it.
class BalanceTestObj;
using BalanceTestKeyType = uint64_t;
using BalanceTestObjPtr = std::unique_ptr<BalanceTestObj>;
using BalanceTestTree =
WAVLTree<BalanceTestKeyType, BalanceTestObjPtr,
DefaultKeyedObjectTraits<BalanceTestKeyType, BalanceTestObj>, DefaultObjectTag,
DefaultWAVLTreeTraits<BalanceTestObjPtr, DefaultObjectTag>, WAVLBalanceTestObserver>;
class BalanceTestObj {
public:
void Init(BalanceTestKeyType val) {
key_ = val;
erase_deck_ptr_ = this;
}
BalanceTestKeyType GetKey() const { return key_; }
BalanceTestKeyType GetMinSubtreeKey() const { return min_subtree_key_; }
BalanceTestKeyType GetMaxSubtreeKey() const { return max_subtree_key_; }
BalanceTestObj* EraseDeckPtr() const { return erase_deck_ptr_; }
void SwapEraseDeckPtr(BalanceTestObj& other) {
BalanceTestObj* tmp = erase_deck_ptr_;
erase_deck_ptr_ = other.erase_deck_ptr_;
other.erase_deck_ptr_ = tmp;
}
bool InContainer() const { return wavl_node_state_.InContainer(); }
private:
friend DefaultWAVLTreeTraits<BalanceTestObjPtr, DefaultObjectTag>;
friend WAVLBalanceTestObserver;
static void operator delete(void* ptr) {
// Deliberate no-op
}
friend class std::default_delete<BalanceTestObj>;
BalanceTestKeyType key_;
BalanceTestKeyType min_subtree_key_;
BalanceTestKeyType max_subtree_key_;
BalanceTestObj* erase_deck_ptr_;
WAVLTreeNodeState<BalanceTestObjPtr, NodeOptions::None, int32_t> wavl_node_state_;
};
// Only enable heavy weight testing when asked to do so.
#if FBL_TEST_ENABLE_WAVL_TREE_BALANCE_TEST
static constexpr size_t kBalanceTestSize = 2048;
#else
static constexpr size_t kBalanceTestSize = 32;
#endif
static void DoBalanceTestInsert(BalanceTestTree& tree, BalanceTestObj* ptr) {
// The selected object should not be in the tree.
ASSERT_NOT_NULL(ptr);
ASSERT_FALSE(ptr->InContainer());
// Put the object into the tree. Assert that it succeeds, then
// sanity check the tree.
ASSERT_TRUE(tree.insert_or_find(BalanceTestObjPtr(ptr)));
ASSERT_NO_FAILURES(WAVLTreeChecker::SanityCheck(tree));
}
static void DoBalanceTestCollide(BalanceTestTree& tree, BalanceTestObj* ptr) {
// The selected object should not be in the tree.
ASSERT_NOT_NULL(ptr);
ASSERT_FALSE(ptr->InContainer());
// Put the object into the tree. Assert that it fails, then
// sanity check the tree.
ASSERT_FALSE(tree.insert_or_find(BalanceTestObjPtr(ptr)));
ASSERT_NO_FAILURES(WAVLTreeChecker::SanityCheck(tree));
}
static void DoBalanceTestReplace(BalanceTestTree& tree, BalanceTestObj* ptr) {
// The selected object should not be in the tree.
ASSERT_NOT_NULL(ptr);
ASSERT_FALSE(ptr->InContainer());
// Put the object into the tree. Assert that it fails, then
// sanity check the tree.
ASSERT_NOT_NULL(tree.insert_or_replace(BalanceTestObjPtr(ptr)));
ASSERT_NO_FAILURES(WAVLTreeChecker::SanityCheck(tree));
}
static void DoBalanceTestErase(BalanceTestTree& tree, BalanceTestObj* ptr) {
// The selected object should still be in the tree.
ASSERT_NOT_NULL(ptr);
ASSERT_TRUE(ptr->InContainer());
// Erase should find the object and transfer its pointer back to us.
// The object should no longer be in the tree.
BalanceTestObjPtr erased = tree.erase(ptr->GetKey());
ASSERT_EQ(ptr, erased.get());
ASSERT_FALSE(ptr->InContainer());
// Run a full sanity check on the tree. Its depth should be
// consistent with a tree which has seen both inserts and erases.
ASSERT_NO_FAILURES(WAVLTreeChecker::SanityCheck(tree));
}
static void ShuffleEraseDeck(const std::unique_ptr<BalanceTestObj[]>& objects,
Lfsr<BalanceTestKeyType>& rng) {
// Note: shuffle algorithm is a Fisher-Yates (aka Knuth) shuffle.
static_assert(kBalanceTestSize > 0, "Test size must be positive!");
for (size_t i = kBalanceTestSize - 1; i > 1; --i) {
size_t ndx = static_cast<size_t>(rng.GetNext()) % i;
if (ndx != i)
objects[i].SwapEraseDeckPtr(objects[ndx]);
}
}
// Performs an efficient check that the augmented binary tree invariants hold.
// The augmented binary tree maintains the min/max keys of every subtree. The
// min/max values in the root node should always match the keys of the leftmost/
// rightmost nodes, respectively.
static void CheckAugmentedInvariants(const BalanceTestTree& tree) {
if (const auto root = tree.croot(); root) {
EXPECT_EQ(root->GetMinSubtreeKey(), tree.front().GetKey());
EXPECT_EQ(root->GetMaxSubtreeKey(), tree.back().GetKey());
}
}
// Checks that left, right, and parent iterator operations reach the expected
// nodes.
static void CheckIterators(const BalanceTestTree& tree) {
// Descend the left and right paths from the root. These should reach the
// leftmost and rightmost nodes in no more iterations than there are nodes,
// in the worst case.
const auto left_most = tree.cbegin();
const auto right_most = --tree.cend();
const auto root = tree.croot();
const auto size = tree.size();
auto left_cursor = root;
auto right_cursor = root;
size_t i;
for (i = 0; (left_cursor != left_most || right_cursor != right_most) && i < size; ++i) {
ASSERT_TRUE(left_cursor);
if (left_cursor == left_most) {
EXPECT_FALSE(left_cursor.left());
} else {
left_cursor = left_cursor.left();
}
ASSERT_TRUE(right_cursor);
if (right_cursor == right_most) {
EXPECT_FALSE(right_cursor.right());
} else {
right_cursor = right_cursor.right();
}
}
EXPECT_EQ(left_cursor, left_most);
EXPECT_EQ(right_cursor, right_most);
// Ascend the left and right paths to the root. These should reach the root
// node in no more iterations than the descent above.
const size_t limit = i;
left_cursor = left_most;
right_cursor = right_most;
for (i = 0; (left_cursor != root || right_cursor != root) && i < limit; ++i) {
ASSERT_TRUE(left_cursor);
if (left_cursor == root) {
EXPECT_FALSE(left_cursor.parent());
} else {
left_cursor = left_cursor.parent();
}
ASSERT_TRUE(right_cursor);
if (right_cursor == root) {
EXPECT_FALSE(right_cursor.parent());
} else {
right_cursor = right_cursor.parent();
}
}
EXPECT_EQ(left_cursor, root);
EXPECT_EQ(right_cursor, root);
}
// clang-format off
//////////////////////////////////////////
// General container specific tests.
//////////////////////////////////////////
RUN_ZXTEST(WavlTreeTest, UMTE, Clear)
RUN_ZXTEST(WavlTreeTest, UPDDTE, Clear)
RUN_ZXTEST(WavlTreeTest, UPCDTE, Clear)
RUN_ZXTEST(WavlTreeTest, RPTE, Clear)
#if TEST_WILL_NOT_COMPILE || 0
// Won't compile because node lacks AllowClearUnsafe option.
RUN_ZXTEST(WavlTreeTest, UMTE, ClearUnsafe)
RUN_ZXTEST(WavlTreeTest, UPDDTE, ClearUnsafe)
RUN_ZXTEST(WavlTreeTest, UPCDTE, ClearUnsafe)
RUN_ZXTEST(WavlTreeTest, RPTE, ClearUnsafe)
#endif
#if TEST_WILL_NOT_COMPILE || 0
// Won't compile because pointer type is managed.
RUN_ZXTEST(WavlTreeTest, CU_UPDDTE, ClearUnsafe)
#endif
RUN_ZXTEST(WavlTreeTest, CU_UMTE, ClearUnsafe)
RUN_ZXTEST(WavlTreeTest, UMTE, IsEmpty)
RUN_ZXTEST(WavlTreeTest, UPDDTE, IsEmpty)
RUN_ZXTEST(WavlTreeTest, UPCDTE, IsEmpty)
RUN_ZXTEST(WavlTreeTest, RPTE, IsEmpty)
RUN_ZXTEST(WavlTreeTest, UMTE, Iterate)
RUN_ZXTEST(WavlTreeTest, UPDDTE, Iterate)
RUN_ZXTEST(WavlTreeTest, UPCDTE, Iterate)
RUN_ZXTEST(WavlTreeTest, RPTE, Iterate)
RUN_ZXTEST(WavlTreeTest, UMTE, IterErase)
RUN_ZXTEST(WavlTreeTest, UPDDTE, IterErase)
RUN_ZXTEST(WavlTreeTest, UPCDTE, IterErase)
RUN_ZXTEST(WavlTreeTest, RPTE, IterErase)
RUN_ZXTEST(WavlTreeTest, UMTE, DirectErase)
RUN_ZXTEST(WavlTreeTest, UPDDTE, DirectErase)
RUN_ZXTEST(WavlTreeTest, UPCDTE, DirectErase)
RUN_ZXTEST(WavlTreeTest, RPTE, DirectErase)
RUN_ZXTEST(WavlTreeTest, UMTE, MakeIterator)
RUN_ZXTEST(WavlTreeTest, UPDDTE, MakeIterator)
RUN_ZXTEST(WavlTreeTest, UPCDTE, MakeIterator)
RUN_ZXTEST(WavlTreeTest, RPTE, MakeIterator)
RUN_ZXTEST(WavlTreeTest, UMTE, ReverseIterErase)
RUN_ZXTEST(WavlTreeTest, UPDDTE, ReverseIterErase)
RUN_ZXTEST(WavlTreeTest, UPCDTE, ReverseIterErase)
RUN_ZXTEST(WavlTreeTest, RPTE, ReverseIterErase)
RUN_ZXTEST(WavlTreeTest, UMTE, ReverseIterate)
RUN_ZXTEST(WavlTreeTest, UPDDTE, ReverseIterate)
RUN_ZXTEST(WavlTreeTest, UPCDTE, ReverseIterate)
RUN_ZXTEST(WavlTreeTest, RPTE, ReverseIterate)
RUN_ZXTEST(WavlTreeTest, UMTE, Swap)
RUN_ZXTEST(WavlTreeTest, UPDDTE, Swap)
RUN_ZXTEST(WavlTreeTest, UPCDTE, Swap)
RUN_ZXTEST(WavlTreeTest, RPTE, Swap)
RUN_ZXTEST(WavlTreeTest, UMTE, RvalueOps)
RUN_ZXTEST(WavlTreeTest, UPDDTE, RvalueOps)
RUN_ZXTEST(WavlTreeTest, UPCDTE, RvalueOps)
RUN_ZXTEST(WavlTreeTest, RPTE, RvalueOps)
RUN_ZXTEST(WavlTreeTest, UPDDTE, Scope)
RUN_ZXTEST(WavlTreeTest, UPCDTE, Scope)
RUN_ZXTEST(WavlTreeTest, RPTE, Scope)
RUN_ZXTEST(WavlTreeTest, UMTE, TwoContainer)
#if TEST_WILL_NOT_COMPILE || 0
RUN_ZXTEST(WavlTreeTest, UPDDTE, TwoContainer)
RUN_ZXTEST(WavlTreeTest, UPCDTE, TwoContainer)
#endif
RUN_ZXTEST(WavlTreeTest, RPTE, TwoContainer)
RUN_ZXTEST(WavlTreeTest, UMTE, ThreeContainerHelper)
#if TEST_WILL_NOT_COMPILE || 0
RUN_ZXTEST(WavlTreeTest, UPDDTE, ThreeContainerHelper)
RUN_ZXTEST(WavlTreeTest, UPCDTE, ThreeContainerHelper)
#endif
RUN_ZXTEST(WavlTreeTest, RPTE, ThreeContainerHelper)
RUN_ZXTEST(WavlTreeTest, UMTE, IterCopyPointer)
#if TEST_WILL_NOT_COMPILE || 0
RUN_ZXTEST(WavlTreeTest, UPDDTE, IterCopyPointer)
RUN_ZXTEST(WavlTreeTest, UPCDTE, IterCopyPointer)
#endif
RUN_ZXTEST(WavlTreeTest, RPTE, IterCopyPointer)
RUN_ZXTEST(WavlTreeTest, UMTE, EraseIf)
RUN_ZXTEST(WavlTreeTest, UPDDTE, EraseIf)
RUN_ZXTEST(WavlTreeTest, UPCDTE, EraseIf)
RUN_ZXTEST(WavlTreeTest, RPTE, EraseIf)
RUN_ZXTEST(WavlTreeTest, UMTE, FindIf)
RUN_ZXTEST(WavlTreeTest, UPDDTE, FindIf)
RUN_ZXTEST(WavlTreeTest, UPCDTE, FindIf)
RUN_ZXTEST(WavlTreeTest, RPTE, FindIf)
//////////////////////////////////////////
// Associative container specific tests.
//////////////////////////////////////////
RUN_ZXTEST(WavlTreeTest, UMTE, InsertByKey)
RUN_ZXTEST(WavlTreeTest, UPDDTE, InsertByKey)
RUN_ZXTEST(WavlTreeTest, UPCDTE, InsertByKey)
RUN_ZXTEST(WavlTreeTest, RPTE, InsertByKey)
RUN_ZXTEST(WavlTreeTest, UMTE, FindByKey)
RUN_ZXTEST(WavlTreeTest, UPDDTE, FindByKey)
RUN_ZXTEST(WavlTreeTest, UPCDTE, FindByKey)
RUN_ZXTEST(WavlTreeTest, RPTE, FindByKey)
RUN_ZXTEST(WavlTreeTest, UMTE, EraseByKey)
RUN_ZXTEST(WavlTreeTest, UPDDTE, EraseByKey)
RUN_ZXTEST(WavlTreeTest, UPCDTE, EraseByKey)
RUN_ZXTEST(WavlTreeTest, RPTE, EraseByKey)
RUN_ZXTEST(WavlTreeTest, UMTE, InsertOrFind)
RUN_ZXTEST(WavlTreeTest, UPDDTE, InsertOrFind)
RUN_ZXTEST(WavlTreeTest, UPCDTE, InsertOrFind)
RUN_ZXTEST(WavlTreeTest, RPTE, InsertOrFind)
RUN_ZXTEST(WavlTreeTest, UMTE, InsertOrReplace)
RUN_ZXTEST(WavlTreeTest, UPDDTE, InsertOrReplace)
RUN_ZXTEST(WavlTreeTest, UPCDTE, InsertOrReplace)
RUN_ZXTEST(WavlTreeTest, RPTE, InsertOrReplace)
////////////////////////////////////////////////
// OrderedAssociative container specific tests.
////////////////////////////////////////////////
RUN_ZXTEST(WavlTreeTest, UMTE, OrderedIter)
RUN_ZXTEST(WavlTreeTest, UPDDTE, OrderedIter)
RUN_ZXTEST(WavlTreeTest, UPCDTE, OrderedIter)
RUN_ZXTEST(WavlTreeTest, RPTE, OrderedIter)
RUN_ZXTEST(WavlTreeTest, UMTE, OrderedReverseIter)
RUN_ZXTEST(WavlTreeTest, UPDDTE, OrderedReverseIter)
RUN_ZXTEST(WavlTreeTest, UPCDTE, OrderedReverseIter)
RUN_ZXTEST(WavlTreeTest, RPTE, OrderedReverseIter)
RUN_ZXTEST(WavlTreeTest, UMTE, UpperBound)
RUN_ZXTEST(WavlTreeTest, UPDDTE, UpperBound)
RUN_ZXTEST(WavlTreeTest, UPCDTE, UpperBound)
RUN_ZXTEST(WavlTreeTest, RPTE, UpperBound)
RUN_ZXTEST(WavlTreeTest, UMTE, LowerBound)
RUN_ZXTEST(WavlTreeTest, UPDDTE, LowerBound)
RUN_ZXTEST(WavlTreeTest, UPCDTE, LowerBound)
RUN_ZXTEST(WavlTreeTest, RPTE, LowerBound)
// Negative compilation test which make sure that we don't accidentally
// mismatch pointer types between the node and the container.
TEST(WavlTreeTest, MismatchedPointerType) {
struct Obj {
fbl::WAVLTreeNodeState<Obj*> wavl_node_state_;
uintptr_t GetKey() const { return reinterpret_cast<uintptr_t>(this); }
};
#if TEST_WILL_NOT_COMPILE || 0
[[maybe_unused]] fbl::WAVLTree<uintptr_t, std::unique_ptr<Obj>> tree;
#endif
}
// Negative compilation test which make sure that we cannot try to use a node
// flagged with AllowRemoveFromContainer with a WAVLTree. Note: While it would
// be a rather complicated refactor, we _could_ allow this, but only if we first
// introduce a version of the WAVLTree which does not track the size of the
// container.
TEST(WavlTreeTest, NoRemoveFromContainer) {
struct Obj : public WAVLTreeContainable<Obj*, NodeOptions::AllowRemoveFromContainer> {
uintptr_t GetKey() const { return reinterpret_cast<uintptr_t>(this); }
};
#if TEST_WILL_NOT_COMPILE || 0
[[maybe_unused]] fbl::WAVLTree<uintptr_t, Obj*> tree;
#endif
}
TEST(WavlTreeTest, BalanceAndInvariants) {
WAVLBalanceTestObserver::OpCounts op_counts;
// Declare these in a specific order (object pointer first) so that the tree
// has a chance to clean up before the memory backing the objects gets
// cleaned up.
std::unique_ptr<BalanceTestObj[]> objects;
std::unique_ptr<BalanceTestObj[]> replacements;
BalanceTestTree tree;
// We will run this test 3 times with 3 different (constant) seeds. During
// the first run, we will insert all of the elements with ascending key
// order. During the second run, we will insert all of the keys with
// descending key order. During the final run, we will insert all of the
// keys in a random order.
Lfsr<BalanceTestKeyType> rng;
static const BalanceTestKeyType seeds[] = {0xe87e1062fc1f4f80u, 0x03d6bffb124b4918u,
0x8f7d83e8d10b4765u};
// The replacements set is a fraction of the size of the object set.
const size_t kReplacementCount = kBalanceTestSize / 8;
static_assert(kReplacementCount != 0);
// Allocate the objects we will use for the test.
{
AllocChecker ac;
objects.reset(new (&ac) BalanceTestObj[kBalanceTestSize]);
ASSERT_TRUE(ac.check(), "Failed to allocate test objects!");
replacements.reset(new (&ac) BalanceTestObj[kReplacementCount]);
ASSERT_TRUE(ac.check(), "Failed to allocate replacement objects!");
}
for (size_t seed_ndx = 0; seed_ndx < std::size(seeds); ++seed_ndx) {
auto seed = seeds[seed_ndx];
// Seed the RNG and reset the observer stats.
rng.SetCore(seed);
WAVLBalanceTestObserver::ResetObserverOpCounts();
// Initialize each object with the proper key for this run. This places
// the object in the erase deck sequence at the same time.
switch (seed_ndx) {
case 0u:
for (size_t i = 0; i < kBalanceTestSize; ++i) {
objects[i].Init(i);
if (i < kReplacementCount) {
replacements[i].Init(i);
}
}
break;
case 1u:
for (size_t i = 0; i < kBalanceTestSize; ++i) {
objects[i].Init(kBalanceTestSize - i);
if (i < kReplacementCount) {
replacements[i].Init(kBalanceTestSize - i);
}
}
break;
default:
for (size_t i = 0; i < kBalanceTestSize; ++i) {
objects[i].Init(rng.GetNext());
if (i < kReplacementCount) {
replacements[i].Init(objects[i].GetKey());
}
}
break;
}
// Place each object into the tree, then perform a full sanity check on
// the tree. If anything goes wrong, just abort the test. If we keep
// going, we are just going to get an unmanageable amt of errors.
for (size_t i = 0; i < kBalanceTestSize; ++i) {
ASSERT_NO_FAILURES(CheckAugmentedInvariants(tree));
ASSERT_NO_FAILURES(DoBalanceTestInsert(tree, &objects[i]));
ASSERT_NO_FAILURES(CheckAugmentedInvariants(tree));
}
ASSERT_NO_FAILURES(CheckIterators(tree));
// Collide the replacement set with the tree.
for (size_t i = 0; i < kReplacementCount; ++i) {
ASSERT_NO_FAILURES(CheckAugmentedInvariants(tree));
ASSERT_NO_FAILURES(DoBalanceTestCollide(tree, &replacements[i]));
ASSERT_NO_FAILURES(CheckAugmentedInvariants(tree));
}
// Replace nodes in the tree with the replacement set.
for (size_t i = 0; i < kReplacementCount; ++i) {
ASSERT_NO_FAILURES(CheckAugmentedInvariants(tree));
ASSERT_NO_FAILURES(DoBalanceTestReplace(tree, &replacements[i]));
ASSERT_NO_FAILURES(CheckAugmentedInvariants(tree));
}
ASSERT_NO_FAILURES(CheckIterators(tree));
// Replace the original nodes in the tree.
for (size_t i = 0; i < kReplacementCount; ++i) {
ASSERT_NO_FAILURES(CheckAugmentedInvariants(tree));
ASSERT_NO_FAILURES(DoBalanceTestReplace(tree, &objects[i]));
ASSERT_NO_FAILURES(CheckAugmentedInvariants(tree));
}
ASSERT_NO_FAILURES(CheckIterators(tree));
// Shuffle the erase deck.
ShuffleEraseDeck(objects, rng);
// Erase half of the elements in the tree.
for (size_t i = 0; i < (kBalanceTestSize >> 1); ++i) {
ASSERT_NO_FAILURES(CheckAugmentedInvariants(tree));
ASSERT_NO_FAILURES(DoBalanceTestErase(tree, objects[i].EraseDeckPtr()));
ASSERT_NO_FAILURES(CheckAugmentedInvariants(tree));
}
ASSERT_NO_FAILURES(CheckIterators(tree));
// Put the elements back so that we have inserted some elements into a
// non-empty tree which has seen erase operations.
for (size_t i = 0; i < (kBalanceTestSize >> 1); ++i) {
ASSERT_NO_FAILURES(CheckAugmentedInvariants(tree));
ASSERT_NO_FAILURES(DoBalanceTestInsert(tree, objects[i].EraseDeckPtr()));
ASSERT_NO_FAILURES(CheckAugmentedInvariants(tree));
}
ASSERT_NO_FAILURES(CheckIterators(tree));
// Shuffle the erase deck again.
ShuffleEraseDeck(objects, rng);
// Now erase every element from the tree.
for (size_t i = 0; i < kBalanceTestSize; ++i) {
ASSERT_NO_FAILURES(CheckAugmentedInvariants(tree));
ASSERT_NO_FAILURES(DoBalanceTestErase(tree, objects[i].EraseDeckPtr()));
ASSERT_NO_FAILURES(CheckAugmentedInvariants(tree));
}
ASSERT_NO_FAILURES(CheckIterators(tree));
ASSERT_EQ(0u, tree.size());
WAVLBalanceTestObserver::AccumulateObserverOpCounts(op_counts);
}
// Finally, make sure that we have exercised all of the different re-balance
// cases.
EXPECT_LT(0u, op_counts.insert_ops_, "Insufficient test coverage!");
EXPECT_LT(0u, op_counts.insert_promotes_, "Insufficient test coverage!");
EXPECT_LT(0u, op_counts.insert_rotations_, "Insufficient test coverage!");
EXPECT_LT(0u, op_counts.insert_double_rotations_, "Insufficient test coverage!");
EXPECT_LT(0u, op_counts.insert_collisions_, "Insufficient test coverage!");
EXPECT_LT(0u, op_counts.insert_replacements_, "Insufficient test coverage!");
EXPECT_LT(0u, op_counts.insert_traversals_, "Insufficient test coverage!");
EXPECT_LT(0u, op_counts.inspected_rotations_, "Insufficient test coverage!");
EXPECT_LT(0u, op_counts.erase_ops_, "Insufficient test coverage!");
EXPECT_LT(0u, op_counts.erase_demotes_, "Insufficient test coverage!");
EXPECT_LT(0u, op_counts.erase_rotations_, "Insufficient test coverage!");
EXPECT_LT(0u, op_counts.erase_double_rotations_, "Insufficient test coverage!");
}
} // namespace intrusive_containers
} // namespace tests
} // namespace fbl