blob: 538c05597b2c3041fa1712e784d2bd356168fdb3 [file] [log] [blame]
// Copyright 2021 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 <algorithm>
#include <array>
#include <limits>
#include <random>
#include <fbl/intrusive_wavl_tree.h>
#include <fbl/wavl_tree_best_node_observer.h>
#include <zxtest/zxtest.h>
namespace {
// The definition of the node we will use during testing.
struct TestNode {
static constexpr uint32_t kInvalidAugmentedVal = std::numeric_limits<uint32_t>::max();
TestNode() = default;
~TestNode() { ZX_ASSERT(subtree_best == kInvalidAugmentedVal); }
uint32_t key{0};
uint32_t augmented_val{0};
uint32_t subtree_best{kInvalidAugmentedVal};
fbl::WAVLTreeNodeState<TestNode*> tree_node;
};
// Traits used to locate the WAVL tree node state in TestNode, as well as to
// establish the sorting invariant.
struct TestNodeTraits {
static uint32_t GetKey(const TestNode& test_node) { return test_node.key; }
static bool LessThan(uint32_t a, uint32_t b) { return a < b; }
static bool EqualTo(uint32_t a, uint32_t b) { return a == b; }
static auto& node_state(TestNode& test_node) { return test_node.tree_node; }
};
// A base definition of the "best node" traits which defines "best" as the node
// with the minimum |augmented_val|.
struct BestNodeTraits {
static uint32_t GetValue(const TestNode& node) { return node.augmented_val; }
static uint32_t GetSubtreeBest(const TestNode& node) { return node.subtree_best; }
static bool Compare(uint32_t a, uint32_t b) { return a < b; }
static void AssignBest(TestNode& node, uint32_t val) { node.subtree_best = val; }
static void ResetBest(TestNode& target) { target.subtree_best = TestNode::kInvalidAugmentedVal; }
};
using TestTree = fbl::WAVLTree<uint32_t, TestNode*, TestNodeTraits, fbl::DefaultObjectTag,
TestNodeTraits, fbl::WAVLTreeBestNodeObserver<BestNodeTraits>>;
template <typename NodeCollection>
void ValidateTree(const TestTree& tree, const NodeCollection& nodes,
const TestNode* extra_node = nullptr) {
std::optional<uint32_t> best;
auto UpdateBest = [&best](const TestNode& node) {
if (node.tree_node.InContainer()) {
ASSERT_NE(TestNode::kInvalidAugmentedVal, node.subtree_best);
best = best.has_value() ? std::min(best.value(), node.augmented_val) : node.augmented_val;
} else {
ASSERT_EQ(TestNode::kInvalidAugmentedVal, node.subtree_best);
}
};
for (const auto& node : nodes) {
ASSERT_NO_FATAL_FAILURE(UpdateBest(node));
}
if (extra_node) {
ASSERT_NO_FATAL_FAILURE(UpdateBest(*extra_node));
}
if (best.has_value()) {
ASSERT_FALSE(tree.is_empty());
ASSERT_EQ(best.value(), tree.root()->subtree_best);
}
for (auto iter = tree.begin(); iter != tree.end(); ++iter) {
uint32_t expected_best = iter->augmented_val;
if (iter.left()) {
expected_best = std::min(expected_best, iter.left()->subtree_best);
}
if (iter.right()) {
expected_best = std::min(expected_best, iter.right()->subtree_best);
}
ASSERT_EQ(expected_best, iter->subtree_best);
}
}
TEST(WAVLTreeBestNodeObserverTests, BestInvariaintMaintained) {
struct TestConfig {
const uint64_t seed;
const bool use_clear;
};
// Run the test a few different times with different random seeds, and at
// least once where we clear the entire tree using |clear|, instead of
// removing the elements one at a time.
constexpr std::array kConfigs = {
TestConfig{0x8a344d45e080e324, false},
TestConfig{0xadbff1880c9ce89b, false},
TestConfig{0x9a068f41344eec43, true},
};
for (const auto& cfg : kConfigs) {
constexpr uint32_t kTestCount = 256;
std::array<TestNode, kTestCount> test_nodes;
std::array<uint32_t, kTestCount> shuffle_order;
TestTree tree;
// Initialize our array of TestNodes with unique primary keys, and random
// augmented values. Also initialize the shuffle order with a set of
// ascending indices.
std::mt19937_64 rng(cfg.seed);
std::uniform_int_distribution<uint32_t> augmented_value_distribution(
1, TestNode::kInvalidAugmentedVal - 1);
for (uint32_t i = 0; i < kTestCount; ++i) {
test_nodes[i].key = i;
test_nodes[i].augmented_val = augmented_value_distribution(rng);
shuffle_order[i] = i;
}
// Shuffle the order deck and add the test nodes to the tree in the shuffled
// order, verifying the tree each time.
ASSERT_NO_FATAL_FAILURE(ValidateTree(tree, test_nodes));
std::shuffle(shuffle_order.begin(), shuffle_order.end(), rng);
for (uint32_t ndx : shuffle_order) {
tree.insert(&test_nodes[ndx]);
ASSERT_NO_FATAL_FAILURE(ValidateTree(tree, test_nodes));
}
// Create a test node which is guaranteed to collide with test_nodes[0].
// Also, give it an augmented value which is "better" then any of the values
// in the tree.
TestNode collision_node;
collision_node.key = 0;
collision_node.augmented_val = 0;
// Attempt an insert-or-find operation using the collision node. The insert
// should fail, leaving the currently computed "best" value unchanged, but
// if the Traits used included an, OnInsertCollision method, it should have
// been called.
typename TestTree::iterator already_in_tree;
ASSERT_FALSE(tree.insert_or_find(&collision_node, &already_in_tree));
ASSERT_EQ(&test_nodes[0], &*already_in_tree);
ASSERT_NO_FATAL_FAILURE(ValidateTree(tree, test_nodes, &collision_node));
// Now attempt an insert-or-replace using the collision node. test_nodes[0]
// should end up being replaced by collision_node, and
// collision_node.augmented_val should become the new best of the tree.
TestNode* replaced_node = tree.insert_or_replace(&collision_node);
ASSERT_EQ(&test_nodes[0], replaced_node);
ASSERT_NO_FATAL_FAILURE(ValidateTree(tree, test_nodes, &collision_node));
// Depending on the test configuration, either simply clear the tree, or
// shuffle the deck again and remove the nodes from the tree in the new
// random order.
if (cfg.use_clear) {
tree.clear();
ASSERT_NO_FATAL_FAILURE(ValidateTree(tree, test_nodes, &collision_node));
} else {
std::shuffle(shuffle_order.begin(), shuffle_order.end(), rng);
for (uint32_t ndx : shuffle_order) {
// Handle the fact that test_nodes[0] was replaced by collision_node
if (ndx == 0) {
tree.erase(collision_node);
} else {
tree.erase(test_nodes[ndx]);
}
ASSERT_NO_FATAL_FAILURE(ValidateTree(tree, test_nodes, &collision_node));
}
}
}
}
} // namespace