blob: 49ff2e8d9774636565ae227a21e89a2fd8946878 [file] [log] [blame]
//===-- Unittests for WeakAVL ---------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "src/__support/CPP/optional.h"
#include "src/__support/weak_avl.h"
#include "test/UnitTest/Test.h"
using Node = LIBC_NAMESPACE::WeakAVLNode<int>;
namespace {
int ternary_compare(int a, int b) { return (a > b) - (a < b); }
constexpr int TEST_SIZE = 128;
// Validate weak-AVL rank-difference invariant assuming **pure insertion only**
// (i.e. no erasure has occurred).
//
// NOTE: This validator is intentionally *not* correct after erase(), because
// weak-AVL allows transient or permanent 2-2 configurations during deletion
// fixup.
bool validate_pure_insertion(const Node *node) {
if (!node)
return true;
bool left_2 = node->has_rank_diff_2(false);
bool right_2 = node->has_rank_diff_2(true);
return (!left_2 || !right_2) && validate_pure_insertion(node->get_left()) &&
validate_pure_insertion(node->get_right());
}
// Insert according to pattern `next(i)`
using NextFn = int (*)(int);
using OptionalNodePtr = LIBC_NAMESPACE::cpp::optional<Node *>;
struct Tree {
Node *root = nullptr;
bool validate_pure_insertion() { return ::validate_pure_insertion(root); }
bool contains(int value) {
return Node::find(root, value, ternary_compare).has_value();
}
OptionalNodePtr insert(int value) {
return Node::find_or_insert(root, value, ternary_compare);
}
OptionalNodePtr find(int value) {
return Node::find(root, value, ternary_compare);
}
void erase(int value) {
if (OptionalNodePtr node = Node::find(root, value, ternary_compare))
Node::erase(root, node.value());
}
template <typename NextFn> static Tree build(NextFn next, int N) {
Tree tree;
for (int i = 0; i < N; ++i)
tree.insert(next(i));
return tree;
}
bool empty() const { return root == nullptr; }
~Tree() { Node::destroy(root); }
};
// Insertion patterns
static int seq(int i) { return i; }
static int rev(int i) {
constexpr int N = TEST_SIZE;
return N - 1 - i;
}
// Coprime stride permutation: i -> (i * X) % N
static int stride(int i, int prime = 7919) {
constexpr int N = TEST_SIZE;
return (i * prime) % N;
}
} // namespace
TEST(LlvmLibcWeakAVLTest, SimpleInsertion) {
Tree tree;
OptionalNodePtr node10 = tree.insert(10);
ASSERT_TRUE(node10.has_value());
ASSERT_TRUE(tree.insert(5).has_value());
ASSERT_TRUE(tree.validate_pure_insertion());
OptionalNodePtr node15 = tree.insert(15);
ASSERT_TRUE(node15.has_value());
ASSERT_TRUE(tree.validate_pure_insertion());
OptionalNodePtr node10_again = tree.insert(10);
ASSERT_EQ(*node10, *node10_again);
ASSERT_TRUE(tree.validate_pure_insertion());
}
TEST(LlvmLibcWeakAVLTest, SequentialInsertion) {
constexpr int N = TEST_SIZE;
Tree tree = Tree::build(seq, N);
ASSERT_TRUE(tree.validate_pure_insertion());
for (int i = 0; i < N; ++i) {
OptionalNodePtr node = tree.insert(i);
ASSERT_TRUE(node.has_value());
ASSERT_EQ(node.value()->get_data(), i);
}
ASSERT_TRUE(tree.validate_pure_insertion());
}
TEST(LlvmLibcWeakAVLTest, ReversedInsertion) {
constexpr int N = TEST_SIZE;
Tree tree = Tree::build(rev, N);
ASSERT_TRUE(tree.validate_pure_insertion());
for (int i = 0; i < N; ++i) {
OptionalNodePtr node = tree.insert(i);
ASSERT_TRUE(node.has_value());
ASSERT_EQ(node.value()->get_data(), i);
}
ASSERT_TRUE(tree.validate_pure_insertion());
}
TEST(LlvmLibcWeakAVLTest, StridedInsertion) {
constexpr int N = TEST_SIZE;
Tree tree = Tree::build([](int i) { return stride(i); }, N);
ASSERT_TRUE(tree.validate_pure_insertion());
for (int i = 0; i < N; ++i) {
OptionalNodePtr node = tree.insert(i);
ASSERT_TRUE(node.has_value());
ASSERT_EQ(node.value()->get_data(), i);
}
ASSERT_TRUE(tree.validate_pure_insertion());
}
TEST(LlvmLibcWeakAVLTest, FindExistingAndMissing) {
constexpr int N = TEST_SIZE;
Tree tree = Tree::build(seq, N);
ASSERT_TRUE(tree.validate_pure_insertion());
for (int i = 0; i < N; ++i) {
OptionalNodePtr node = tree.find(i);
ASSERT_TRUE(node.has_value());
ASSERT_EQ(node.value()->get_data(), i);
}
ASSERT_FALSE(tree.find(-1).has_value());
ASSERT_FALSE(tree.find(N).has_value());
ASSERT_FALSE(tree.find(2 * N).has_value());
}
TEST(LlvmLibcWeakAVLTest, SequentialErase) {
constexpr int N = TEST_SIZE;
Tree tree = Tree::build(seq, N);
for (int i = 0; i < N; ++i) {
ASSERT_TRUE(tree.contains(i));
tree.erase(i);
ASSERT_FALSE(tree.contains(i));
}
ASSERT_TRUE(tree.empty());
}
TEST(LlvmLibcWeakAVLTest, ReverseErase) {
constexpr int N = TEST_SIZE;
Tree tree = Tree::build(seq, N);
for (int i = N - 1; i >= 0; --i) {
ASSERT_TRUE(tree.contains(i));
tree.erase(i);
ASSERT_FALSE(tree.contains(i));
}
ASSERT_TRUE(tree.empty());
}
TEST(LlvmLibcWeakAVLTest, StridedErase) {
constexpr int N = TEST_SIZE;
Tree tree = Tree::build(seq, N);
for (int i = 0; i < N; ++i) {
int key = stride(i, 5261);
ASSERT_TRUE(tree.contains(key));
tree.erase(key);
ASSERT_FALSE(tree.contains(key));
}
ASSERT_TRUE(tree.empty());
}
TEST(LlvmLibcWeakAVLTest, EraseStructuralCases) {
Tree tree;
int keys[] = {10, 5, 15, 3, 7, 12, 18};
// rank1: 10 10
// / / \
// rank0: 10 --> 5 --> 5 15
// rank2: 10 10
// / \ / \
// rank1: 10 5 \ 5 \
// / \ --> / \ --> /\ \
// rank0: 5 15 3 15 3 7 15
// rank2: 10 10 10
// / \ / \ / \
// rank1: 5 \ --> 5 15 --> 5 15
// /\ \ /\ / /\ / \
// rank0: 3 7 15 3 7 12 3 7 12 18
for (int k : keys)
tree.insert(k);
// Erase leaf.
// rank2: 10 10
// / \ / \
// rank1: 5 15 5 15
// /\ / \ --> \ / \
// rank0: 3 7 12 18 7 12 18
tree.erase(3);
ASSERT_FALSE(tree.contains(3));
// Erase internal nodes.
// Erase leaf.
// rank2: 10 10 10
// / \ / \ / \
// rank1: 5 15 7 15 / 15
// \ / \ --> \ / \ --> / /\
// rank0: 7 12 18 5 12 18 7 12 18
tree.erase(5);
ASSERT_FALSE(tree.contains(5));
// Erase root.
// rank2: 10 12 12
// / \ / \ / \
// rank1: / 15 --> / 15 --> / 15
// / /\ / /\ / \
// rank0: 7 12 18 7 10 18 7 18
tree.erase(10);
ASSERT_FALSE(tree.contains(10));
int attempts[] = {7, 12, 15, 18};
for (int k : attempts)
ASSERT_TRUE(tree.contains(k));
}
TEST(LlvmLibcTreeWalk, InOrderTraversal) {
Tree tree = Tree::build([](int x) { return stride(x, 1007); }, TEST_SIZE);
int data[TEST_SIZE];
int counter = 0;
Node::walk(tree.root, [&](Node *node, Node::WalkType type) {
if (type == Node::WalkType::InOrder || type == Node::WalkType::Leaf)
data[counter++] = node->get_data();
});
for (int i = 0; i < TEST_SIZE; ++i)
ASSERT_EQ(data[i], i);
}