blob: ec227193ad74de4e1565705d5e1ba23350349782 [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/base_test_environments.h>
#include <utility>
namespace fbl {
namespace tests {
namespace intrusive_containers {
// SequenceContainerTestEnvironment<>
//
// Test environment which defines and implements tests and test utilities which
// are applicable to all sequence containers such as lists.
template <typename TestEnvTraits>
class SequenceContainerTestEnvironment : public TestEnvironment<TestEnvTraits> {
public:
using ObjType = typename TestEnvTraits::ObjType;
using PtrType = typename TestEnvTraits::PtrType;
using ContainerTraits = typename ObjType::ContainerTraits;
using ContainerType = typename ContainerTraits::ContainerType;
using ContainerChecker = typename ContainerType::CheckerType;
using OtherContainerType = typename ContainerTraits::OtherContainerType;
using PtrTraits = typename ContainerType::PtrTraits;
using RefAction = typename TestEnvironment<TestEnvTraits>::RefAction;
using TestEnvironment<TestEnvTraits>::TakePtr;
// Utility method for checking the size of the container via either size()
// or size_slow(), depending on whether or not the container supports a
// constant order size operation.
template <typename CType>
static size_t Size(const CType& container) {
return SizeUtils<CType>::size(container);
}
bool Populate(ContainerType& container, RefAction ref_action = RefAction::HoldSome) override {
BEGIN_TEST;
EXPECT_EQ(0U, ObjType::live_obj_count(), "");
for (size_t i = 0; i < OBJ_COUNT; ++i) {
size_t ndx = OBJ_COUNT - i - 1;
EXPECT_EQ(i, Size(container), "");
// Unless explicitly told to do so, don't hold a reference in the
// test environment for every 4th object created. Note, this only
// affects RefPtr tests. Unmanaged pointers always hold an
// unmanaged copy of the pointer (so it can be cleaned up), while
// unique_ptr tests are not able to hold an extra copy of the
// pointer (because it is unique)
bool hold_ref;
switch (ref_action) {
case RefAction::HoldNone: hold_ref = false; break;
case RefAction::HoldSome: hold_ref = (i & 0x3); break;
case RefAction::HoldAll:
default:
hold_ref = true;
break;
}
PtrType new_object = this->CreateTrackedObject(ndx, ndx, hold_ref);
ASSERT_NONNULL(new_object, "");
EXPECT_EQ(new_object->raw_ptr(), objects()[ndx], "");
// Alternate whether or not we move the pointer, or "transfer" it.
// Transferring means different things for different pointer types.
// For unmanaged, it just returns a reference to the pointer and
// leaves the original unaltered. For unique, it moves the pointer
// (clearing the source). For RefPtr, it makes a new RefPtr
// instance, bumping the reference count in the process.
if (i & 1) {
#if TEST_WILL_NOT_COMPILE || 0
container.push_front(new_object);
#else
container.push_front(TestEnvTraits::Transfer(new_object));
#endif
EXPECT_TRUE(TestEnvTraits::WasTransferred(new_object), "");
} else {
container.push_front(std::move(new_object));
EXPECT_TRUE(TestEnvTraits::WasMoved(new_object), "");
}
}
EXPECT_EQ(OBJ_COUNT, Size(container), "");
EXPECT_EQ(OBJ_COUNT, ObjType::live_obj_count(), "");
EXPECT_TRUE(ContainerChecker::SanityCheck(container), "");
END_TEST;
}
bool PushFront() {
BEGIN_TEST;
EXPECT_TRUE(Populate(container()), "");
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations(0));
END_TEST;
}
bool PushBack() {
BEGIN_TEST;
EXPECT_EQ(0U, ObjType::live_obj_count(), "");
for (size_t i = 0; i < OBJ_COUNT; ++i) {
EXPECT_EQ(i, Size(container()), "");
PtrType new_object = this->CreateTrackedObject(i, i);
ASSERT_NONNULL(new_object, "");
EXPECT_EQ(new_object->raw_ptr(), objects()[i], "");
// Alternate whether or not we move the pointer, or "transfer" it.
if (i & 1) {
#if TEST_WILL_NOT_COMPILE || 0
container().push_back(new_object);
#else
container().push_back(TestEnvTraits::Transfer(new_object));
#endif
EXPECT_TRUE(TestEnvTraits::WasTransferred(new_object), "");
} else {
container().push_back(std::move(new_object));
EXPECT_TRUE(TestEnvTraits::WasMoved(new_object), "");
}
}
EXPECT_EQ(OBJ_COUNT, Size(container()), "");
EXPECT_EQ(OBJ_COUNT, ObjType::live_obj_count(), "");
size_t i = 0;
for (const auto& obj : container()) {
ASSERT_LT(i, OBJ_COUNT, "");
EXPECT_EQ(objects()[i]->value(), obj.value(), "");
EXPECT_EQ(objects()[i], obj.raw_ptr(), "");
i++;
}
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations(0));
END_TEST;
}
bool PopFront() {
BEGIN_TEST;
ASSERT_TRUE(Populate(container()), "");
// Remove elements using pop_front. List should shrink each time we
// remove an element, but the number of live objects should only shrink
// when we let the last reference go out of scope.
for (size_t i = 0; i < OBJ_COUNT; ++i) {
size_t remaining = OBJ_COUNT - i;
ASSERT_TRUE(!container().is_empty(), "");
EXPECT_EQ(remaining, ObjType::live_obj_count(), "");
EXPECT_EQ(remaining, Size(container()), "");
{
// Pop the item and sanity check it against our tracking.
PtrType tmp = container().pop_front();
EXPECT_NONNULL(tmp, "");
EXPECT_EQ(tmp->value(), i, "");
EXPECT_EQ(objects()[i], tmp->raw_ptr(), "");
// Make sure that the intrusive bookkeeping is up-to-date.
auto& ns = ContainerType::NodeTraits::node_state(*tmp);
EXPECT_NULL(ns.next_, "");
// The container has shrunk, but the object should still be around.
EXPECT_EQ(remaining, ObjType::live_obj_count(), "");
EXPECT_EQ(remaining - 1, Size(container()), "");
}
// If we were not holding onto the object using the test
// environment's tracking, the live object count should have
// dropped. Otherwise, it should remain the same.
if (!HoldingObject(i))
EXPECT_EQ(remaining - 1, ObjType::live_obj_count(), "");
else
EXPECT_EQ(remaining, ObjType::live_obj_count(), "");
// Let go of the object and verify that it has now gone away.
ReleaseObject(i);
EXPECT_EQ(remaining - 1, ObjType::live_obj_count(), "");
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations(i + 1));
}
// List should be empty now. Popping anything else should result in a
// null pointer.
EXPECT_TRUE(container().is_empty(), "");
PtrType should_be_null = container().pop_front();
EXPECT_NULL(should_be_null, "");
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations(OBJ_COUNT));
END_TEST;
}
bool PopBack() {
BEGIN_TEST;
ASSERT_TRUE(Populate(container()), "");
// Remove elements using pop_back. List should shrink each time we
// remove an element, but the number of live objects should only shrink
// when we let the last reference go out of scope.
for (size_t i = 0; i < OBJ_COUNT; ++i) {
size_t remaining = OBJ_COUNT - i;
size_t obj_ndx = OBJ_COUNT - i - 1;
ASSERT_TRUE(!container().is_empty(), "");
EXPECT_EQ(remaining, ObjType::live_obj_count(), "");
EXPECT_EQ(remaining, Size(container()), "");
{
// Pop the item and sanity check it against our tracking.
PtrType tmp = container().pop_back();
EXPECT_NONNULL(tmp, "");
EXPECT_EQ(tmp->value(), obj_ndx, "");
EXPECT_EQ(objects()[obj_ndx], tmp->raw_ptr(), "");
// Make sure that the intrusive bookkeeping is up-to-date.
auto& ns = ContainerType::NodeTraits::node_state(*tmp);
EXPECT_NULL(ns.next_, "");
// The container has shrunk, but the object should still be around.
EXPECT_EQ(remaining, ObjType::live_obj_count(), "");
EXPECT_EQ(remaining - 1, Size(container()), "");
}
// If we were not holding onto the object using the test
// environment's tracking, the live object count should have
// dropped. Otherwise, it should remain the same.
if (!HoldingObject(obj_ndx))
EXPECT_EQ(remaining - 1, ObjType::live_obj_count(), "");
else
EXPECT_EQ(remaining, ObjType::live_obj_count(), "");
// Let go of the object and verify that it has now gone away.
ReleaseObject(obj_ndx);
EXPECT_EQ(remaining - 1, ObjType::live_obj_count(), "");
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations(i + 1));
}
// List should be empty now. Popping anything else should result in a
// null pointer.
EXPECT_TRUE(container().is_empty(), "");
PtrType should_be_null = container().pop_back();
EXPECT_NULL(should_be_null, "");
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations(OBJ_COUNT));
END_TEST;
}
bool EraseNext() {
BEGIN_TEST;
ASSERT_TRUE(Populate(container()), "");
// Remove as many elements as we can using erase_next.
auto iter = container().begin();
for (size_t i = 1; i < OBJ_COUNT; ++i) {
size_t remaining = OBJ_COUNT - i + 1;
ASSERT_TRUE(!container().is_empty(), "");
ASSERT_TRUE(iter != container().end(), "");
EXPECT_EQ(remaining, ObjType::live_obj_count(), "");
EXPECT_EQ(remaining, Size(container()), "");
{
// Erase the item and sanity check it against our tracking.
PtrType tmp = container().erase_next(iter);
EXPECT_NONNULL(tmp, "");
EXPECT_EQ(tmp->value(), i, "");
EXPECT_EQ(objects()[i], tmp->raw_ptr(), "");
// Make sure that the intrusive bookkeeping is up-to-date.
auto& ns = ContainerType::NodeTraits::node_state(*tmp);
EXPECT_TRUE(ns.IsValid(), "");
EXPECT_FALSE(ns.InContainer(), "");
// The container has shrunk, but the object should still be around.
EXPECT_EQ(remaining, ObjType::live_obj_count(), "");
EXPECT_EQ(remaining - 1, Size(container()), "");
}
// If we were not holding onto the object using the test
// environment's tracking, the live object count should have
// dropped. Otherwise, it should remain the same.
if (!HoldingObject(i))
EXPECT_EQ(remaining - 1, ObjType::live_obj_count(), "");
else
EXPECT_EQ(remaining, ObjType::live_obj_count(), "");
// Let go of the object and verify that it has now gone away.
ReleaseObject(i);
EXPECT_EQ(remaining - 1, ObjType::live_obj_count(), "");
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations(i));
}
// Iterator should now be one away from the end, and there should be one
// object left
EXPECT_EQ(1u, ObjType::live_obj_count(), "");
EXPECT_EQ(1u, Size(container()), "");
EXPECT_TRUE(iter != container().end(), "");
iter++;
EXPECT_TRUE(iter == container().end(), "");
// Attempt to erase the element after the final element. This should
// fail, and indicate that it has failed by returning null.
iter = container().begin();
PtrType tmp = container().erase_next(iter);
EXPECT_NULL(tmp, "");
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations(OBJ_COUNT - 1));
END_TEST;
}
template <typename IterType>
bool DoInsertAfter(IterType&& iter, size_t pos) {
BEGIN_TEST;
EXPECT_EQ(ObjType::live_obj_count(), Size(container()), "");
EXPECT_TRUE(iter != container().end(), "");
size_t orig_container_len = ObjType::live_obj_count();
size_t orig_iter_pos = iter->value();
ASSERT_LT(orig_iter_pos, OBJ_COUNT, "");
EXPECT_EQ(objects()[orig_iter_pos], iter->raw_ptr(), "");
PtrType new_object = this->CreateTrackedObject(pos, pos, true);
ASSERT_NONNULL(new_object, "");
EXPECT_EQ(new_object->raw_ptr(), objects()[pos], "");
if (pos & 1) {
#if TEST_WILL_NOT_COMPILE || 0
container().insert_after(iter, new_object);
#else
container().insert_after(iter, TestEnvTraits::Transfer(new_object));
#endif
EXPECT_TRUE(TestEnvTraits::WasTransferred(new_object), "");
} else {
container().insert_after(iter, std::move(new_object));
EXPECT_TRUE(TestEnvTraits::WasMoved(new_object), "");
}
// List and number of live object should have grown.
EXPECT_EQ(orig_container_len + 1, ObjType::live_obj_count(), "");
EXPECT_EQ(orig_container_len + 1, Size(container()), "");
// The iterator should not have moved yet.
EXPECT_TRUE(iter != container().end(), "");
EXPECT_EQ(objects()[orig_iter_pos], iter->raw_ptr(), "");
EXPECT_EQ(orig_iter_pos, iter->value(), "");
// This test should delete no objects
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations(0));
END_TEST;
}
bool InsertAfter() {
BEGIN_TEST;
// In order to insert_after, we need at least one object already in the
// container. Use push_front to make one.
EXPECT_EQ(0u, ObjType::live_obj_count(), "");
EXPECT_EQ(0U, Size(container()), "");
EXPECT_TRUE(container().is_empty(), "");
container().push_front(std::move(this->CreateTrackedObject(0, 0, true)));
// Insert some elements after the last element container.
static constexpr size_t END_INSERT_COUNT = 2;
static_assert(END_INSERT_COUNT <= OBJ_COUNT,
"OBJ_COUNT too small to run InsertAfter test!");
auto iter = container().begin();
for (size_t i = (OBJ_COUNT - END_INSERT_COUNT); i < OBJ_COUNT; ++i) {
ASSERT_TRUE(DoInsertAfter(iter, i), "");
// Now that we have inserted after, we should be able to advance the
// iterator to what we just inserted.
iter++;
ASSERT_TRUE(iter != container().end(), "");
EXPECT_EQ(objects()[i], iter->raw_ptr(), "");
EXPECT_EQ(objects()[i], (*iter).raw_ptr(), "");
EXPECT_EQ(i, iter->value(), "");
EXPECT_EQ(i, (*iter).value(), "");
}
// Advancing iter at this point should bring it to the end.
EXPECT_TRUE(iter != container().end(), "");
iter++;
EXPECT_TRUE(iter == container().end(), "");
// Reset the iterator to the first element in the container, and test
// inserting between elements instead of at the end. To keep the
// final container in order, we need to insert in reverse order and to not
// advance the iterator in the process.
iter = container().begin();
for (size_t i = (OBJ_COUNT - END_INSERT_COUNT - 1); i > 0; --i) {
ASSERT_TRUE(DoInsertAfter(iter, i), "");
}
EXPECT_TRUE(iter != container().end(), "");
// Check to make sure the container has the expected number of elements, and
// that they are in the proper order.
EXPECT_EQ(OBJ_COUNT, ObjType::live_obj_count(), "");
EXPECT_EQ(OBJ_COUNT, Size(container()), "");
size_t i = 0;
for (const auto& obj : container()) {
EXPECT_EQ(objects()[i], &obj, "");
EXPECT_EQ(objects()[i], obj.raw_ptr(), "");
EXPECT_EQ(i, obj.value(), "");
i++;
}
// This test should delete no objects
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations(0));
END_TEST;
}
template <typename TargetType>
bool DoInsert(TargetType&& target, size_t pos) {
BEGIN_TEST;
EXPECT_EQ(ObjType::live_obj_count(), Size(container()), "");
size_t orig_container_len = ObjType::live_obj_count();
PtrType new_object = this->CreateTrackedObject(pos, pos, true);
ASSERT_NONNULL(new_object, "");
EXPECT_EQ(new_object->raw_ptr(), objects()[pos], "");
if (pos & 1) {
#if TEST_WILL_NOT_COMPILE || 0
container().insert(target, new_object);
#else
container().insert(target, TestEnvTraits::Transfer(new_object));
#endif
EXPECT_TRUE(TestEnvTraits::WasTransferred(new_object), "");
} else {
container().insert(target, std::move(new_object));
EXPECT_TRUE(TestEnvTraits::WasMoved(new_object), "");
}
// List and number of live object should have grown.
EXPECT_EQ(orig_container_len + 1, ObjType::live_obj_count(), "");
EXPECT_EQ(orig_container_len + 1, Size(container()), "");
END_TEST;
}
bool Insert() {
BEGIN_TEST;
EXPECT_EQ(0u, ObjType::live_obj_count(), "");
EXPECT_EQ(0U, Size(container()), "");
static constexpr size_t END_INSERT_COUNT = 3;
static constexpr size_t START_INSERT_COUNT = 3;
static constexpr size_t MID_INSERT_COUNT = OBJ_COUNT
- START_INSERT_COUNT - END_INSERT_COUNT;
static_assert((END_INSERT_COUNT <= OBJ_COUNT) &&
(START_INSERT_COUNT <= (OBJ_COUNT - END_INSERT_COUNT)) &&
((START_INSERT_COUNT + END_INSERT_COUNT) < OBJ_COUNT),
"OBJ_COUNT too small to run Insert test!");
// Insert some elements at the end of an initially empty container using the
// end() iterator accessor.
for (size_t i = (OBJ_COUNT - END_INSERT_COUNT); i < OBJ_COUNT; ++i)
ASSERT_TRUE(DoInsert(container().end(), i), "");
// Insert some elements at the start of a non-empty container using the
// begin() iterator accessor.
for (size_t i = 0; i < START_INSERT_COUNT; ++i) {
size_t ndx = START_INSERT_COUNT - i - 1;
ASSERT_TRUE(DoInsert(container().begin(), ndx), "");
}
// Insert some elements in the middle non-empty container using an iterator
// we compute.
auto iter = container().begin();
for (size_t i = 0; i < START_INSERT_COUNT; ++i)
++iter;
for (size_t i = 0; i < MID_INSERT_COUNT; ++i) {
size_t ndx = START_INSERT_COUNT + i;
ASSERT_TRUE(DoInsert(iter, ndx), "");
}
// iter should be END_INSERT_COUNT from the end of the
// container.
for (size_t i = 0; i < END_INSERT_COUNT; ++i) {
EXPECT_TRUE(iter != container().end(), "");
++iter;
}
EXPECT_TRUE(++iter == container().end(), "");
// Check to make sure the container has the expected number of elements, and
// that they are in the proper order.
EXPECT_EQ(OBJ_COUNT, ObjType::live_obj_count(), "");
EXPECT_EQ(OBJ_COUNT, Size(container()), "");
size_t i = 0;
for (const auto& obj : container()) {
ASSERT_LT(i, OBJ_COUNT, "");
EXPECT_EQ(objects()[i], &obj, "");
EXPECT_EQ(objects()[i], obj.raw_ptr(), "");
EXPECT_EQ(i, obj.value(), "");
i++;
}
// This test should delete no objects
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations(0));
END_TEST;
}
bool DirectInsert() {
BEGIN_TEST;
EXPECT_EQ(0u, ObjType::live_obj_count(), "");
EXPECT_EQ(0U, Size(container()), "");
static constexpr size_t END_INSERT_COUNT = 3;
static constexpr size_t START_INSERT_COUNT = 3;
static constexpr size_t MID_INSERT_COUNT = OBJ_COUNT
- START_INSERT_COUNT - END_INSERT_COUNT;
static_assert((END_INSERT_COUNT <= OBJ_COUNT) &&
(START_INSERT_COUNT <= (OBJ_COUNT - END_INSERT_COUNT)) &&
((START_INSERT_COUNT + END_INSERT_COUNT) < OBJ_COUNT),
"OBJ_COUNT too small to run DirectInsert test!");
// Insert some elements at the end of an initially empty container using
// the end() iterator as the target.
for (size_t i = (OBJ_COUNT - END_INSERT_COUNT); i < OBJ_COUNT; ++i)
ASSERT_TRUE(DoInsert(container().end(), i), "");
// Insert some elements at the start of a non-empty container node
// pointers which are always at the start of the container.
size_t insert_before_ndx = (OBJ_COUNT - END_INSERT_COUNT);
for (size_t i = 0; i < START_INSERT_COUNT; ++i) {
size_t ndx = START_INSERT_COUNT - i - 1;
ASSERT_NONNULL(objects()[insert_before_ndx], "");
ASSERT_TRUE(DoInsert(*objects()[insert_before_ndx], ndx), "");
insert_before_ndx = ndx;
}
// Insert some elements in the middle non-empty container.
insert_before_ndx = (OBJ_COUNT - END_INSERT_COUNT);
for (size_t i = 0; i < MID_INSERT_COUNT; ++i) {
size_t ndx = START_INSERT_COUNT + i;
ASSERT_NONNULL(objects()[insert_before_ndx], "");
ASSERT_TRUE(DoInsert(*objects()[insert_before_ndx], ndx), "");
}
// Check to make sure the container has the expected number of elements,
// and that they are in the proper order.
EXPECT_EQ(OBJ_COUNT, ObjType::live_obj_count(), "");
EXPECT_EQ(OBJ_COUNT, Size(container()), "");
size_t i = 0;
for (const auto& obj : container()) {
ASSERT_LT(i, OBJ_COUNT, "");
EXPECT_EQ(objects()[i], &obj, "");
EXPECT_EQ(objects()[i], obj.raw_ptr(), "");
EXPECT_EQ(i, obj.value(), "");
i++;
}
// This test should delete no objects
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations(0));
END_TEST;
}
bool FillN(size_t begin, size_t end) {
BEGIN_TEST;
for (size_t i = begin; i < end; ++i) {
PtrType new_object = this->CreateTrackedObject(i, i);
ASSERT_NONNULL(new_object, "");
container().push_back(std::move(new_object));
EXPECT_TRUE(TestEnvTraits::WasMoved(new_object), "");
}
END_TEST;
}
bool BidirectionalEquals(const ContainerType& sequence, const size_t* values, size_t size) {
BEGIN_TEST;
// We use SupportsConstantOrderErase as a way to discriminate a singly-
// linked list from a doubly-linked list.
static_assert(ContainerType::IsSequenced && ContainerType::SupportsConstantOrderErase,
"BidirectionalEquals must be used with a bi-directional sequence");
ASSERT_EQ(size, Size(sequence), "");
// Iterate forwards and verify values.
const size_t* val_iter = values;
for (const auto& node : sequence) {
EXPECT_EQ(node.value(), *val_iter, "");
++val_iter;
}
// Iterate backwards and verify values.
for (auto begin = sequence.begin(), end = sequence.end(); begin != end;) {
--val_iter;
--end;
EXPECT_EQ(end->value(), *val_iter, "");
}
// This test should delete no objects
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations(0));
END_TEST;
}
bool Splice() {
BEGIN_TEST;
constexpr size_t LIST_COUNT = 2;
static_assert(LIST_COUNT * 4 < OBJ_COUNT,
"OBJ_COUNT too small to run Splice test!");
EXPECT_EQ(0U, Size(container()), "");
ContainerType target;
EXPECT_EQ(0U, Size(target), "");
// Splice empty source into end of empty target list.
target.splice(target.end(), container());
EXPECT_EQ(0U, Size(target), "");
EXPECT_EQ(0u, Size(container()), "");
// Populate the source list.
ASSERT_TRUE(FillN(0, LIST_COUNT), "");
EXPECT_EQ(LIST_COUNT, Size(container()), "");
// Splice into end of empty target list.
target.splice(target.end(), container());
static const size_t expected_1[] = {0, 1};
EXPECT_TRUE(BidirectionalEquals(target, expected_1, fbl::count_of(expected_1)), "");
EXPECT_EQ(0u, Size(container()), "");
// Populate the source list again.
ASSERT_TRUE(FillN(LIST_COUNT, LIST_COUNT * 2), "");
EXPECT_EQ(LIST_COUNT, Size(container()), "");
// Splice into end of non-empty target list.
target.splice(target.end(), container());
static const size_t expected_2[] = {0, 1, 2, 3};
EXPECT_TRUE(BidirectionalEquals(target, expected_2, fbl::count_of(expected_2)), "");
EXPECT_EQ(0u, Size(container()), "");
// Populate the source list again.
ASSERT_TRUE(FillN(LIST_COUNT * 2, LIST_COUNT * 3), "");
EXPECT_EQ(LIST_COUNT, Size(container()), "");
// Splice into start of non-empty target list.
target.splice(target.begin(), container());
static const size_t expected_3[] = {4, 5, 0, 1, 2, 3};
EXPECT_TRUE(BidirectionalEquals(target, expected_3, fbl::count_of(expected_3)), "");
EXPECT_EQ(0u, Size(container()), "");
// Populate the source list again.
ASSERT_TRUE(FillN(LIST_COUNT * 3, LIST_COUNT * 4), "");
EXPECT_EQ(LIST_COUNT, Size(container()), "");
// Splice into second element of non-empty target list.
target.splice(++target.begin(), container());
static const size_t expected_4[] = {4, 6, 7, 5, 0, 1, 2, 3};
EXPECT_TRUE(BidirectionalEquals(target, expected_4, fbl::count_of(expected_4)), "");
EXPECT_EQ(0u, Size(container()), "");
// Splice empty source into end of non-empty target list.
target.splice(target.end(), container());
EXPECT_TRUE(BidirectionalEquals(target, expected_4, fbl::count_of(expected_4)), "");
EXPECT_EQ(0u, Size(container()), "");
// No objects should have been deleted yet.
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations(0));
// Finally clear the target.
target.clear();
EXPECT_EQ(0u, Size(target), "");
// By now, we should have created LIST_COUNT * 4 objects. They should all be gone now.
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations(LIST_COUNT * 4));
END_TEST;
}
template <typename IterType>
bool DoSeqIterate(const IterType& begin, const IterType& end) {
BEGIN_TEST;
IterType iter;
// begin() should point to the front of the sequence.
iter = begin;
ASSERT_TRUE(iter.IsValid(), "");
EXPECT_TRUE(container().front() == *iter, "");
// Iterate using begin/end
size_t i = 0;
for (iter = begin; iter != end; ) {
// Exercise both -> and * dereferencing
ASSERT_TRUE(iter.IsValid(), "");
EXPECT_EQ(objects()[i], iter->raw_ptr(), "");
EXPECT_EQ(objects()[i], (*iter).raw_ptr(), "");
EXPECT_EQ(i, iter->value(), "");
EXPECT_EQ(i, (*iter).value(), "");
// Exercise both pre and postfix increment
if ((i++) & 1) iter++;
else ++iter;
}
EXPECT_FALSE(iter.IsValid(), "");
END_TEST;
}
bool SeqIterate() {
BEGIN_TEST;
// Start by making some objects.
ASSERT_TRUE(Populate(container()), "");
EXPECT_EQ(OBJ_COUNT, Size(container()), "");
// Test iterator
EXPECT_TRUE(DoSeqIterate(container().begin(), container().end()), "");
// Test const_iterator
EXPECT_TRUE(DoSeqIterate(container().cbegin(), container().cend()), "");
// Iterate using the range-based for loop syntax
size_t i = 0;
for (auto& obj : container()) {
EXPECT_EQ(objects()[i], &obj, "");
EXPECT_EQ(objects()[i], obj.raw_ptr(), "");
EXPECT_EQ(i, obj.value(), "");
i++;
}
// Iterate using the range-based for loop syntax over const references.
i = 0;
for (const auto& obj : container()) {
EXPECT_EQ(objects()[i], &obj, "");
EXPECT_EQ(objects()[i], obj.raw_ptr(), "");
EXPECT_EQ(i, obj.value(), "");
i++;
}
// This test should delete no objects
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations(0));
END_TEST;
}
template <typename IterType>
bool DoSeqReverseIterate(const IterType& begin, const IterType& end) {
BEGIN_TEST;
IterType iter;
// Backing up one from end() should give us back(). Check both pre
// and post-fix behavior.
iter = end; --iter;
ASSERT_TRUE(iter.IsValid(), "");
ASSERT_TRUE(iter != end, "");
EXPECT_TRUE(container().back() == *iter, "");
iter = end; iter--;
ASSERT_TRUE(iter.IsValid(), "");
ASSERT_TRUE(iter != end, "");
EXPECT_TRUE(container().back() == *iter, "");
// Make sure that backing up an iterator by one points always points
// to the previous object in the container.
iter = begin;
while (++iter != end) {
size_t prev_ndx = iter->value() - 1;
ASSERT_LT(prev_ndx, OBJ_COUNT, "");
ASSERT_NONNULL(objects()[prev_ndx], "");
auto prev_iter = iter;
--prev_iter;
ASSERT_TRUE(prev_iter.IsValid(), "");
EXPECT_FALSE(prev_iter == iter, "");
EXPECT_TRUE(*prev_iter == *objects()[prev_ndx], "");
prev_iter = iter;
prev_iter--;
ASSERT_TRUE(prev_iter.IsValid(), "");
EXPECT_FALSE(prev_iter == iter, "");
EXPECT_TRUE(*prev_iter == *objects()[prev_ndx], "");
}
// This test should delete no objects
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations(0));
END_TEST;
}
bool SeqReverseIterate() {
BEGIN_TEST;
// Start by making some objects.
ASSERT_TRUE(Populate(container()), "");
EXPECT_EQ(OBJ_COUNT, Size(container()), "");
// Test iterator
EXPECT_TRUE(DoSeqReverseIterate(container().begin(), container().end()), "");
// Test const_iterator
EXPECT_TRUE(DoSeqReverseIterate(container().cbegin(), container().cend()), "");
// This test should delete no objects
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations(0));
END_TEST;
}
bool ReplaceIfCopy() {
BEGIN_TEST;
EXPECT_TRUE(ContainerChecker::SanityCheck(container()), "");
// Try (and fail) to replace an element in an empty container.
{
PtrType new_obj = TestEnvTraits::CreateObject(0);
auto raw_obj = PtrTraits::GetRaw(new_obj);
ASSERT_NONNULL(new_obj, "");
EXPECT_EQ(0, Size(container()));
EXPECT_EQ(1, ObjType::live_obj_count(), "");
PtrType replaced = container().replace_if(
[](const ObjType& obj) -> bool { return true; },
new_obj);
EXPECT_TRUE(ContainerChecker::SanityCheck(container()), "");
ASSERT_NONNULL(new_obj, "");
EXPECT_NULL(replaced, "");
EXPECT_EQ(raw_obj, PtrTraits::GetRaw(new_obj));
EXPECT_EQ(0, Size(container()));
EXPECT_EQ(1, ObjType::live_obj_count(), "");
TestEnvTraits::ReleaseObject(new_obj);
EXPECT_EQ(0, Size(container()));
EXPECT_EQ(0, ObjType::live_obj_count(), "");
}
// The object which we create in an attempt to replace an object in an
// empty container should be gone now.
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations(1));
// Populate our container.
for (size_t i = 0; i < OBJ_COUNT; ++i) {
EXPECT_EQ(i, ObjType::live_obj_count(), "");
PtrType new_obj = TestEnvTraits::CreateObject(OBJ_COUNT - i - 1);
ASSERT_NONNULL(new_obj, "");
EXPECT_EQ(i + 1, ObjType::live_obj_count(), "");
container().push_front(std::move(new_obj));
}
EXPECT_TRUE(ContainerChecker::SanityCheck(container()), "");
// Replace all of the members of the contianer with new members which
// have a value never created during the populate phase.
for (size_t i = 0; i < OBJ_COUNT; ++i) {
EXPECT_EQ(OBJ_COUNT, ObjType::live_obj_count(), "");
PtrType new_obj = TestEnvTraits::CreateObject(OBJ_COUNT + i);
ASSERT_NONNULL(new_obj, "");
PtrType replaced = container().replace_if(
[i](const ObjType& obj) -> bool {
return (obj.value() == i);
}, new_obj);
EXPECT_TRUE(ContainerChecker::SanityCheck(container()), "");
ASSERT_NONNULL(replaced, "");
EXPECT_TRUE(new_obj->InContainer());
EXPECT_FALSE(replaced->InContainer());
EXPECT_EQ(i, replaced->value());
EXPECT_EQ(OBJ_COUNT + 1, ObjType::live_obj_count(), "");
TestEnvTraits::ReleaseObject(replaced);
EXPECT_EQ(OBJ_COUNT, ObjType::live_obj_count(), "");
}
// All of the replaced objects should have gone away now too.
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations(OBJ_COUNT + 1));
// Try again, but this time fail each time (since all of the original
// element values have already been replaced).
for (size_t i = 0; i < OBJ_COUNT; ++i) {
EXPECT_EQ(OBJ_COUNT, ObjType::live_obj_count(), "");
PtrType new_obj = TestEnvTraits::CreateObject(OBJ_COUNT + (2 * i));
ASSERT_NONNULL(new_obj, "");
PtrType replaced = container().replace_if(
[i](const ObjType& obj) -> bool {
return (obj.value() == i);
}, new_obj);
EXPECT_TRUE(ContainerChecker::SanityCheck(container()), "");
ASSERT_NULL(replaced, "");
EXPECT_FALSE(new_obj->InContainer());
EXPECT_EQ(OBJ_COUNT + 1, ObjType::live_obj_count(), "");
TestEnvTraits::ReleaseObject(new_obj);
EXPECT_EQ(OBJ_COUNT, ObjType::live_obj_count(), "");
}
// The new objects we created (but failed to replace in the container) should now be gone.
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations((2 * OBJ_COUNT) + 1));
// Make sure that the object are in order and have the values we expect.
size_t i = 0;
while (!container().is_empty()) {
PtrType ptr = container().pop_front();
EXPECT_EQ(OBJ_COUNT + i, ptr->value(), "");
TestEnvTraits::ReleaseObject(ptr);
++i;
}
EXPECT_EQ(0, ObjType::live_obj_count(), "");
EXPECT_TRUE(ContainerChecker::SanityCheck(container()), "");
// Now all of the objects we created during the test should be gone.
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations((3 * OBJ_COUNT) + 1));
END_TEST;
}
bool ReplaceIfMove() {
BEGIN_TEST;
// Try (and fail) to replace an element in an empty container.
{
PtrType new_obj = TestEnvTraits::CreateObject(0);
auto raw_obj = PtrTraits::GetRaw(new_obj);
ASSERT_NONNULL(new_obj, "");
EXPECT_EQ(0, Size(container()));
EXPECT_EQ(1, ObjType::live_obj_count(), "");
PtrType replaced = container().replace_if(
[](const ObjType& obj) -> bool { return true; },
TakePtr(new_obj));
EXPECT_NULL(new_obj, "");
ASSERT_NONNULL(replaced, "");
EXPECT_EQ(raw_obj, PtrTraits::GetRaw(replaced));
EXPECT_EQ(0, Size(container()));
EXPECT_EQ(1, ObjType::live_obj_count(), "");
TestEnvTraits::ReleaseObject(replaced);
EXPECT_EQ(0, Size(container()));
EXPECT_EQ(0, ObjType::live_obj_count(), "");
}
// The object which we create in an attempt to replace an object in an
// empty container should be gone now.
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations(1));
// Populate our container.
for (size_t i = 0; i < OBJ_COUNT; ++i) {
EXPECT_EQ(i, ObjType::live_obj_count(), "");
PtrType new_obj = TestEnvTraits::CreateObject(OBJ_COUNT - i - 1);
ASSERT_NONNULL(new_obj, "");
EXPECT_EQ(i + 1, ObjType::live_obj_count(), "");
container().push_front(std::move(new_obj));
}
// Replace all of the members of the contianer with new members which
// have a value never created during Populate().
for (size_t i = 0; i < OBJ_COUNT; ++i) {
EXPECT_EQ(OBJ_COUNT, ObjType::live_obj_count(), "");
PtrType new_obj = TestEnvTraits::CreateObject(OBJ_COUNT + i);
ASSERT_NONNULL(new_obj, "");
PtrType replaced = container().replace_if(
[i](const ObjType& obj) -> bool {
return (obj.value() == i);
}, TakePtr(new_obj));
EXPECT_NULL(new_obj, "");
ASSERT_NONNULL(replaced, "");
EXPECT_FALSE(replaced->InContainer());
EXPECT_EQ(i, replaced->value());
EXPECT_EQ(OBJ_COUNT + 1, ObjType::live_obj_count(), "");
TestEnvTraits::ReleaseObject(replaced);
EXPECT_EQ(OBJ_COUNT, ObjType::live_obj_count(), "");
}
// All of the replaced objects should have gone away now too.
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations(OBJ_COUNT + 1));
// Try again, but this time fail each time (since all of the original
// element values have already been replaced).
for (size_t i = 0; i < OBJ_COUNT; ++i) {
EXPECT_EQ(OBJ_COUNT, ObjType::live_obj_count(), "");
PtrType new_obj = TestEnvTraits::CreateObject(OBJ_COUNT + (2 * i));
ASSERT_NONNULL(new_obj, "");
auto orig_raw = PtrTraits::GetRaw(new_obj);
PtrType replaced = container().replace_if(
[i](const ObjType& obj) -> bool {
return (obj.value() == i);
}, TakePtr(new_obj));
EXPECT_NULL(new_obj, "");
ASSERT_NONNULL(replaced, "");
EXPECT_EQ(PtrTraits::GetRaw(replaced), orig_raw, "");
EXPECT_FALSE(replaced->InContainer());
EXPECT_EQ(OBJ_COUNT + (2 * i), replaced->value());
EXPECT_EQ(OBJ_COUNT + 1, ObjType::live_obj_count(), "");
TestEnvTraits::ReleaseObject(replaced);
EXPECT_EQ(OBJ_COUNT, ObjType::live_obj_count(), "");
}
// The new objects we created (but failed to replace in the container) should now be gone.
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations((2 * OBJ_COUNT) + 1));
// Make sure that the object are in order and have the values we expect.
size_t i = 0;
while (!container().is_empty()) {
PtrType ptr = container().pop_front();
EXPECT_EQ(OBJ_COUNT + i, ptr->value(), "");
TestEnvTraits::ReleaseObject(ptr);
++i;
}
EXPECT_EQ(0, ObjType::live_obj_count(), "");
// Now all of the objects we created during the test should be gone.
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations((3 * OBJ_COUNT) + 1));
END_TEST;
}
bool ReplaceCopy() {
BEGIN_TEST;
EXPECT_TRUE(ContainerChecker::SanityCheck(container()), "");
// Populate our container.
for (size_t i = 0; i < OBJ_COUNT; ++i) {
EXPECT_EQ(i, ObjType::live_obj_count(), "");
PtrType new_obj = TestEnvTraits::CreateObject(OBJ_COUNT - i - 1);
ASSERT_NONNULL(new_obj, "");
EXPECT_EQ(i + 1, ObjType::live_obj_count(), "");
container().push_front(std::move(new_obj));
}
EXPECT_TRUE(ContainerChecker::SanityCheck(container()), "");
// Replace all of the members of the contianer with new members which
// have a value never created during the populate phase.
for (size_t i = 0; i < OBJ_COUNT; ++i) {
EXPECT_EQ(OBJ_COUNT, ObjType::live_obj_count(), "");
PtrType new_obj = TestEnvTraits::CreateObject(OBJ_COUNT + i);
ASSERT_NONNULL(new_obj, "");
auto iter = container().find_if(
[i](const ObjType& obj) -> bool {
return (obj.value() == i);
});
ASSERT_TRUE(iter.IsValid(), "");
EXPECT_EQ(i, iter->value(), "");
PtrType replaced = container().replace(*iter, new_obj);
EXPECT_TRUE(ContainerChecker::SanityCheck(container()), "");
ASSERT_NONNULL(replaced, "");
EXPECT_TRUE(new_obj->InContainer());
EXPECT_FALSE(replaced->InContainer());
EXPECT_EQ(i, replaced->value());
EXPECT_EQ(OBJ_COUNT + 1, ObjType::live_obj_count(), "");
TestEnvTraits::ReleaseObject(replaced);
EXPECT_EQ(OBJ_COUNT, ObjType::live_obj_count(), "");
}
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations(OBJ_COUNT));
// Make sure that the object are in order and have the values we expect.
size_t i = 0;
while (!container().is_empty()) {
PtrType ptr = container().pop_front();
EXPECT_EQ(OBJ_COUNT + i, ptr->value(), "");
TestEnvTraits::ReleaseObject(ptr);
++i;
}
EXPECT_EQ(0, ObjType::live_obj_count(), "");
EXPECT_TRUE(ContainerChecker::SanityCheck(container()), "");
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations(2 * OBJ_COUNT));
END_TEST;
}
bool ReplaceMove() {
BEGIN_TEST;
// Populate our container.
for (size_t i = 0; i < OBJ_COUNT; ++i) {
EXPECT_EQ(i, ObjType::live_obj_count(), "");
PtrType new_obj = TestEnvTraits::CreateObject(OBJ_COUNT - i - 1);
ASSERT_NONNULL(new_obj, "");
EXPECT_EQ(i + 1, ObjType::live_obj_count(), "");
container().push_front(std::move(new_obj));
}
// Replace all of the members of the contianer with new members which
// have a value never created during Populate().
for (size_t i = 0; i < OBJ_COUNT; ++i) {
EXPECT_EQ(OBJ_COUNT, ObjType::live_obj_count(), "");
PtrType new_obj = TestEnvTraits::CreateObject(OBJ_COUNT + i);
ASSERT_NONNULL(new_obj, "");
auto iter = container().find_if(
[i](const ObjType& obj) -> bool {
return (obj.value() == i);
});
ASSERT_TRUE(iter.IsValid(), "");
EXPECT_EQ(i, iter->value(), "");
PtrType replaced = container().replace(*iter, TakePtr(new_obj));
EXPECT_NULL(new_obj, "");
ASSERT_NONNULL(replaced, "");
EXPECT_FALSE(replaced->InContainer());
EXPECT_EQ(i, replaced->value());
EXPECT_EQ(OBJ_COUNT + 1, ObjType::live_obj_count(), "");
TestEnvTraits::ReleaseObject(replaced);
EXPECT_EQ(OBJ_COUNT, ObjType::live_obj_count(), "");
}
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations(OBJ_COUNT));
// Make sure that the object are in order and have the values we expect.
size_t i = 0;
while (!container().is_empty()) {
PtrType ptr = container().pop_front();
EXPECT_EQ(OBJ_COUNT + i, ptr->value(), "");
TestEnvTraits::ReleaseObject(ptr);
++i;
}
EXPECT_EQ(0, ObjType::live_obj_count(), "");
EXPECT_TRUE(TestEnvTraits::CheckCustomDeleteInvocations(2 * OBJ_COUNT));
END_TEST;
}
private:
// Accessors for base class members so we don't have to type
// this->base_member all of the time.
using Sp = TestEnvironmentSpecialized<TestEnvTraits>;
using Base = TestEnvironmentBase<TestEnvTraits>;
static constexpr size_t OBJ_COUNT = Base::OBJ_COUNT;
ContainerType& container() { return this->container_; }
const ContainerType& const_container() { return this->container_; }
ObjType** objects() { return this->objects_; }
size_t& refs_held() { return this->refs_held_; }
void ReleaseObject(size_t ndx) { Sp::ReleaseObject(ndx); }
bool HoldingObject(size_t ndx) const { return Sp::HoldingObject(ndx); }
};
} // namespace intrusive_containers
} // namespace tests
} // namespace fbl