blob: 8c2eb355cc63f914b0a1cc16fa1a60ff04857ae9 [file] [edit]
//===----------- ImmutableSetTest.cpp - ImmutableSet unit tests ------------===//
//
// 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 "llvm/ADT/ImmutableSet.h"
#include "gtest/gtest.h"
#include <algorithm>
#include <numeric>
#include <random>
#include <set>
#include <vector>
using namespace llvm;
// Non-canonicalizing set, used by tests that specifically exercise the
// structural (isEqual) / iterator paths on distinct, non-deduplicated trees.
template <typename T>
using NCSet = ImmutableSet<T, ImutContainerInfo<T>, /*Canonicalize=*/false>;
namespace {
class ImmutableSetTest : public testing::Test {
protected:
// for callback tests
static char buffer[10];
struct MyIter {
int counter;
char *ptr;
MyIter() : counter(0), ptr(buffer) {
for (unsigned i=0; i<sizeof(buffer);++i) buffer[i]='\0';
}
void operator()(char c) {
*ptr++ = c;
++counter;
}
};
};
char ImmutableSetTest::buffer[10];
TEST_F(ImmutableSetTest, EmptyIntSetTest) {
ImmutableSet<int>::Factory f;
EXPECT_TRUE(f.getEmptySet() == f.getEmptySet());
EXPECT_FALSE(f.getEmptySet() != f.getEmptySet());
EXPECT_TRUE(f.getEmptySet().isEmpty());
ImmutableSet<int> S = f.getEmptySet();
EXPECT_EQ(0u, S.getHeight());
EXPECT_TRUE(S.begin() == S.end());
EXPECT_FALSE(S.begin() != S.end());
}
TEST_F(ImmutableSetTest, OneElemIntSetTest) {
ImmutableSet<int>::Factory f;
ImmutableSet<int> S = f.getEmptySet();
ImmutableSet<int> S2 = f.add(S, 3);
EXPECT_TRUE(S.isEmpty());
EXPECT_FALSE(S2.isEmpty());
EXPECT_FALSE(S == S2);
EXPECT_TRUE(S != S2);
EXPECT_FALSE(S.contains(3));
EXPECT_TRUE(S2.contains(3));
EXPECT_FALSE(S2.begin() == S2.end());
EXPECT_TRUE(S2.begin() != S2.end());
ImmutableSet<int> S3 = f.add(S, 2);
EXPECT_TRUE(S.isEmpty());
EXPECT_FALSE(S3.isEmpty());
EXPECT_FALSE(S == S3);
EXPECT_TRUE(S != S3);
EXPECT_FALSE(S.contains(2));
EXPECT_TRUE(S3.contains(2));
EXPECT_FALSE(S2 == S3);
EXPECT_TRUE(S2 != S3);
EXPECT_FALSE(S2.contains(2));
EXPECT_FALSE(S3.contains(3));
}
TEST_F(ImmutableSetTest, MultiElemIntSetTest) {
ImmutableSet<int>::Factory f;
ImmutableSet<int> S = f.getEmptySet();
ImmutableSet<int> S2 = f.add(f.add(f.add(S, 3), 4), 5);
ImmutableSet<int> S3 = f.add(f.add(f.add(S2, 9), 20), 43);
ImmutableSet<int> S4 = f.add(S2, 9);
EXPECT_TRUE(S.isEmpty());
EXPECT_FALSE(S2.isEmpty());
EXPECT_FALSE(S3.isEmpty());
EXPECT_FALSE(S4.isEmpty());
EXPECT_FALSE(S.contains(3));
EXPECT_FALSE(S.contains(9));
EXPECT_TRUE(S2.contains(3));
EXPECT_TRUE(S2.contains(4));
EXPECT_TRUE(S2.contains(5));
EXPECT_FALSE(S2.contains(9));
EXPECT_FALSE(S2.contains(0));
EXPECT_TRUE(S3.contains(43));
EXPECT_TRUE(S3.contains(20));
EXPECT_TRUE(S3.contains(9));
EXPECT_TRUE(S3.contains(3));
EXPECT_TRUE(S3.contains(4));
EXPECT_TRUE(S3.contains(5));
EXPECT_FALSE(S3.contains(0));
EXPECT_TRUE(S4.contains(9));
EXPECT_TRUE(S4.contains(3));
EXPECT_TRUE(S4.contains(4));
EXPECT_TRUE(S4.contains(5));
EXPECT_FALSE(S4.contains(20));
EXPECT_FALSE(S4.contains(43));
}
TEST_F(ImmutableSetTest, RemoveIntSetTest) {
ImmutableSet<int>::Factory f;
ImmutableSet<int> S = f.getEmptySet();
ImmutableSet<int> S2 = f.add(f.add(S, 4), 5);
ImmutableSet<int> S3 = f.add(S2, 3);
ImmutableSet<int> S4 = f.remove(S3, 3);
EXPECT_TRUE(S3.contains(3));
EXPECT_FALSE(S2.contains(3));
EXPECT_FALSE(S4.contains(3));
EXPECT_TRUE(S2 == S4);
EXPECT_TRUE(S3 != S2);
EXPECT_TRUE(S3 != S4);
EXPECT_TRUE(S3.contains(4));
EXPECT_TRUE(S3.contains(5));
EXPECT_TRUE(S4.contains(4));
EXPECT_TRUE(S4.contains(5));
}
TEST_F(ImmutableSetTest, IterLongSetTest) {
ImmutableSet<long>::Factory f;
ImmutableSet<long> S = f.getEmptySet();
ImmutableSet<long> S2 = f.add(f.add(f.add(S, 0), 1), 2);
ImmutableSet<long> S3 = f.add(f.add(f.add(S2, 3), 4), 5);
int i = 0;
for (ImmutableSet<long>::iterator I = S.begin(), E = S.end(); I != E; ++I) {
i++;
}
ASSERT_EQ(0, i);
i = 0;
for (ImmutableSet<long>::iterator I = S2.begin(), E = S2.end(); I != E; ++I) {
ASSERT_EQ(i, *I);
i++;
}
ASSERT_EQ(3, i);
i = 0;
for (ImmutableSet<long>::iterator I = S3.begin(), E = S3.end(); I != E; I++) {
ASSERT_EQ(i, *I);
i++;
}
ASSERT_EQ(6, i);
}
TEST_F(ImmutableSetTest, AddIfNotFoundTest) {
NCSet<long>::Factory f;
NCSet<long> S = f.getEmptySet();
S = f.add(S, 1);
S = f.add(S, 2);
S = f.add(S, 3);
NCSet<long> T1 = f.add(S, 1);
NCSet<long> T2 = f.add(S, 2);
NCSet<long> T3 = f.add(S, 3);
EXPECT_EQ(S.getRoot(), T1.getRoot());
EXPECT_EQ(S.getRoot(), T2.getRoot());
EXPECT_EQ(S.getRoot(), T3.getRoot());
NCSet<long> U = f.add(S, 4);
EXPECT_NE(S.getRoot(), U.getRoot());
}
TEST_F(ImmutableSetTest, RemoveIfNotFoundTest) {
NCSet<long>::Factory f;
NCSet<long> S = f.getEmptySet();
S = f.add(S, 1);
S = f.add(S, 2);
S = f.add(S, 3);
NCSet<long> T = f.remove(S, 4);
EXPECT_EQ(S.getRoot(), T.getRoot());
NCSet<long> U = f.remove(S, 3);
EXPECT_NE(S.getRoot(), U.getRoot());
}
//===----------------------------------------------------------------------===//
// In-order iterator correctness, validated against independent oracles.
//
// These checks do not assume any particular iterator implementation; they pin
// the externally observable contract (in-order ordering, reverse traversal,
// and skipSubTree). This is what the clang static analyzer and the tree
// canonicalization machinery rely on.
//===----------------------------------------------------------------------===//
namespace {
using Info = ImutContainerInfo<int>;
using Tree = ImutAVLTree<Info, /*Canonicalize=*/false>;
using TreeIter = Tree::iterator; // ImutAVLTreeInOrderIterator
// Build an ImmutableSet from the given values (in the given insertion order),
// optionally removing some afterwards, so trees of varied shape are produced.
NCSet<int> buildSet(NCSet<int>::Factory &F, ArrayRef<int> ToAdd,
ArrayRef<int> ToRemove = {}) {
NCSet<int> S = F.getEmptySet();
for (int V : ToAdd)
S = F.add(S, V);
for (int V : ToRemove)
S = F.remove(S, V);
return S;
}
// A representative collection of trees: degenerate, hand-picked small shapes,
// a large balanced one, and many pseudo-random insert/remove mixes.
std::vector<NCSet<int>> makeTestSets(NCSet<int>::Factory &F) {
std::vector<NCSet<int>> Sets;
Sets.push_back(F.getEmptySet());
Sets.push_back(buildSet(F, {42}));
Sets.push_back(buildSet(F, {1, 2, 3}));
Sets.push_back(buildSet(F, {3, 2, 1}));
Sets.push_back(buildSet(F, {2, 1, 3}));
std::vector<int> Sorted(200);
std::iota(Sorted.begin(), Sorted.end(), 0);
Sets.push_back(buildSet(F, Sorted));
std::mt19937 Rng(12345);
for (int Trial = 0; Trial < 25; ++Trial) {
std::vector<int> Vals(150);
std::iota(Vals.begin(), Vals.end(), 0);
std::shuffle(Vals.begin(), Vals.end(), Rng);
std::vector<int> Removals(Vals.begin(), Vals.begin() + (Trial % 40));
Sets.push_back(buildSet(F, Vals, Removals));
}
return Sets;
}
} // namespace
// Forward iteration must visit keys in ascending order, exactly matching an
// independent std::set holding the same elements.
TEST_F(ImmutableSetTest, IteratorInOrderMatchesStdSet) {
NCSet<int>::Factory F;
for (const NCSet<int> &S : makeTestSets(F)) {
std::set<int> Oracle;
for (NCSet<int>::iterator I = S.begin(), E = S.end(); I != E; ++I)
Oracle.insert(*I);
std::vector<int> Forward;
for (NCSet<int>::iterator I = S.begin(), E = S.end(); I != E; ++I)
Forward.push_back(*I);
EXPECT_TRUE(std::is_sorted(Forward.begin(), Forward.end()));
EXPECT_TRUE(std::adjacent_find(Forward.begin(), Forward.end()) ==
Forward.end()); // no duplicates
EXPECT_TRUE(std::equal(Forward.begin(), Forward.end(), Oracle.begin(),
Oracle.end()));
}
}
// Walking backwards with operator-- from the last element must reproduce the
// reverse of the forward traversal.
TEST_F(ImmutableSetTest, IteratorReverseMatchesForward) {
NCSet<int>::Factory F;
for (const NCSet<int> &S : makeTestSets(F)) {
const Tree *Root = S.getRootWithoutRetain();
std::vector<const Tree *> Forward;
for (TreeIter I(Root), E; I != E; ++I)
Forward.push_back(&*I);
if (Forward.empty())
continue;
// Advance to the last element, then walk backwards.
TreeIter Last(Root);
for (TreeIter I(Root), E; I != E; ++I)
Last = I;
std::vector<const Tree *> Backward;
Backward.push_back(&*Last);
for (TreeIter B = Last; Backward.size() < Forward.size();) {
--B;
Backward.push_back(&*B);
}
std::reverse(Backward.begin(), Backward.end());
EXPECT_EQ(Forward, Backward);
}
}
// skipSubTree must land on the in-order successor of the *entire* subtree
// rooted at the current node. Since an in-order traversal visits a subtree as a
// contiguous run, the destination index is computable independently from the
// node's right-subtree size.
TEST_F(ImmutableSetTest, IteratorSkipSubTree) {
NCSet<int>::Factory F;
for (const NCSet<int> &S : makeTestSets(F)) {
const Tree *Root = S.getRootWithoutRetain();
std::vector<const Tree *> Order;
for (TreeIter I(Root), E; I != E; ++I)
Order.push_back(&*I);
// From each starting index, skipSubTree should jump past the current node
// and its whole right subtree.
for (size_t Start = 0; Start < Order.size(); ++Start) {
TreeIter I(Root);
for (size_t N = 0; N < Start; ++N)
++I;
const Tree *Node = &*I;
unsigned RightSize = Node->getRight() ? Node->getRight()->size() : 0;
size_t ExpectedIdx = Start + 1 + RightSize;
I.skipSubTree();
if (ExpectedIdx >= Order.size())
EXPECT_TRUE(I == TreeIter()); // reached end
else
EXPECT_EQ(Order[ExpectedIdx], &*I);
}
}
}
// Structural equality (used by canonicalization) must agree with element-wise
// comparison of the in-order sequences. This exercises ++ and skipSubTree on
// the shared-subtree fast path inside ImutAVLTree::isEqual.
TEST_F(ImmutableSetTest, StructuralEqualityMatchesContents) {
NCSet<int>::Factory F;
std::vector<NCSet<int>> Sets = makeTestSets(F);
for (const NCSet<int> &A : Sets) {
for (const NCSet<int> &B : Sets) {
std::vector<int> VA(A.begin(), A.end());
std::vector<int> VB(B.begin(), B.end());
EXPECT_EQ(VA == VB, A == B);
}
}
}
// Two independent iterators compare equal iff they sit on the same node, and
// equal-to-end iff both are at end. Exercises the node-identity operator==.
TEST_F(ImmutableSetTest, IteratorEquality) {
NCSet<int>::Factory F;
for (const NCSet<int> &S : makeTestSets(F)) {
const Tree *Root = S.getRootWithoutRetain();
size_t N = std::distance(TreeIter(Root), TreeIter());
for (size_t I = 0; I < N; ++I) {
TreeIter A(Root), B(Root);
for (size_t K = 0; K < I; ++K) {
++A;
++B;
}
EXPECT_TRUE(A == B); // same position
EXPECT_FALSE(A != B);
EXPECT_TRUE(A != TreeIter()); // not end
TreeIter C = B;
++C;
EXPECT_TRUE(A != C); // adjacent positions differ
}
// Walking both to the end makes them equal to each other and to end().
TreeIter A(Root), B(Root);
while (A != TreeIter())
++A;
while (B != TreeIter())
++B;
EXPECT_TRUE(A == B);
EXPECT_TRUE(A == TreeIter());
}
}
// Exercises Factory::unionSets against a std::set oracle over many random
// input pairs, for both the canonicalizing (per-element fallback) and
// non-canonicalizing (bulk tree-tree) code paths.
template <bool Canonicalize> static void unionSetsStress() {
using Set = ImmutableSet<int, ImutContainerInfo<int>, Canonicalize>;
std::mt19937 Rng(42);
std::uniform_int_distribution<int> SizeDist(0, 120);
std::uniform_int_distribution<int> ValDist(0, 200);
for (int Trial = 0; Trial < 300; ++Trial) {
typename Set::Factory F;
Set A = F.getEmptySet();
Set B = F.getEmptySet();
std::set<int> RefA, RefB;
int NA = SizeDist(Rng), NB = SizeDist(Rng);
for (int I = 0; I < NA; ++I) {
int V = ValDist(Rng);
A = F.add(A, V);
RefA.insert(V);
}
for (int I = 0; I < NB; ++I) {
int V = ValDist(Rng);
B = F.add(B, V);
RefB.insert(V);
}
Set U = F.unionSets(A, B);
// The result is a structurally valid AVL tree.
if (const auto *Root = U.getRootWithoutRetain())
Root->validateTree();
// Its contents are exactly the union of the inputs.
std::set<int> RefU = RefA;
RefU.insert(RefB.begin(), RefB.end());
std::set<int> Got(U.begin(), U.end());
EXPECT_EQ(RefU, Got);
// The inputs are unchanged (persistence).
EXPECT_EQ(RefA, std::set<int>(A.begin(), A.end()));
EXPECT_EQ(RefB, std::set<int>(B.begin(), B.end()));
// Unioning with a subset / self is the identity.
EXPECT_TRUE(F.unionSets(U, A) == U);
EXPECT_TRUE(F.unionSets(A, A) == A);
}
}
TEST_F(ImmutableSetTest, UnionSetsStressTest) {
unionSetsStress</*Canonicalize=*/false>();
unionSetsStress</*Canonicalize=*/true>();
}
} // namespace