1150 lines
31 KiB
C++
1150 lines
31 KiB
C++
// Copyright 2019 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "base/containers/intrusive_heap.h"
|
|
|
|
#include "base/check_op.h"
|
|
#include "base/functional/callback_helpers.h"
|
|
#include "base/memory/ptr_util.h"
|
|
#include "base/memory/raw_ptr_exclusion.h"
|
|
#include "base/notreached.h"
|
|
#include "base/rand_util.h"
|
|
#include "base/test/bind.h"
|
|
#include "testing/gmock/include/gmock/gmock.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
|
|
namespace base {
|
|
|
|
namespace {
|
|
|
|
using IntrusiveHeapInt = IntrusiveHeap<WithHeapHandle<int>>;
|
|
|
|
// Validates whether or not the given heap satisfies the heap invariant.
|
|
template <class H>
|
|
void ExpectHeap(const H& heap) {
|
|
const auto& less = heap.value_comp();
|
|
const auto& handle_access = heap.heap_handle_access();
|
|
|
|
for (size_t i = 0; i < heap.size(); ++i) {
|
|
size_t left = intrusive_heap::LeftIndex(i);
|
|
size_t right = left + 1;
|
|
|
|
if (left < heap.size())
|
|
EXPECT_FALSE(less(heap[i], heap[left]));
|
|
if (right < heap.size())
|
|
EXPECT_FALSE(less(heap[i], heap[right]));
|
|
|
|
intrusive_heap::CheckInvalidOrEqualTo(handle_access.GetHeapHandle(&heap[i]),
|
|
i);
|
|
}
|
|
}
|
|
|
|
// A small set of canonical elements, and the a function for validating the
|
|
// heap that should be created by those elements. This is used in various
|
|
// constructor/insertion tests.
|
|
#define CANONICAL_ELEMENTS 3, 1, 2, 4, 5, 6, 7, 0
|
|
void ExpectCanonical(const IntrusiveHeapInt& heap) {
|
|
ExpectHeap(heap);
|
|
|
|
// Manual implementation of a max-heap inserting the elements defined by
|
|
// CANONICAL_ELEMENTS:
|
|
// 3
|
|
// 3 1
|
|
// 3 1 2
|
|
// 3 1 2 4 -> 3 4 2 1 -> 4 3 2 1
|
|
// 4 3 2 1 5 -> 4 5 2 1 3 -> 5 4 2 1 3
|
|
// 5 4 2 1 3 6 -> 5 4 6 1 3 2 -> 6 4 5 1 3 2
|
|
// 6 4 5 1 3 2 7 -> 6 4 7 1 3 2 5 -> 7 4 6 1 3 2 5
|
|
// 7 4 6 1 3 2 5 0
|
|
std::vector<int> expected{7, 4, 6, 1, 3, 2, 5, 0};
|
|
std::vector<int> actual;
|
|
for (const auto& element : heap)
|
|
actual.push_back(element.value());
|
|
ASSERT_THAT(actual, testing::ContainerEq(expected));
|
|
}
|
|
|
|
// Initializes the given heap to be the "canonical" heap from the point of view
|
|
// of these tests.
|
|
void MakeCanonical(IntrusiveHeapInt* heap) {
|
|
static constexpr int kInts[] = {CANONICAL_ELEMENTS};
|
|
heap->clear();
|
|
heap->insert(kInts, kInts + std::size(kInts));
|
|
ExpectCanonical(*heap);
|
|
}
|
|
|
|
// A handful of helper functions and classes related to building an exhaustive
|
|
// stress test for IntrusiveHeap, with all combinations of default-constructible
|
|
// supports-move-operations and supports-copy-operations value types.
|
|
|
|
// IntrusiveHeap supports 3 types of operations: those that cause the heap to
|
|
// get smaller (deletions), those that keep the heap the same size (updates,
|
|
// replaces, etc), and those that cause the heap to get bigger (insertions).
|
|
enum OperationTypes : int {
|
|
kGrowing,
|
|
kShrinking,
|
|
kSameSize,
|
|
kOperationTypesCount
|
|
};
|
|
|
|
// The operations that cause a heap to get bigger.
|
|
enum GrowingOperations : int { kInsert, kEmplace, kGrowingOperationsCount };
|
|
|
|
// The operations that cause a heap to get smaller. Some of these are only
|
|
// supported by move-only value types.
|
|
enum ShrinkingOperations : int {
|
|
kTake,
|
|
kTakeTop,
|
|
kErase,
|
|
kPop,
|
|
kShrinkingOperationsCount
|
|
};
|
|
|
|
// The operations that keep a heap the same size.
|
|
enum SameSizeOperations : int {
|
|
kReplace,
|
|
kReplaceTop,
|
|
kUpdate,
|
|
kSameSizeOperationsCount
|
|
};
|
|
|
|
// Randomly selects an operation for the GrowingOperations enum, applies it to
|
|
// the given heap, and validates that the operation completed as expected.
|
|
template <typename T>
|
|
void DoGrowingOperation(IntrusiveHeap<T>* heap) {
|
|
GrowingOperations op = static_cast<GrowingOperations>(
|
|
base::RandInt(0, kGrowingOperationsCount - 1));
|
|
|
|
int value = base::RandInt(0, 1000);
|
|
size_t old_size = heap->size();
|
|
typename IntrusiveHeap<T>::const_iterator it;
|
|
|
|
switch (op) {
|
|
case kInsert: {
|
|
it = heap->insert(T(value));
|
|
break;
|
|
}
|
|
|
|
case kEmplace: {
|
|
it = heap->emplace(value);
|
|
break;
|
|
}
|
|
|
|
case kGrowingOperationsCount:
|
|
NOTREACHED();
|
|
}
|
|
|
|
EXPECT_EQ(old_size + 1, heap->size());
|
|
EXPECT_EQ(value, it->value());
|
|
EXPECT_EQ(it->GetHeapHandle().index(), heap->ToIndex(it));
|
|
}
|
|
|
|
// Helper struct for determining with the given value type T is movable or not.
|
|
// Used to determine whether or not the "take" operations can be used.
|
|
template <typename T>
|
|
struct NotMovable {
|
|
static constexpr bool value = !std::is_nothrow_move_constructible<T>::value &&
|
|
std::is_copy_constructible<T>::value;
|
|
};
|
|
|
|
// Invokes "take" if the type is movable, otherwise invokes erase.
|
|
template <typename T, bool kNotMovable = NotMovable<T>::value>
|
|
struct Take;
|
|
template <typename T>
|
|
struct Take<T, true> {
|
|
static void Do(IntrusiveHeap<T>* heap, size_t index) { heap->erase(index); }
|
|
};
|
|
template <typename T>
|
|
struct Take<T, false> {
|
|
static void Do(IntrusiveHeap<T>* heap, size_t index) {
|
|
int value = heap->at(index).value();
|
|
T t = heap->take(index);
|
|
EXPECT_EQ(value, t.value());
|
|
EXPECT_FALSE(t.GetHeapHandle().IsValid());
|
|
}
|
|
};
|
|
|
|
// Invokes "take_top" if the type is movable, otherwise invokes pop.
|
|
template <typename T, bool kNotMovable = NotMovable<T>::value>
|
|
struct TakeTop;
|
|
template <typename T>
|
|
struct TakeTop<T, true> {
|
|
static void Do(IntrusiveHeap<T>* heap) { heap->pop(); }
|
|
};
|
|
template <typename T>
|
|
struct TakeTop<T, false> {
|
|
static void Do(IntrusiveHeap<T>* heap) {
|
|
int value = heap->at(0).value();
|
|
T t = heap->take_top();
|
|
EXPECT_EQ(value, t.value());
|
|
EXPECT_FALSE(t.GetHeapHandle().IsValid());
|
|
}
|
|
};
|
|
|
|
// Randomly selects a shrinking operations, applies it to the given |heap| and
|
|
// validates that the operation completed as expected and resulted in a valid
|
|
// heap. The "take" operations will only be invoked for a value type T that
|
|
// supports move operations, otherwise they will be mapped to erase/pop.
|
|
template <typename T>
|
|
void DoShrinkingOperation(IntrusiveHeap<T>* heap) {
|
|
ShrinkingOperations op = static_cast<ShrinkingOperations>(
|
|
base::RandInt(0, kShrinkingOperationsCount - 1));
|
|
|
|
size_t old_size = heap->size();
|
|
size_t index = static_cast<size_t>(base::RandInt(0, old_size - 1));
|
|
|
|
switch (op) {
|
|
case kTake: {
|
|
Take<T>::Do(heap, index);
|
|
break;
|
|
}
|
|
|
|
case kTakeTop: {
|
|
TakeTop<T>::Do(heap);
|
|
break;
|
|
}
|
|
|
|
case kErase: {
|
|
heap->erase(index);
|
|
break;
|
|
}
|
|
|
|
case kPop: {
|
|
heap->pop();
|
|
break;
|
|
}
|
|
|
|
case kShrinkingOperationsCount:
|
|
NOTREACHED();
|
|
}
|
|
|
|
EXPECT_EQ(old_size - 1, heap->size());
|
|
}
|
|
|
|
// Randomly selects a same size operation, applies it to the given |heap| and
|
|
// validates that the operation completed as expected and resulted in a valid
|
|
// heap.
|
|
template <typename T>
|
|
void DoSameSizeOperation(IntrusiveHeap<T>* heap) {
|
|
SameSizeOperations op = static_cast<SameSizeOperations>(
|
|
base::RandInt(0, kSameSizeOperationsCount - 1));
|
|
|
|
size_t old_size = heap->size();
|
|
size_t index = static_cast<size_t>(base::RandInt(0, old_size - 1));
|
|
if (op == kReplaceTop)
|
|
index = 0;
|
|
int new_value = base::RandInt(0, 1000);
|
|
typename IntrusiveHeap<T>::const_iterator it;
|
|
|
|
switch (op) {
|
|
case kReplace: {
|
|
it = heap->Replace(index, T(new_value));
|
|
break;
|
|
}
|
|
|
|
case kReplaceTop: {
|
|
it = heap->ReplaceTop(T(new_value));
|
|
break;
|
|
}
|
|
|
|
case kUpdate: {
|
|
it = heap->Modify(
|
|
index, [&new_value](T& element) { element.set_value(new_value); });
|
|
break;
|
|
}
|
|
|
|
case kSameSizeOperationsCount:
|
|
NOTREACHED();
|
|
}
|
|
|
|
EXPECT_EQ(old_size, heap->size());
|
|
EXPECT_EQ(new_value, it->value());
|
|
EXPECT_EQ(it->GetHeapHandle().index(), heap->ToIndex(it));
|
|
}
|
|
|
|
// Randomly selects an operation, applies it to the given |heap| and validates
|
|
// that the operation completed as expected and resulted in a valid heap.
|
|
template <typename T>
|
|
void DoRandomHeapOperation(IntrusiveHeap<T>* heap) {
|
|
static constexpr int kMinHeapSize = 10u;
|
|
static constexpr int kMaxHeapSize = 100u;
|
|
|
|
OperationTypes operation_type =
|
|
static_cast<OperationTypes>(base::RandInt(0, kOperationTypesCount - 1));
|
|
|
|
// Keep the heap size bounded by forcing growing and shrinking operations when
|
|
// it exceeds the bounds.
|
|
if (heap->size() < kMinHeapSize) {
|
|
operation_type = kGrowing;
|
|
} else if (heap->size() > kMaxHeapSize) {
|
|
operation_type = kShrinking;
|
|
}
|
|
|
|
switch (operation_type) {
|
|
case kGrowing:
|
|
DoGrowingOperation(heap);
|
|
break;
|
|
case kShrinking:
|
|
DoShrinkingOperation(heap);
|
|
break;
|
|
case kSameSize:
|
|
DoSameSizeOperation(heap);
|
|
break;
|
|
case kOperationTypesCount:
|
|
NOTREACHED();
|
|
}
|
|
}
|
|
|
|
// A stress test that is only applicable to value types T that support move
|
|
// operations.
|
|
template <typename T>
|
|
void MoveStressTest() {
|
|
IntrusiveHeap<T> heap({2, 4, 6, 8});
|
|
EXPECT_EQ(4u, heap.size());
|
|
EXPECT_FALSE(heap.empty());
|
|
ExpectHeap(heap);
|
|
|
|
IntrusiveHeap<T> heap2(std::move(heap));
|
|
EXPECT_EQ(4u, heap2.size());
|
|
EXPECT_FALSE(heap2.empty());
|
|
ExpectHeap(heap2);
|
|
EXPECT_EQ(0u, heap.size());
|
|
EXPECT_TRUE(heap.empty());
|
|
ExpectHeap(heap);
|
|
|
|
heap = std::move(heap2);
|
|
EXPECT_EQ(4u, heap.size());
|
|
EXPECT_FALSE(heap.empty());
|
|
ExpectHeap(heap);
|
|
EXPECT_EQ(0u, heap2.size());
|
|
EXPECT_TRUE(heap2.empty());
|
|
ExpectHeap(heap2);
|
|
}
|
|
|
|
// A stress that that is only applicable to value types T that support copy
|
|
// operations.
|
|
template <typename T>
|
|
void CopyStressTest() {
|
|
IntrusiveHeap<T> heap({2, 4, 6, 8});
|
|
EXPECT_EQ(4u, heap.size());
|
|
EXPECT_FALSE(heap.empty());
|
|
ExpectHeap(heap);
|
|
|
|
IntrusiveHeap<T> heap2(heap);
|
|
EXPECT_EQ(4u, heap2.size());
|
|
EXPECT_FALSE(heap2.empty());
|
|
ExpectHeap(heap2);
|
|
EXPECT_EQ(4u, heap.size());
|
|
EXPECT_FALSE(heap.empty());
|
|
ExpectHeap(heap);
|
|
|
|
IntrusiveHeap<T> heap3({1, 3, 5});
|
|
heap3.clear();
|
|
heap3 = heap;
|
|
EXPECT_EQ(4u, heap3.size());
|
|
EXPECT_FALSE(heap3.empty());
|
|
ExpectHeap(heap);
|
|
EXPECT_EQ(4u, heap.size());
|
|
EXPECT_FALSE(heap.empty());
|
|
ExpectHeap(heap);
|
|
|
|
EXPECT_TRUE(heap == heap2);
|
|
EXPECT_FALSE(heap != heap2);
|
|
}
|
|
|
|
// A stress test that is applicable to all value types, whether or not they
|
|
// support copy and/or move operations.
|
|
template <typename T>
|
|
void GeneralStressTest() {
|
|
std::vector<int> vector{2, 4, 6, 8};
|
|
IntrusiveHeap<T> heap(vector.begin(), vector.end());
|
|
EXPECT_EQ(4u, heap.size());
|
|
EXPECT_FALSE(heap.empty());
|
|
ExpectHeap(heap);
|
|
|
|
heap.clear();
|
|
EXPECT_EQ(0u, heap.size());
|
|
EXPECT_TRUE(heap.empty());
|
|
ExpectHeap(heap);
|
|
|
|
// Create an element and get a handle to it.
|
|
auto it = heap.insert(T(34));
|
|
EXPECT_EQ(1u, heap.size());
|
|
HeapHandle* handle = it->handle();
|
|
EXPECT_EQ(0u, handle->index());
|
|
ExpectHeap(heap);
|
|
|
|
// Add some other elements.
|
|
heap.insert(T(12));
|
|
heap.emplace(14);
|
|
EXPECT_EQ(3u, heap.size());
|
|
ExpectHeap(heap);
|
|
|
|
// The handle should have tracked the element it is associated with.
|
|
EXPECT_EQ(34, heap[*handle].value());
|
|
|
|
// Replace with a value that shouldn't need moving in the heap.
|
|
size_t index = handle->index();
|
|
handle = heap.Replace(*handle, T(40))->handle();
|
|
EXPECT_EQ(3u, heap.size());
|
|
ExpectHeap(heap);
|
|
EXPECT_EQ(index, handle->index());
|
|
|
|
// Replace with a value that should cause the entry to move.
|
|
handle = heap.Replace(handle->index(), T(1))->handle();
|
|
EXPECT_EQ(3u, heap.size());
|
|
ExpectHeap(heap);
|
|
EXPECT_NE(index, handle->index());
|
|
|
|
// Replace the top element.
|
|
heap.ReplaceTop(T(65));
|
|
EXPECT_EQ(3u, heap.size());
|
|
ExpectHeap(heap);
|
|
|
|
// Insert several more elements.
|
|
std::vector<int> elements({13, 17, 19, 23, 29, 31, 37, 41});
|
|
heap.insert(elements.begin(), elements.end());
|
|
EXPECT_EQ(11u, heap.size());
|
|
ExpectHeap(heap);
|
|
|
|
// Invasively change an element that is already inside the heap, and then
|
|
// repair the heap.
|
|
T* element = const_cast<T*>(&heap[7]);
|
|
element->set_value(97);
|
|
heap.Update(7u);
|
|
ExpectHeap(heap);
|
|
|
|
// Safely modify an element that is already inside the heap.
|
|
heap.Modify(7u, [](T& element) { element.set_value(128); });
|
|
ExpectHeap(heap);
|
|
|
|
// Do some more updates that are no-ops, just to explore all the flavours of
|
|
// ToIndex.
|
|
handle = heap[5].handle();
|
|
heap.Update(*handle);
|
|
heap.Update(heap.begin() + 6);
|
|
heap.Update(heap.rbegin() + 8);
|
|
ExpectHeap(heap);
|
|
|
|
handle = heap[5].handle();
|
|
EXPECT_TRUE(handle);
|
|
EXPECT_EQ(5u, handle->index());
|
|
EXPECT_EQ(5u, heap.ToIndex(*handle));
|
|
EXPECT_EQ(5u, heap.ToIndex(heap.begin() + 5));
|
|
EXPECT_EQ(5u, heap.ToIndex(heap.cbegin() + 5));
|
|
EXPECT_EQ(5u, heap.ToIndex(heap.rbegin() + 5));
|
|
EXPECT_EQ(5u, heap.ToIndex(heap.crbegin() + 5));
|
|
EXPECT_EQ(HeapHandle::kInvalidIndex, heap.ToIndex(heap.end()));
|
|
EXPECT_EQ(HeapHandle::kInvalidIndex, heap.ToIndex(heap.cend()));
|
|
EXPECT_EQ(HeapHandle::kInvalidIndex, heap.ToIndex(heap.rend()));
|
|
EXPECT_EQ(HeapHandle::kInvalidIndex, heap.ToIndex(heap.crend()));
|
|
|
|
EXPECT_EQ(&heap[0], &heap.at(0));
|
|
EXPECT_EQ(&heap[0], &heap.front());
|
|
EXPECT_EQ(&heap[0], &heap.top());
|
|
EXPECT_EQ(&heap[heap.size() - 1], &heap.back());
|
|
EXPECT_EQ(&heap[0], heap.data());
|
|
|
|
// Do a bunch of random operations on a heap as a stress test.
|
|
for (size_t i = 0; i < 1000; ++i) {
|
|
DoRandomHeapOperation(&heap);
|
|
ExpectHeap(heap);
|
|
}
|
|
}
|
|
|
|
// A basic value type that wraps an integer. This is default constructible, and
|
|
// supports both move and copy operations.
|
|
class Value : public InternalHeapHandleStorage {
|
|
public:
|
|
explicit Value(int value) : value_(value) {}
|
|
Value() : value_(-1) {}
|
|
Value(Value&& other) noexcept
|
|
: InternalHeapHandleStorage(std::move(other)),
|
|
value_(std::exchange(other.value_, -1)) {}
|
|
Value(const Value& other) : value_(other.value_) {
|
|
HeapHandle h = other.GetHeapHandle();
|
|
if (h.IsValid())
|
|
SetHeapHandle(h);
|
|
}
|
|
~Value() override {}
|
|
|
|
Value& operator=(Value&& other) noexcept {
|
|
InternalHeapHandleStorage::operator=(std::move(other));
|
|
value_ = std::exchange(other.value_, -1);
|
|
return *this;
|
|
}
|
|
Value& operator=(const Value& other) {
|
|
value_ = other.value_;
|
|
return *this;
|
|
}
|
|
|
|
int value() const { return value_; }
|
|
void set_value(int value) { value_ = value; }
|
|
|
|
bool operator==(const Value& rhs) const { return value_ == rhs.value_; }
|
|
bool operator!=(const Value& rhs) const { return value_ != rhs.value_; }
|
|
bool operator<=(const Value& rhs) const { return value_ <= rhs.value_; }
|
|
bool operator>=(const Value& rhs) const { return value_ >= rhs.value_; }
|
|
bool operator<(const Value& rhs) const { return value_ < rhs.value_; }
|
|
bool operator>(const Value& rhs) const { return value_ > rhs.value_; }
|
|
|
|
private:
|
|
int value_;
|
|
};
|
|
|
|
// Macro for creating versions of Value that selectively enable/disable
|
|
// default-constructors, move-operations and copy-operations.
|
|
#define DEFINE_VALUE_TYPE(name, default_construct, move, copy) \
|
|
class name : public Value { \
|
|
public: \
|
|
explicit name(int value) : Value(value) {} \
|
|
name() = default_construct; \
|
|
name(name&&) noexcept = move; \
|
|
name(const name&) = copy; \
|
|
name& operator=(name&&) noexcept = move; \
|
|
name& operator=(const name&) = copy; \
|
|
};
|
|
|
|
DEFINE_VALUE_TYPE(Value_DMC, default, default, default)
|
|
DEFINE_VALUE_TYPE(Value_DmC, default, delete, default)
|
|
DEFINE_VALUE_TYPE(Value_DMc, default, default, delete)
|
|
DEFINE_VALUE_TYPE(Value_dMC, delete, default, default)
|
|
DEFINE_VALUE_TYPE(Value_dmC, delete, delete, default)
|
|
DEFINE_VALUE_TYPE(Value_dMc, delete, default, delete)
|
|
|
|
// Used to validate that the generated value types work as expected wrt
|
|
// default-constructors, move-operations and copy-operations.
|
|
template <typename ValueType, bool D, bool M, bool C>
|
|
void ValidateValueType() {
|
|
static_assert(std::is_default_constructible<ValueType>::value == D, "oops");
|
|
static_assert(std::is_move_constructible<ValueType>::value == M, "oops");
|
|
static_assert(std::is_move_assignable<ValueType>::value == M, "oops");
|
|
static_assert(std::is_copy_constructible<ValueType>::value == C, "oops");
|
|
static_assert(std::is_copy_assignable<ValueType>::value == C, "oops");
|
|
}
|
|
|
|
// A small test element that provides its own HeapHandle storage and implements
|
|
// the contract expected of the DefaultHeapHandleAccessor.
|
|
struct TestElement {
|
|
int key;
|
|
// This field is not a raw_ptr<> because it was filtered by the rewriter for:
|
|
// #reinterpret-cast-trivial-type
|
|
RAW_PTR_EXCLUSION HeapHandle* handle;
|
|
|
|
// Make this a min-heap by return > instead of <.
|
|
bool operator<(const TestElement& other) const { return key > other.key; }
|
|
|
|
void SetHeapHandle(HeapHandle h) {
|
|
if (handle)
|
|
*handle = h;
|
|
}
|
|
|
|
void ClearHeapHandle() {
|
|
if (handle)
|
|
handle->reset();
|
|
}
|
|
|
|
HeapHandle GetHeapHandle() const {
|
|
if (handle)
|
|
return *handle;
|
|
return HeapHandle::Invalid();
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// TEST SUITE 1
|
|
//
|
|
// Explicit tests of a simple heap using WithHeapHandle<>.
|
|
|
|
TEST(IntrusiveHeapTest, Constructors) {
|
|
{
|
|
// Default constructor.
|
|
IntrusiveHeapInt heap;
|
|
EXPECT_TRUE(heap.empty());
|
|
}
|
|
|
|
{
|
|
// Constructor with iterators.
|
|
std::vector<int> ints{CANONICAL_ELEMENTS};
|
|
IntrusiveHeapInt heap(ints.begin(), ints.end());
|
|
ExpectCanonical(heap);
|
|
|
|
// Move constructor.
|
|
IntrusiveHeapInt heap2(std::move(heap));
|
|
EXPECT_TRUE(heap.empty());
|
|
ExpectCanonical(heap2);
|
|
}
|
|
|
|
{
|
|
// Constructor with initializer list.
|
|
IntrusiveHeapInt heap{CANONICAL_ELEMENTS};
|
|
ExpectCanonical(heap);
|
|
}
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, Assignment) {
|
|
IntrusiveHeapInt heap{CANONICAL_ELEMENTS};
|
|
|
|
// Move assignment.
|
|
IntrusiveHeapInt heap2;
|
|
heap2 = std::move(heap);
|
|
EXPECT_TRUE(heap.empty());
|
|
ExpectCanonical(heap2);
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, Swap) {
|
|
IntrusiveHeapInt heap{CANONICAL_ELEMENTS};
|
|
IntrusiveHeapInt heap2;
|
|
swap(heap, heap2);
|
|
EXPECT_TRUE(heap.empty());
|
|
ExpectCanonical(heap2);
|
|
heap.swap(heap2);
|
|
EXPECT_TRUE(heap2.empty());
|
|
ExpectCanonical(heap);
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, ElementAccess) {
|
|
IntrusiveHeapInt heap{CANONICAL_ELEMENTS};
|
|
EXPECT_EQ(heap.front(), heap[0]);
|
|
EXPECT_EQ(heap.back(), heap[7]);
|
|
EXPECT_EQ(heap.top(), heap[0]);
|
|
for (size_t i = 0; i < heap.size(); ++i) {
|
|
EXPECT_EQ(heap[i], heap.at(i));
|
|
EXPECT_EQ(heap[i], heap.data()[i]);
|
|
}
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, SizeManagement) {
|
|
IntrusiveHeapInt heap;
|
|
EXPECT_TRUE(heap.empty());
|
|
EXPECT_LE(heap.size(), heap.capacity());
|
|
|
|
MakeCanonical(&heap);
|
|
EXPECT_FALSE(heap.empty());
|
|
EXPECT_LE(heap.size(), heap.capacity());
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, Iterators) {
|
|
IntrusiveHeapInt heap;
|
|
MakeCanonical(&heap);
|
|
|
|
size_t i = 0;
|
|
for (auto it = heap.begin(); it != heap.end(); ++it) {
|
|
EXPECT_EQ(i, heap.ToIndex(it));
|
|
EXPECT_EQ(&(*it), heap.data() + i);
|
|
++i;
|
|
}
|
|
|
|
i = heap.size() - 1;
|
|
for (auto rit = heap.rbegin(); rit != heap.rend(); ++rit) {
|
|
EXPECT_EQ(i, heap.ToIndex(rit));
|
|
EXPECT_EQ(&(*rit), heap.data() + i);
|
|
--i;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// TEST SUITE 2
|
|
//
|
|
// Exhaustive stress tests with different value types, exploring all
|
|
// possibilities of default-constrible, movable and copyable value types.
|
|
|
|
TEST(IntrusiveHeapTest, MoveOnlyNoDefaultConstructorTest) {
|
|
using ValueType = Value_dMc;
|
|
ValidateValueType<ValueType, false, true, false>();
|
|
MoveStressTest<ValueType>();
|
|
GeneralStressTest<ValueType>();
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, CopyOnlyNoDefaultConstructorTest) {
|
|
using ValueType = Value_dmC;
|
|
ValidateValueType<ValueType, false, false, true>();
|
|
// We cannot perform CopyStressTest nor GeneralStressTest here, because
|
|
// Value_dmC has deleted move constructor and assignment operator. See
|
|
// crbug.com/1022576.
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, CopyAndMoveNoDefaultConstructorTest) {
|
|
using ValueType = Value_dMC;
|
|
ValidateValueType<ValueType, false, true, true>();
|
|
CopyStressTest<ValueType>();
|
|
MoveStressTest<ValueType>();
|
|
GeneralStressTest<ValueType>();
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, MoveOnlyWithDefaultConstructorTest) {
|
|
using ValueType = Value_DMc;
|
|
ValidateValueType<ValueType, true, true, false>();
|
|
MoveStressTest<ValueType>();
|
|
GeneralStressTest<ValueType>();
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, CopyOnlyWithDefaultConstructorTest) {
|
|
using ValueType = Value_DmC;
|
|
ValidateValueType<ValueType, true, false, true>();
|
|
// We cannot perform CopyStressTest nor GeneralStressTest here, because
|
|
// Value_DmC has deleted move constructor and assignment operator. See
|
|
// crbug.com/1022576.
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, CopyAndMoveWithDefaultConstructorTest) {
|
|
using ValueType = Value_DMC;
|
|
ValidateValueType<ValueType, true, true, true>();
|
|
CopyStressTest<ValueType>();
|
|
MoveStressTest<ValueType>();
|
|
GeneralStressTest<ValueType>();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// TEST SUITE 3
|
|
//
|
|
// Tests individual functions on a custom type that provides heap handle storage
|
|
// externally through raw pointers.
|
|
|
|
TEST(IntrusiveHeapTest, Basic) {
|
|
IntrusiveHeap<TestElement> heap;
|
|
|
|
EXPECT_TRUE(heap.empty());
|
|
EXPECT_EQ(0u, heap.size());
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, Clear) {
|
|
IntrusiveHeap<TestElement> heap;
|
|
HeapHandle index1;
|
|
|
|
heap.insert({11, &index1});
|
|
EXPECT_EQ(1u, heap.size());
|
|
EXPECT_TRUE(index1.IsValid());
|
|
|
|
heap.clear();
|
|
EXPECT_EQ(0u, heap.size());
|
|
EXPECT_FALSE(index1.IsValid());
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, Destructor) {
|
|
HeapHandle index1;
|
|
|
|
{
|
|
IntrusiveHeap<TestElement> heap;
|
|
|
|
heap.insert({11, &index1});
|
|
EXPECT_EQ(1u, heap.size());
|
|
EXPECT_TRUE(index1.IsValid());
|
|
}
|
|
|
|
EXPECT_FALSE(index1.IsValid());
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, Min) {
|
|
IntrusiveHeap<TestElement> heap;
|
|
|
|
heap.insert({9, nullptr});
|
|
heap.insert({10, nullptr});
|
|
heap.insert({8, nullptr});
|
|
heap.insert({2, nullptr});
|
|
heap.insert({7, nullptr});
|
|
heap.insert({15, nullptr});
|
|
heap.insert({22, nullptr});
|
|
heap.insert({3, nullptr});
|
|
|
|
EXPECT_FALSE(heap.empty());
|
|
EXPECT_EQ(8u, heap.size());
|
|
EXPECT_EQ(2, heap.top().key);
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, MinDuplicates) {
|
|
IntrusiveHeap<TestElement> heap;
|
|
|
|
heap.insert({2, nullptr});
|
|
heap.insert({2, nullptr});
|
|
heap.insert({3, nullptr});
|
|
|
|
EXPECT_FALSE(heap.empty());
|
|
EXPECT_EQ(3u, heap.size());
|
|
EXPECT_EQ(2, heap.top().key);
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, InsertAscending) {
|
|
IntrusiveHeap<TestElement> heap;
|
|
|
|
for (int i = 0; i < 50; i++)
|
|
heap.insert({i, nullptr});
|
|
|
|
EXPECT_EQ(0, heap.top().key);
|
|
EXPECT_EQ(50u, heap.size());
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, InsertDescending) {
|
|
IntrusiveHeap<TestElement> heap;
|
|
|
|
for (int i = 0; i < 50; i++)
|
|
heap.insert({50 - i, nullptr});
|
|
|
|
EXPECT_EQ(1, heap.top().key);
|
|
EXPECT_EQ(50u, heap.size());
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, HeapIndex) {
|
|
HeapHandle index5;
|
|
HeapHandle index4;
|
|
HeapHandle index3;
|
|
HeapHandle index2;
|
|
HeapHandle index1;
|
|
IntrusiveHeap<TestElement> heap;
|
|
|
|
EXPECT_FALSE(index1.IsValid());
|
|
EXPECT_FALSE(index2.IsValid());
|
|
EXPECT_FALSE(index3.IsValid());
|
|
EXPECT_FALSE(index4.IsValid());
|
|
EXPECT_FALSE(index5.IsValid());
|
|
|
|
heap.insert({15, &index5});
|
|
heap.insert({14, &index4});
|
|
heap.insert({13, &index3});
|
|
heap.insert({12, &index2});
|
|
heap.insert({11, &index1});
|
|
|
|
EXPECT_TRUE(index1.IsValid());
|
|
EXPECT_TRUE(index2.IsValid());
|
|
EXPECT_TRUE(index3.IsValid());
|
|
EXPECT_TRUE(index4.IsValid());
|
|
EXPECT_TRUE(index5.IsValid());
|
|
|
|
EXPECT_FALSE(heap.empty());
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, HeapIndexDuplicates) {
|
|
HeapHandle index2;
|
|
HeapHandle index1;
|
|
IntrusiveHeap<TestElement> heap;
|
|
|
|
EXPECT_FALSE(index1.IsValid());
|
|
EXPECT_FALSE(index2.IsValid());
|
|
|
|
heap.insert({2, &index2});
|
|
heap.insert({2, &index1});
|
|
|
|
EXPECT_TRUE(index1.IsValid());
|
|
EXPECT_TRUE(index2.IsValid());
|
|
|
|
EXPECT_EQ(2U, heap.size());
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, Pop) {
|
|
IntrusiveHeap<TestElement> heap;
|
|
HeapHandle index1;
|
|
HeapHandle index2;
|
|
|
|
heap.insert({11, &index1});
|
|
heap.insert({12, &index2});
|
|
EXPECT_EQ(2u, heap.size());
|
|
EXPECT_TRUE(index1.IsValid());
|
|
EXPECT_TRUE(index2.IsValid());
|
|
|
|
heap.pop();
|
|
EXPECT_EQ(1u, heap.size());
|
|
EXPECT_FALSE(index1.IsValid());
|
|
EXPECT_TRUE(index2.IsValid());
|
|
|
|
heap.pop();
|
|
EXPECT_EQ(0u, heap.size());
|
|
EXPECT_FALSE(index1.IsValid());
|
|
EXPECT_FALSE(index2.IsValid());
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, PopMany) {
|
|
IntrusiveHeap<TestElement> heap;
|
|
|
|
for (int i = 0; i < 500; i++)
|
|
heap.insert({i, nullptr});
|
|
|
|
EXPECT_FALSE(heap.empty());
|
|
EXPECT_EQ(500u, heap.size());
|
|
for (int i = 0; i < 500; i++) {
|
|
EXPECT_EQ(i, heap.top().key);
|
|
heap.pop();
|
|
}
|
|
EXPECT_TRUE(heap.empty());
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, Erase) {
|
|
IntrusiveHeap<TestElement> heap;
|
|
|
|
HeapHandle index12;
|
|
|
|
heap.insert({15, nullptr});
|
|
heap.insert({14, nullptr});
|
|
heap.insert({13, nullptr});
|
|
heap.insert({12, &index12});
|
|
heap.insert({11, nullptr});
|
|
|
|
EXPECT_EQ(5u, heap.size());
|
|
EXPECT_TRUE(index12.IsValid());
|
|
heap.erase(index12);
|
|
EXPECT_EQ(4u, heap.size());
|
|
EXPECT_FALSE(index12.IsValid());
|
|
|
|
EXPECT_EQ(11, heap.top().key);
|
|
heap.pop();
|
|
EXPECT_EQ(13, heap.top().key);
|
|
heap.pop();
|
|
EXPECT_EQ(14, heap.top().key);
|
|
heap.pop();
|
|
EXPECT_EQ(15, heap.top().key);
|
|
heap.pop();
|
|
EXPECT_TRUE(heap.empty());
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, ReplaceTop) {
|
|
IntrusiveHeap<TestElement> heap;
|
|
|
|
for (int i = 0; i < 500; i++)
|
|
heap.insert({500 - i, nullptr});
|
|
|
|
EXPECT_EQ(1, heap.top().key);
|
|
|
|
for (int i = 0; i < 500; i++)
|
|
heap.ReplaceTop({1000 + i, nullptr});
|
|
|
|
EXPECT_EQ(1000, heap.top().key);
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, ReplaceTopWithNonLeafNode) {
|
|
IntrusiveHeap<TestElement> heap;
|
|
|
|
for (int i = 0; i < 50; i++) {
|
|
heap.insert({i, nullptr});
|
|
heap.insert({200 + i, nullptr});
|
|
}
|
|
|
|
EXPECT_EQ(0, heap.top().key);
|
|
|
|
for (int i = 0; i < 50; i++)
|
|
heap.ReplaceTop({100 + i, nullptr});
|
|
|
|
for (int i = 0; i < 50; i++) {
|
|
EXPECT_EQ((100 + i), heap.top().key);
|
|
heap.pop();
|
|
}
|
|
for (int i = 0; i < 50; i++) {
|
|
EXPECT_EQ((200 + i), heap.top().key);
|
|
heap.pop();
|
|
}
|
|
EXPECT_TRUE(heap.empty());
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, ReplaceTopCheckAllFinalPositions) {
|
|
HeapHandle index[100];
|
|
HeapHandle top_index;
|
|
|
|
for (int j = -1; j <= 201; j += 2) {
|
|
IntrusiveHeap<TestElement> heap;
|
|
for (size_t i = 0; i < 100; i++) {
|
|
heap.insert({static_cast<int>(i) * 2, &index[i]});
|
|
}
|
|
|
|
heap.ReplaceTop({j, &top_index});
|
|
|
|
int prev = -2;
|
|
while (!heap.empty()) {
|
|
DCHECK_GT(heap.top().key, prev);
|
|
DCHECK(heap.top().key == j || (heap.top().key % 2) == 0);
|
|
DCHECK_NE(heap.top().key, 0);
|
|
prev = heap.top().key;
|
|
heap.pop();
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, ReplaceUp) {
|
|
IntrusiveHeap<TestElement> heap;
|
|
HeapHandle index[10];
|
|
|
|
for (size_t i = 0; i < 10; i++) {
|
|
heap.insert({static_cast<int>(i) * 2, &index[i]});
|
|
}
|
|
|
|
heap.Replace(index[5], {17, &index[5]});
|
|
|
|
std::vector<int> results;
|
|
while (!heap.empty()) {
|
|
results.push_back(heap.top().key);
|
|
heap.pop();
|
|
}
|
|
|
|
EXPECT_THAT(results, testing::ElementsAre(0, 2, 4, 6, 8, 12, 14, 16, 17, 18));
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, ReplaceUpButDoesntMove) {
|
|
IntrusiveHeap<TestElement> heap;
|
|
HeapHandle index[10];
|
|
|
|
for (size_t i = 0; i < 10; i++) {
|
|
heap.insert({static_cast<int>(i) * 2, &index[i]});
|
|
}
|
|
|
|
heap.Replace(index[5], {11, &index[5]});
|
|
|
|
std::vector<int> results;
|
|
while (!heap.empty()) {
|
|
results.push_back(heap.top().key);
|
|
heap.pop();
|
|
}
|
|
|
|
EXPECT_THAT(results, testing::ElementsAre(0, 2, 4, 6, 8, 11, 12, 14, 16, 18));
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, ReplaceDown) {
|
|
IntrusiveHeap<TestElement> heap;
|
|
HeapHandle index[10];
|
|
|
|
for (size_t i = 0; i < 10; i++) {
|
|
heap.insert({static_cast<int>(i) * 2, &index[i]});
|
|
}
|
|
|
|
heap.Replace(index[5], {1, &index[5]});
|
|
|
|
std::vector<int> results;
|
|
while (!heap.empty()) {
|
|
results.push_back(heap.top().key);
|
|
heap.pop();
|
|
}
|
|
|
|
EXPECT_THAT(results, testing::ElementsAre(0, 1, 2, 4, 6, 8, 12, 14, 16, 18));
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, ReplaceDownButDoesntMove) {
|
|
IntrusiveHeap<TestElement> heap;
|
|
HeapHandle index[10];
|
|
|
|
for (size_t i = 0; i < 10; i++) {
|
|
heap.insert({static_cast<int>(i) * 2, &index[i]});
|
|
}
|
|
|
|
heap.Replace(index[5], {9, &index[5]});
|
|
|
|
std::vector<int> results;
|
|
while (!heap.empty()) {
|
|
results.push_back(heap.top().key);
|
|
heap.pop();
|
|
}
|
|
|
|
EXPECT_THAT(results, testing::ElementsAre(0, 2, 4, 6, 8, 9, 12, 14, 16, 18));
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, ReplaceCheckAllFinalPositions) {
|
|
HeapHandle index[100];
|
|
|
|
for (int j = -1; j <= 201; j += 2) {
|
|
IntrusiveHeap<TestElement> heap;
|
|
for (size_t i = 0; i < 100; i++) {
|
|
heap.insert({static_cast<int>(i) * 2, &index[i]});
|
|
}
|
|
|
|
heap.Replace(index[40], {j, &index[40]});
|
|
|
|
int prev = -2;
|
|
while (!heap.empty()) {
|
|
DCHECK_GT(heap.top().key, prev);
|
|
DCHECK(heap.top().key == j || (heap.top().key % 2) == 0);
|
|
DCHECK_NE(heap.top().key, 80);
|
|
prev = heap.top().key;
|
|
heap.pop();
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, At) {
|
|
HeapHandle index[10];
|
|
IntrusiveHeap<TestElement> heap;
|
|
|
|
for (int i = 0; i < 10; i++)
|
|
heap.insert({static_cast<int>(i ^ (i + 1)), &index[i]});
|
|
|
|
for (int i = 0; i < 10; i++) {
|
|
EXPECT_EQ(heap.at(index[i]).key, i ^ (i + 1));
|
|
EXPECT_EQ(heap.at(index[i]).handle, &index[i]);
|
|
}
|
|
}
|
|
|
|
bool IsEven(int i) {
|
|
return i % 2 == 0;
|
|
}
|
|
|
|
TEST(IntrusiveHeapTest, EraseIf) {
|
|
HeapHandle index[10];
|
|
IntrusiveHeap<TestElement> heap;
|
|
|
|
for (int i = 0; i < 10; i++)
|
|
heap.insert({i, &index[i]});
|
|
ASSERT_EQ(heap.size(), 10u);
|
|
|
|
// Remove all even elements.
|
|
heap.EraseIf([](const TestElement& element) { return IsEven(element.key); });
|
|
ASSERT_EQ(heap.size(), 5u);
|
|
|
|
// Handles were correctly updated.
|
|
for (int i = 0; i < 10; i++)
|
|
EXPECT_EQ(IsEven(i), !index[i].IsValid());
|
|
|
|
// Now iterate over all elements of the heap and check their handles.
|
|
for (size_t i = 0; i < heap.size(); i++) {
|
|
auto it = heap.begin();
|
|
std::advance(it, i);
|
|
|
|
// Retrieve the value of the element at this position.
|
|
int value = it->key;
|
|
|
|
// Its handle should have the correct index.
|
|
EXPECT_EQ(index[value].index(), i);
|
|
}
|
|
|
|
std::vector<int> results;
|
|
while (!heap.empty()) {
|
|
results.push_back(heap.top().key);
|
|
heap.pop();
|
|
}
|
|
|
|
EXPECT_THAT(results, testing::ElementsAre(1, 3, 5, 7, 9));
|
|
}
|
|
|
|
// A comparator class whose sole purpose is to allow the insertion of a
|
|
// ScopedClosureRunner inside the heap. The ordering does not matter.
|
|
class Comparator {
|
|
public:
|
|
bool operator()(const WithHeapHandle<ScopedClosureRunner>& lhs,
|
|
const WithHeapHandle<ScopedClosureRunner>& rhs) {
|
|
// Treat all closures as equal.
|
|
return true;
|
|
}
|
|
};
|
|
|
|
// Tests that inserting another element from the destructor of an object removed
|
|
// during EraseIf() doesn't crash.
|
|
TEST(IntrusiveHeapTest, EraseIf_Reentrancy) {
|
|
IntrusiveHeap<WithHeapHandle<ScopedClosureRunner>, Comparator> heap;
|
|
|
|
// The task that will post a new element inside the heap upon destruction of
|
|
// the first.
|
|
OnceClosure insert_task = BindLambdaForTesting([&]() {
|
|
// Insert a null callback so it can be differentiated.
|
|
heap.insert(OnceClosure());
|
|
});
|
|
heap.insert(ScopedClosureRunner(std::move(insert_task)));
|
|
|
|
// The heap contains the non-null closure.
|
|
EXPECT_EQ(heap.size(), 1u);
|
|
EXPECT_TRUE(heap.top().value());
|
|
|
|
// Erase the only element using EraseIf().
|
|
heap.EraseIf([](const auto& element) { return true; });
|
|
|
|
// Now the heap contains the null closure.
|
|
EXPECT_EQ(heap.size(), 1u);
|
|
EXPECT_FALSE(heap.top().value());
|
|
}
|
|
|
|
} // namespace base
|