blob: 83b2ba0924b5ef6b0a0c4be054dcaeb599d1e3a1 [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/algorithm.h>
#include <fbl/tests/intrusive_containers/associative_container_test_environment.h>
namespace fbl {
namespace tests {
namespace intrusive_containers {
// OrderedAssociativeContainerTestEnvironment<>
//
// Test environment which defines and implements tests and test utilities which
// are applicable to all ordered associative containers (containers which keep
// their elements sorted by key) such as binary search trees.
template <typename TestEnvTraits>
class OrderedAssociativeContainerTestEnvironment
: public AssociativeContainerTestEnvironment<TestEnvTraits> {
public:
using ACTE = AssociativeContainerTestEnvironment<TestEnvTraits>;
using PopulateMethod = typename ACTE::PopulateMethod;
using ContainerType = typename ACTE::ContainerType;
using KeyTraits = typename ContainerType::KeyTraits;
using KeyType = typename ContainerType::KeyType;
using RawPtrType = typename ContainerType::RawPtrType;
struct NonConstTraits {
using ContainerType = typename ACTE::ContainerType;
using IterType = typename ACTE::ContainerType::iterator;
};
struct ConstTraits {
using ContainerType = const typename ACTE::ContainerType;
using IterType = typename ACTE::ContainerType::const_iterator;
};
template <typename ContainerTraits>
struct UpperBoundTraits {
static typename ContainerTraits::IterType Search(
typename ContainerTraits::ContainerType& container,
const KeyType& key) {
return container.upper_bound(key);
}
static bool BoundedBy(KeyType& key, const KeyType& bound_key) {
return KeyTraits::LessThan(key, bound_key);
}
};
template <typename ContainerTraits>
struct LowerBoundTraits {
static typename ContainerTraits::IterType Search(
typename ContainerTraits::ContainerType& container,
const KeyType& key) {
return container.lower_bound(key);
}
static bool BoundedBy(const KeyType& key, const KeyType& bound_key) {
return KeyTraits::LessThan(key, bound_key) || KeyTraits::EqualTo(key, bound_key);
}
};
bool DoOrderedIter(PopulateMethod populate_method) {
BEGIN_TEST;
ASSERT_TRUE(ACTE::Populate(container(), populate_method), "");
auto iter = container().begin();
EXPECT_TRUE(iter.IsValid(), "");
for (auto prev = iter++; iter.IsValid(); prev = iter++) {
// None of the associative containers currently support storing
// mutliple nodes with the same key, therefor the iteration ordering
// of the keys should be strictly monotonically increasing.
ASSERT_TRUE(prev.IsValid(), "");
auto iter_key = KeyTraits::GetKey(*iter);
auto prev_key = KeyTraits::GetKey(*prev);
EXPECT_TRUE (KeyTraits::LessThan(prev_key, iter_key), "");
EXPECT_FALSE(KeyTraits::LessThan(iter_key, prev_key), "");
EXPECT_FALSE(KeyTraits::EqualTo(prev_key, iter_key), "");
EXPECT_FALSE(KeyTraits::EqualTo(iter_key, prev_key), "");
}
ASSERT_TRUE(TestEnvironment<TestEnvTraits>::Reset(), "");
END_TEST;
}
bool OrderedIter() {
BEGIN_TEST;
EXPECT_TRUE(DoOrderedIter(PopulateMethod::AscendingKey), "");
EXPECT_TRUE(DoOrderedIter(PopulateMethod::DescendingKey), "");
EXPECT_TRUE(DoOrderedIter(PopulateMethod::RandomKey), "");
END_TEST;
}
bool DoOrderedReverseIter(PopulateMethod populate_method) {
BEGIN_TEST;
ASSERT_TRUE(ACTE::Populate(container(), populate_method), "");
auto iter = container().end();
EXPECT_FALSE(iter.IsValid(), "");
--iter;
EXPECT_TRUE(iter.IsValid(), "");
for (auto prev = iter--; iter.IsValid(); prev = iter--) {
// None of the associative containers currently support storing
// mutliple nodes with the same key, therefor the reverse iteration
// ordering of the keys should be strictly monotonically decreasing.
ASSERT_TRUE(prev.IsValid(), "");
auto iter_key = KeyTraits::GetKey(*iter);
auto prev_key = KeyTraits::GetKey(*prev);
EXPECT_TRUE (KeyTraits::LessThan(iter_key, prev_key), "");
EXPECT_FALSE(KeyTraits::LessThan(prev_key, iter_key), "");
EXPECT_FALSE(KeyTraits::EqualTo(prev_key, iter_key), "");
EXPECT_FALSE(KeyTraits::EqualTo(iter_key, prev_key), "");
}
ASSERT_TRUE(TestEnvironment<TestEnvTraits>::Reset(), "");
END_TEST;
}
bool OrderedReverseIter() {
BEGIN_TEST;
EXPECT_TRUE(DoOrderedReverseIter(PopulateMethod::AscendingKey), "");
EXPECT_TRUE(DoOrderedReverseIter(PopulateMethod::DescendingKey), "");
EXPECT_TRUE(DoOrderedReverseIter(PopulateMethod::RandomKey), "");
END_TEST;
}
template <typename BoundTraits>
bool DoBoundTest(PopulateMethod populate_method) {
BEGIN_TEST;
// Searching for a value while the tree is empty should always fail.
auto found = BoundTraits::Search(container(), 0u);
EXPECT_FALSE(found.IsValid(), "");
// Populate the container.
ASSERT_TRUE(ACTE::Populate(container(), populate_method), "");
// For every object we just put into the container compute the bound of
// obj.key, (obj.key - 1) and (obj.key + 1) using brute force as well as
// by using (upper|lower)_bound. Make sure that the result agree.
for (size_t i = 0; i < ACTE::OBJ_COUNT; ++i) {
auto ptr = ACTE::objects()[i];
ASSERT_NONNULL(ptr, "");
struct {
KeyType key;
RawPtrType bound;
} tests[] = {
{ .key = KeyTraits::GetKey(*ptr) - 1, .bound = nullptr }, // prev (key - 1)
{ .key = KeyTraits::GetKey(*ptr), .bound = nullptr }, // this (key)
{ .key = KeyTraits::GetKey(*ptr) + 1, .bound = nullptr }, // next (key + 1)
};
// Brute force search all of the objects we have populated the
// collect with to find the objects with the smallest keys which
// bound this/prev/next.
for (size_t j = 0; j < ACTE::OBJ_COUNT; ++j) {
auto tmp = ACTE::objects()[j];
ASSERT_NONNULL(tmp, "");
KeyType tmp_key = KeyTraits::GetKey(*tmp);
for (size_t k = 0; k < fbl::count_of(tests); ++k) {
auto& test = tests[k];
if (BoundTraits::BoundedBy(test.key, tmp_key) &&
(!test.bound ||
KeyTraits::LessThan(tmp_key, KeyTraits::GetKey(*test.bound))))
test.bound = tmp;
}
}
// Now perform the same searchs using upper_bound/lower_bound.
for (size_t k = 0; k < fbl::count_of(tests); ++k) {
auto& test = tests[k];
auto iter = BoundTraits::Search(container(), test.key);
// We should successfully find a bound using (upper|lower)_bound
// if (and only if) we successfully found a bound using brute
// force. If we did find a bound, it should be the same bound
// we found using brute force.
if (test.bound != nullptr) {
ASSERT_TRUE(iter.IsValid(), "");
EXPECT_EQ(test.bound, iter->raw_ptr(), "");
} else {
EXPECT_FALSE(iter.IsValid(), "");
}
}
}
ASSERT_TRUE(TestEnvironment<TestEnvTraits>::Reset(), "");
END_TEST;
}
bool UpperBound() {
BEGIN_TEST;
using NonConstBoundTraits = UpperBoundTraits<NonConstTraits>;
EXPECT_TRUE(DoBoundTest<NonConstBoundTraits>(PopulateMethod::AscendingKey), "");
EXPECT_TRUE(DoBoundTest<NonConstBoundTraits>(PopulateMethod::DescendingKey), "");
EXPECT_TRUE(DoBoundTest<NonConstBoundTraits>(PopulateMethod::RandomKey), "");
using ConstBoundTraits = UpperBoundTraits<ConstTraits>;
EXPECT_TRUE(DoBoundTest<ConstBoundTraits>(PopulateMethod::AscendingKey), "");
EXPECT_TRUE(DoBoundTest<ConstBoundTraits>(PopulateMethod::DescendingKey), "");
EXPECT_TRUE(DoBoundTest<ConstBoundTraits>(PopulateMethod::RandomKey), "");
END_TEST;
}
bool LowerBound() {
BEGIN_TEST;
using NonConstBoundTraits = LowerBoundTraits<NonConstTraits>;
EXPECT_TRUE(DoBoundTest<NonConstBoundTraits>(PopulateMethod::AscendingKey), "");
EXPECT_TRUE(DoBoundTest<NonConstBoundTraits>(PopulateMethod::DescendingKey), "");
EXPECT_TRUE(DoBoundTest<NonConstBoundTraits>(PopulateMethod::RandomKey), "");
using ConstBoundTraits = LowerBoundTraits<ConstTraits>;
EXPECT_TRUE(DoBoundTest<ConstBoundTraits>(PopulateMethod::AscendingKey), "");
EXPECT_TRUE(DoBoundTest<ConstBoundTraits>(PopulateMethod::DescendingKey), "");
EXPECT_TRUE(DoBoundTest<ConstBoundTraits>(PopulateMethod::RandomKey), "");
END_TEST;
}
private:
ContainerType& container() { return this->container_; }
const ContainerType& const_container() { return this->container_; }
};
} // namespace intrusive_containers
} // namespace tests
} // namespace fbl