blob: f1be426aabe0c7ba90eea47cc1fb6f6d44ffef66 [file]
// Copyright 2025 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#include "pw_containers/dynamic_deque.h"
#include <cstddef>
#include <deque>
#include <random>
#include <vector>
#include "pw_allocator/fault_injecting_allocator.h"
#include "pw_allocator/null_allocator.h"
#include "pw_allocator/testing.h"
#include "pw_compilation_testing/negative_compilation.h"
#include "pw_containers/algorithm.h"
#include "pw_containers/internal/container_tests.h"
#include "pw_containers/internal/test_helpers.h"
#include "pw_polyfill/language_feature_macros.h"
#include "pw_unit_test/framework.h"
namespace {
using pw::containers::Equal;
using pw::containers::test::CopyOnly;
using pw::containers::test::Counter;
using pw::containers::test::MoveOnly;
// Instantiate common container tests.
template <typename SizeType>
class CommonTest
: public ::pw::containers::test::CommonTestFixture<CommonTest<SizeType>> {
public:
// "Container" declares an empty container usable in the test.
template <typename T>
class Container {
public:
constexpr Container(CommonTest& fixture) : container_(fixture.allocator_) {}
pw::DynamicDeque<T, SizeType>& get() { return container_; }
const pw::DynamicDeque<T, SizeType>& get() const { return container_; }
private:
pw::DynamicDeque<T, SizeType> container_;
};
private:
::pw::allocator::test::AllocatorForTest<512> allocator_;
};
using DynamicDequeCommonTestUint8 = CommonTest<uint8_t>;
using DynamicDequeCommonTestUint16 = CommonTest<uint16_t>;
PW_CONTAINERS_COMMON_DEQUE_TESTS(DynamicDequeCommonTestUint8);
PW_CONTAINERS_COMMON_DEQUE_TESTS(DynamicDequeCommonTestUint16);
PW_CONSTINIT pw::allocator::NullAllocator null_allocator;
PW_CONSTINIT const pw::DynamicDeque<int> kEmpty(null_allocator);
class DynamicDequeTest : public ::testing::Test {
protected:
DynamicDequeTest() : allocator_(allocator_for_test_) {}
pw::allocator::test::AllocatorForTest<1024> allocator_for_test_;
pw::allocator::test::FaultInjectingAllocator allocator_;
};
TEST(DynamicDeque, Constinit) {
EXPECT_TRUE(kEmpty.empty());
for (auto unused : kEmpty) {
ADD_FAILURE() << unused;
}
}
TEST_F(DynamicDequeTest, MoveConstruct) {
pw::DynamicDeque<MoveOnly> deque(allocator_);
deque.emplace_back(MoveOnly(1));
deque.emplace_back(MoveOnly(2));
deque.emplace_back(MoveOnly(3));
deque.emplace_back(MoveOnly(4));
pw::DynamicDeque<MoveOnly> moved_into(std::move(deque));
EXPECT_EQ(0u, deque.size()); // NOLINT(bugprone-use-after-move)
ASSERT_EQ(4u, moved_into.size());
EXPECT_EQ(1, moved_into[0].value);
EXPECT_EQ(2, moved_into[1].value);
EXPECT_EQ(3, moved_into[2].value);
EXPECT_EQ(4, moved_into[3].value);
}
TEST_F(DynamicDequeTest, AllocationFailure) {
allocator_.DisableAll();
pw::DynamicDeque<int> deque(allocator_);
EXPECT_FALSE(deque.try_push_back(6));
EXPECT_FALSE(deque.try_push_front(7));
allocator_.EnableAll();
EXPECT_TRUE(deque.try_push_back(6));
ASSERT_TRUE(Equal(deque, std::array{6}));
}
TEST_F(DynamicDequeTest, InterspersedAllocationFailures) {
pw::DynamicDeque<int> deque(allocator_);
allocator_.DisableAll();
EXPECT_FALSE(deque.try_push_back(1));
allocator_.EnableAll();
ASSERT_TRUE(deque.try_push_back(1));
ASSERT_TRUE(deque.try_push_front(2));
ASSERT_TRUE(Equal(deque, std::array{2, 1}));
allocator_.DisableAll();
// Fill to capacity
for (int i = 0; deque.size() < deque.capacity(); ++i) {
ASSERT_TRUE(deque.try_push_front(i));
}
EXPECT_FALSE(deque.try_push_front(100));
EXPECT_FALSE(deque.try_push_back(100));
allocator_.EnableAll();
EXPECT_TRUE(deque.try_push_back(100));
}
TEST_F(DynamicDequeTest, Capacity_ResizesWhenPossible) {
pw::DynamicDeque<int> deque(allocator_);
ASSERT_TRUE(deque.try_push_back(1)) << "Allocate initial array";
allocator_.DisableAllocate();
// Fill to capacity to force resize.
for (int i = 0; deque.size() < deque.capacity(); ++i) {
ASSERT_TRUE(deque.try_push_back(i));
}
ASSERT_TRUE(deque.try_push_back(-1)) << "Must resize instead of allocate";
deque.pop_front(); // Free a slot in the front
// Fill again, wrap from the back to the front
for (int i = 0; deque.size() < deque.capacity(); ++i) {
ASSERT_TRUE(deque.try_push_back(i));
}
ASSERT_FALSE(deque.try_reserve_exact(deque.capacity() + 1)) << "Wrapped";
deque.pop_back(); // remove wrapped element
ASSERT_TRUE(deque.try_reserve_exact(deque.capacity() + 1))
<< "No longer wrapped";
// Wrap from the front to the back
ASSERT_TRUE(deque.try_push_front(123));
ASSERT_TRUE(deque.try_push_front(1234));
ASSERT_FALSE(deque.try_reserve_exact(deque.capacity() + 1)) << "Wrapped";
deque.pop_front(); // remove wrapped element
ASSERT_TRUE(deque.try_reserve_exact(deque.capacity() + 1))
<< "No longer wrapped";
// Fill to capacity and wrap to the back
ASSERT_TRUE(deque.try_push_front(123));
ASSERT_TRUE(deque.try_push_front(1234));
ASSERT_FALSE(deque.try_push_front(12345)) << "Wrapped, cannot resize";
ASSERT_FALSE(deque.try_push_back(12345)) << "Wrapped, cannot resize";
allocator_.EnableAllocate();
ASSERT_TRUE(deque.try_push_front(12345));
ASSERT_TRUE(deque.try_push_back(12345));
}
TEST_F(DynamicDequeTest, Move_MovesBufferWithoutAllocation) {
pw::DynamicDeque<int> deque_1(allocator_);
pw::DynamicDeque<int> deque_2(allocator_);
deque_1.assign({1});
deque_2.assign({-1, -2, -3, -4, -5});
const int* const deque_2_first = &deque_2.front();
allocator_.DisableAll();
deque_1 = std::move(deque_2);
EXPECT_TRUE(Equal(deque_1, std::array{-1, -2, -3, -4, -5}));
EXPECT_EQ(deque_2_first, &deque_1.front());
}
TEST_F(DynamicDequeTest, MoveAssign_DestroysOldElementsAndFreesBuffer) {
pw::DynamicDeque<Counter> deque_1(allocator_);
pw::DynamicDeque<Counter> deque_2(allocator_);
ASSERT_TRUE(deque_1.try_push_back(1));
ASSERT_TRUE(deque_1.try_push_back(2));
ASSERT_TRUE(deque_2.try_push_back(3));
ASSERT_TRUE(deque_2.try_push_back(4));
ASSERT_TRUE(deque_2.try_push_back(5));
ASSERT_EQ(deque_1.size(), 2u);
ASSERT_EQ(deque_2.size(), 3u);
Counter::Reset();
const size_t initial_bytes = allocator_for_test_.GetAllocated();
deque_1 = std::move(deque_2);
EXPECT_EQ(deque_1.size(), 3u);
EXPECT_EQ(Counter::destroyed, 2);
EXPECT_LT(allocator_for_test_.GetAllocated(), initial_bytes);
}
TEST_F(DynamicDequeTest, Capacity_ReserveExactBeforeBufferIsAllocated) {
pw::DynamicDeque<int> deque(allocator_);
ASSERT_TRUE(deque.try_reserve_exact(3));
allocator_.DisableAll();
deque.push_front(1);
deque.push_front(2);
deque.push_front(3);
EXPECT_FALSE(deque.try_push_back(0));
}
TEST_F(DynamicDequeTest, Capacity_ReserveExactRetriesIfAllocationFails) {
pw::DynamicDeque<int> deque(allocator_);
ASSERT_TRUE(deque.try_reserve_exact(3));
allocator_.DisableAll();
deque.push_front(1);
deque.push_front(2);
deque.push_front(3);
EXPECT_FALSE(deque.try_push_back(0));
}
TEST_F(DynamicDequeTest, Capacity_ReserveIncreasesByMoreThanOne) {
pw::DynamicDeque<int> deque(allocator_);
ASSERT_TRUE(deque.try_reserve_exact(50));
deque.reserve(51);
EXPECT_GT(deque.capacity(), 51);
const auto original_capacity = deque.capacity();
deque.reserve(52);
EXPECT_EQ(original_capacity, deque.capacity());
}
TEST_F(DynamicDequeTest, Capacity_ReserveSucceedsWhenCannotDouble) {
pw::DynamicDeque<int> deque(allocator_);
ASSERT_TRUE(deque.try_reserve_exact(200));
ASSERT_EQ(deque.capacity(), 200u);
ASSERT_FALSE(deque.try_reserve_exact(400));
EXPECT_TRUE(deque.try_reserve(201));
EXPECT_LT(deque.capacity(), 400);
EXPECT_GE(deque.capacity(), 201);
}
TEST_F(DynamicDequeTest, Capacity_ShrinkToFitNopWhenFull) {
pw::DynamicDeque<int> deque(allocator_);
deque.reserve_exact(3);
deque.assign({1, 2, 3});
ASSERT_EQ(deque.capacity(), 3u);
ASSERT_EQ(deque.size(), 3u);
deque.shrink_to_fit();
ASSERT_EQ(deque.capacity(), 3u);
}
TEST_F(DynamicDequeTest, Capacity_ShrinkToFitResizesWhenPossible) {
pw::DynamicDeque<int> deque(allocator_);
deque.reserve_exact(10);
deque.push_back(1);
ASSERT_EQ(deque.capacity(), 10u);
allocator_.DisableAllocate();
deque.shrink_to_fit();
ASSERT_EQ(deque.capacity(), 1u);
}
TEST_F(DynamicDequeTest, Capacity_ShrinkToFitOnlyResizesIfHeadIs0) {
pw::DynamicDeque<int> deque(allocator_);
// Empty slot is in front, so resize is not possible.
deque.reserve_exact(4);
deque.assign({1, 2, 3, 4});
deque.pop_front();
ASSERT_TRUE(Equal(deque, std::array{2, 3, 4}));
// Rely on resize, which isn't possible since there's an empty slot in front.
allocator_.DisableAllocate();
deque.shrink_to_fit();
EXPECT_EQ(deque.capacity(), 4u) << "shrink_to_fit() failed";
ASSERT_TRUE(Equal(deque, std::array{2, 3, 4}));
allocator_.EnableAllocate(); // Allow falling back to reallocate
deque.shrink_to_fit();
ASSERT_EQ(deque.capacity(), 3u) << "shrink_to_fit() reallocated";
ASSERT_TRUE(Equal(deque, std::array{2, 3, 4}));
}
TEST_F(DynamicDequeTest, Capacity_ShrinkToFitEmptyFreesBuffer) {
pw::DynamicDeque<int> deque(allocator_);
deque.reserve_exact(4);
ASSERT_EQ(deque.capacity(), 4u);
deque.clear();
ASSERT_EQ(deque.capacity(), 4u);
deque.shrink_to_fit();
ASSERT_EQ(deque.capacity(), 0u);
}
TEST_F(DynamicDequeTest, Reset_DestroysElementsAndFreesBuffer) {
pw::DynamicDeque<Counter> deque(allocator_);
deque.push_back(1);
deque.push_back(2);
ASSERT_EQ(deque.size(), 2u);
Counter::Reset();
deque.reset();
EXPECT_EQ(deque.size(), 0u);
EXPECT_EQ(deque.capacity(), 0u);
EXPECT_EQ(Counter::destroyed, 2);
}
TEST_F(DynamicDequeTest, Capacity_ShrinkToFailsSilentlyIfCannotShrink) {
pw::DynamicDeque<int> deque(allocator_);
deque.reserve_exact(8);
deque.assign(3u, 123);
ASSERT_TRUE(Equal(deque, std::array{123, 123, 123}));
allocator_.DisableAll();
deque.shrink_to_fit();
EXPECT_EQ(deque.capacity(), 8u);
}
constexpr size_t kHardcodedMinAllocSize = 4 * sizeof(void*);
TEST_F(DynamicDequeTest, Capacity_MinimumAllocationSizeSmallItem) {
pw::DynamicDeque<char> deque(allocator_);
deque.push_back('a');
EXPECT_EQ(deque.capacity() * sizeof(char), kHardcodedMinAllocSize);
}
TEST_F(DynamicDequeTest, Capacity_MinimumAllocationSizeMediumItem) {
struct MediumOne {
char bytes[kHardcodedMinAllocSize / 3];
};
pw::DynamicDeque<MediumOne> deque(allocator_);
deque.push_back(MediumOne{});
EXPECT_EQ(deque.capacity(), 3u);
}
TEST_F(DynamicDequeTest, Capacity_MinimumAllocationSizeLargeItem) {
struct BigOne {
char whoa[128];
};
static_assert(sizeof(BigOne) > kHardcodedMinAllocSize);
pw::DynamicDeque<BigOne> deque(allocator_);
deque.push_back(BigOne{});
EXPECT_EQ(deque.capacity(), 1u);
}
TEST_F(DynamicDequeTest, TryAssign_NoPartialAssignments) {
static bool fail = true;
struct FailOnCopy {
constexpr FailOnCopy() = default;
FailOnCopy(const FailOnCopy& other) { *this = other; }
FailOnCopy& operator=(const FailOnCopy&) {
if (fail) {
ADD_FAILURE() << "Unwanted copy detected!";
}
return *this;
}
};
std::array<FailOnCopy, 5> array{};
pw::DynamicDeque<FailOnCopy> deque(allocator_);
deque.reserve_exact(4);
allocator_.DisableAll();
EXPECT_FALSE(deque.try_assign(array.begin(), array.end()));
EXPECT_TRUE(deque.empty());
EXPECT_FALSE(deque.try_assign(5, FailOnCopy()));
EXPECT_TRUE(deque.empty());
fail = false; // allow copies now
EXPECT_TRUE(deque.try_assign(array.begin(), array.end() - 1));
EXPECT_EQ(deque.size(), 4u);
EXPECT_TRUE(deque.try_assign(2, FailOnCopy()));
EXPECT_EQ(deque.size(), 2u);
}
TEST_F(DynamicDequeTest, MaxSize_BasedOnSizeType) {
EXPECT_EQ((pw::DynamicDeque<int, uint8_t>(allocator_).max_size()), 255u);
EXPECT_EQ((pw::DynamicDeque<int, uint16_t>(allocator_).max_size()), 65535u);
EXPECT_EQ((pw::DynamicDeque<int, uint32_t>(allocator_).max_size()),
std::numeric_limits<uint32_t>::max());
}
TEST_F(DynamicDequeTest, MaxSize_CannotExceed) {
pw::DynamicDeque<bool, uint8_t> deque(allocator_);
deque.assign(255, true);
ASSERT_EQ(deque.size(), 255u);
ASSERT_EQ(deque.capacity(), 255u);
EXPECT_FALSE(deque.try_push_back(false));
EXPECT_FALSE(deque.try_push_front(true));
EXPECT_EQ(deque.try_insert(deque.begin(), {true, false, true, false}),
std::nullopt);
}
TEST_F(DynamicDequeTest, MaxSize_CapacityClamps) {
pw::DynamicDeque<bool, uint8_t> deque(allocator_);
deque.assign(200, true);
EXPECT_EQ(deque.capacity(), 200u);
ASSERT_TRUE(deque.try_push_back(false));
EXPECT_EQ(deque.capacity(), 255u);
}
TEST_F(DynamicDequeTest, Erase_Wrapped) {
pw::DynamicDeque<int> deque(allocator_);
deque.reserve_exact(5);
deque.assign({1, 2, 3, 4, 5});
deque.pop_front();
deque.pop_front();
deque.pop_front();
deque.push_back(6);
deque.push_back(7);
deque.push_back(8);
ASSERT_TRUE(Equal(deque, std::array{4, 5, 6, 7, 8}));
ASSERT_LT(&deque.back(), &deque.front()) << "Must be wrapped";
pw::DynamicDeque<int>::iterator it = deque.erase(deque.begin() + 2);
EXPECT_EQ(*it, 7);
ASSERT_TRUE(Equal(deque, std::array{4, 5, 7, 8}));
it = deque.erase(deque.begin() + 1, deque.begin() + 3);
EXPECT_EQ(*it, 8);
ASSERT_TRUE(Equal(deque, std::array{4, 8}));
it = deque.erase(deque.begin() + 1, deque.end());
EXPECT_EQ(it, deque.end());
ASSERT_TRUE(Equal(deque, std::array{4}));
}
TEST_F(DynamicDequeTest, Erase_Wrapped_RangeAcrossWrap) {
pw::DynamicDeque<int> deque(allocator_);
deque.reserve_exact(5);
deque.assign({1, 2, 3, 4, 5});
deque.pop_front();
deque.pop_front();
deque.pop_front();
deque.push_back(6);
deque.push_back(7);
deque.push_back(8);
ASSERT_TRUE(Equal(deque, std::array{4, 5, 6, 7, 8}));
ASSERT_LT(&deque.back(), &deque.front()) << "Must be wrapped";
auto it = deque.erase(deque.begin() + 1, deque.begin() + 4);
EXPECT_EQ(*it, 8);
ASSERT_TRUE(Equal(deque, std::array{4, 8}));
}
TEST_F(DynamicDequeTest, Erase_Wrapped_All) {
pw::DynamicDeque<int> deque(allocator_);
deque.reserve_exact(5);
deque.assign({1, 2, 3, 4, 5});
deque.pop_front();
deque.pop_front();
deque.pop_front();
deque.push_back(6);
deque.push_back(7);
deque.push_back(8);
ASSERT_TRUE(Equal(deque, std::array{4, 5, 6, 7, 8}));
ASSERT_LT(&deque.back(), &deque.front()) << "Must be wrapped";
pw::DynamicDeque<int>::iterator it = deque.erase(deque.begin(), deque.end());
EXPECT_EQ(it, deque.end());
ASSERT_TRUE(deque.empty());
}
class DynamicDequeWrappedTest : public DynamicDequeTest {
protected:
DynamicDequeWrappedTest() : deque_(allocator_) {
deque_.reserve_exact(10);
for (int i = 0; i < 10; ++i) {
deque_.push_back(i);
}
for (int i = 0; i < 5; ++i) {
deque_.pop_front();
deque_.push_back(i + 10);
}
EXPECT_TRUE(Equal(deque_, std::array{5, 6, 7, 8, 9, 10, 11, 12, 13, 14}));
EXPECT_LT(&deque_.back(), &deque_.front()) << "Deque must be wrapped";
}
pw::DynamicDeque<int> deque_;
};
TEST_F(DynamicDequeWrappedTest, Insert_NearBegin_FewerThanBefore) {
std::array<int, 1> to_insert = {99};
auto it =
deque_.insert(deque_.begin() + 2, to_insert.begin(), to_insert.end());
EXPECT_EQ(*it, 99);
ASSERT_TRUE(Equal(deque_, std::array{5, 6, 99, 7, 8, 9, 10, 11, 12, 13, 14}));
}
TEST_F(DynamicDequeWrappedTest, Insert_NearBegin_SameAsBefore) {
std::array<int, 2> to_insert = {98, 99};
auto it =
deque_.insert(deque_.begin() + 2, to_insert.begin(), to_insert.end());
EXPECT_EQ(*it, 98);
ASSERT_TRUE(
Equal(deque_, std::array{5, 6, 98, 99, 7, 8, 9, 10, 11, 12, 13, 14}));
}
TEST_F(DynamicDequeWrappedTest, Insert_NearBegin_MoreThanBefore) {
std::array<int, 3> to_insert = {97, 98, 99};
auto it =
deque_.insert(deque_.begin() + 2, to_insert.begin(), to_insert.end());
EXPECT_EQ(*it, 97);
ASSERT_TRUE(
Equal(deque_, std::array{5, 6, 97, 98, 99, 7, 8, 9, 10, 11, 12, 13, 14}));
}
TEST_F(DynamicDequeWrappedTest, Insert_NearEnd_FewerThanAfter) {
std::array<int, 1> to_insert = {99};
auto it = deque_.insert(deque_.end() - 2, to_insert.begin(), to_insert.end());
EXPECT_EQ(*it, 99);
ASSERT_TRUE(Equal(deque_, std::array{5, 6, 7, 8, 9, 10, 11, 12, 99, 13, 14}));
}
TEST_F(DynamicDequeWrappedTest, Insert_NearEnd_SameAsAfter) {
std::array<int, 2> to_insert = {98, 99};
auto it = deque_.insert(deque_.end() - 2, to_insert.begin(), to_insert.end());
EXPECT_EQ(*it, 98);
ASSERT_TRUE(
Equal(deque_, std::array{5, 6, 7, 8, 9, 10, 11, 12, 98, 99, 13, 14}));
}
TEST_F(DynamicDequeWrappedTest, Insert_NearEnd_MoreThanAfter) {
std::array<int, 3> to_insert = {97, 98, 99};
auto it = deque_.insert(deque_.end() - 2, to_insert.begin(), to_insert.end());
EXPECT_EQ(*it, 97);
ASSERT_TRUE(
Equal(deque_, std::array{5, 6, 7, 8, 9, 10, 11, 12, 97, 98, 99, 13, 14}));
}
TEST_F(DynamicDequeWrappedTest, Insert_CopiesNearBegin_FewerThanBefore) {
auto it = deque_.insert(deque_.begin() + 2, 1, 99);
EXPECT_EQ(*it, 99);
ASSERT_TRUE(Equal(deque_, std::array{5, 6, 99, 7, 8, 9, 10, 11, 12, 13, 14}));
}
TEST_F(DynamicDequeWrappedTest, Insert_CopiesNearBegin_SameAsBefore) {
auto it = deque_.insert(deque_.begin() + 2, 2, 99);
EXPECT_EQ(*it, 99);
ASSERT_TRUE(
Equal(deque_, std::array{5, 6, 99, 99, 7, 8, 9, 10, 11, 12, 13, 14}));
}
TEST_F(DynamicDequeWrappedTest, Insert_CopiesNearBegin_MoreThanBefore) {
auto it = deque_.insert(deque_.begin() + 2, 3, 99);
EXPECT_EQ(*it, 99);
ASSERT_TRUE(
Equal(deque_, std::array{5, 6, 99, 99, 99, 7, 8, 9, 10, 11, 12, 13, 14}));
}
TEST_F(DynamicDequeWrappedTest, Insert_CopiesNearEnd_FewerThanAfter) {
auto it = deque_.insert(deque_.end() - 2, 1, 99);
EXPECT_EQ(*it, 99);
ASSERT_TRUE(Equal(deque_, std::array{5, 6, 7, 8, 9, 10, 11, 12, 99, 13, 14}));
}
TEST_F(DynamicDequeWrappedTest, Insert_CopiesNearEnd_SameAsAfter) {
auto it = deque_.insert(deque_.end() - 2, 2, 99);
EXPECT_EQ(*it, 99);
ASSERT_TRUE(
Equal(deque_, std::array{5, 6, 7, 8, 9, 10, 11, 12, 99, 99, 13, 14}));
}
TEST_F(DynamicDequeWrappedTest, Insert_CopiesNearEnd_MoreThanAfter) {
auto it = deque_.insert(deque_.end() - 2, 3, 99);
EXPECT_EQ(*it, 99);
ASSERT_TRUE(
Equal(deque_, std::array{5, 6, 7, 8, 9, 10, 11, 12, 99, 99, 99, 13, 14}));
}
TEST_F(DynamicDequeWrappedTest, Insert_InputIterator_NearBegin) {
using pw::containers::test::InputIt;
auto it = deque_.insert(deque_.begin() + 2, InputIt(97), InputIt(100));
EXPECT_EQ(*it, 97);
ASSERT_TRUE(
Equal(deque_, std::array{5, 6, 97, 98, 99, 7, 8, 9, 10, 11, 12, 13, 14}));
}
TEST_F(DynamicDequeWrappedTest, Insert_InputIterator_NearEnd) {
using pw::containers::test::InputIt;
auto it = deque_.insert(deque_.end() - 2, InputIt(97), InputIt(100));
EXPECT_EQ(*it, 97);
ASSERT_TRUE(
Equal(deque_, std::array{5, 6, 7, 8, 9, 10, 11, 12, 97, 98, 99, 13, 14}));
}
TEST_F(DynamicDequeWrappedTest, Insert_InputIterator_MoreThanSize) {
using pw::containers::test::InputIt;
deque_.assign({1, 2});
auto it = deque_.insert(deque_.begin() + 1, InputIt(3), InputIt(15));
EXPECT_EQ(*it, 3);
ASSERT_TRUE(
Equal(deque_, std::array{1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 2}));
}
TEST_F(DynamicDequeTest, Swap_BothEmpty) {
pw::DynamicDeque<Counter> container_1(allocator_);
pw::DynamicDeque<Counter> container_2(null_allocator);
container_1.swap(container_2);
EXPECT_TRUE(container_1.empty());
EXPECT_TRUE(container_2.empty());
EXPECT_EQ(&container_1.get_allocator(), &null_allocator);
EXPECT_EQ(&container_2.get_allocator(), &allocator_);
}
TEST_F(DynamicDequeTest, Swap_EmptyToNonEmpty) {
pw::DynamicDeque<Counter> container_1(allocator_);
container_1.assign({1, 2});
pw::DynamicDeque<Counter> container_2(null_allocator);
container_1.swap(container_2);
EXPECT_TRUE(container_1.empty());
EXPECT_TRUE(Equal(container_2, std::array{1, 2}));
EXPECT_EQ(&container_1.get_allocator(), &null_allocator);
EXPECT_EQ(&container_2.get_allocator(), &allocator_);
}
TEST_F(DynamicDequeTest, Swap_NonEmptyToEmpty) {
pw::DynamicDeque<Counter> container_1(allocator_);
pw::DynamicDeque<Counter> container_2(allocator_);
container_2.assign({-1, -2, -3, -4});
container_2.pop_front();
container_2.pop_front();
container_2.push_back(-5);
container_1.swap(container_2);
EXPECT_TRUE(Equal(container_1, std::array{-3, -4, -5}));
EXPECT_TRUE(container_2.empty());
}
TEST_F(DynamicDequeTest, Swap_BothNonEmpty) {
pw::DynamicDeque<Counter> container_1(allocator_);
container_1.assign({1, 2});
pw::DynamicDeque<Counter> container_2(allocator_);
container_2.assign({-1, -2, -3, -4});
container_2.pop_front();
container_1.swap(container_2);
EXPECT_TRUE(Equal(container_1, std::array{-2, -3, -4}));
EXPECT_TRUE(Equal(container_2, std::array{1, 2}));
}
TEST_F(DynamicDequeTest, Modify_TryEmplace_Empty) {
pw::DynamicDeque<Counter> container(allocator_);
Counter::Reset();
auto it = container.try_emplace(container.cbegin(), 1);
ASSERT_TRUE(it.has_value());
EXPECT_EQ(**it, 1);
EXPECT_TRUE(Equal(container, std::array{1}));
EXPECT_EQ(Counter::created, 1);
}
TEST_F(DynamicDequeTest, Modify_TryEmplace_Front) {
pw::DynamicDeque<Counter> container(allocator_);
container.assign({1, 2, 3});
Counter::Reset();
auto it = container.try_emplace(container.cbegin(), 0);
ASSERT_TRUE(it.has_value());
EXPECT_EQ(**it, 0);
EXPECT_TRUE(Equal(container, std::array{0, 1, 2, 3}));
EXPECT_EQ(Counter::created, 1);
}
TEST_F(DynamicDequeTest, Modify_TryEmplace_Back) {
pw::DynamicDeque<Counter> container(allocator_);
container.assign({1, 2, 3});
Counter::Reset();
auto it = container.try_emplace(container.cend(), 4);
ASSERT_TRUE(it.has_value());
EXPECT_EQ(**it, 4);
EXPECT_TRUE(Equal(container, std::array{1, 2, 3, 4}));
EXPECT_EQ(Counter::created, 1);
}
TEST_F(DynamicDequeTest, Modify_TryEmplace_Middle) {
pw::DynamicDeque<Counter> container(allocator_);
container.assign({1, 2, 4});
auto it = container.try_emplace(container.cbegin() + 2, 3);
ASSERT_TRUE(it.has_value());
EXPECT_EQ(**it, 3);
EXPECT_TRUE(Equal(container, std::array{1, 2, 3, 4}));
}
TEST_F(DynamicDequeTest, Modify_TryInsertCopy) {
pw::DynamicDeque<Counter> container(allocator_);
container.assign({1, 2, 4});
Counter value(3);
auto it = container.try_insert(container.cbegin() + 2, value);
ASSERT_TRUE(it.has_value());
EXPECT_EQ(*it, container.begin() + 2);
EXPECT_EQ(**it, 3);
EXPECT_TRUE(Equal(container, std::array{1, 2, 3, 4}));
}
TEST_F(DynamicDequeTest, Modify_TryInsertMove) {
pw::DynamicDeque<Counter> container(allocator_);
container.assign({1, 2, 4});
Counter value(3);
auto it = container.try_insert(container.cbegin() + 2, std::move(value));
ASSERT_TRUE(it.has_value());
EXPECT_EQ(*it, container.begin() + 2);
EXPECT_EQ(**it, 3);
EXPECT_TRUE(Equal(container, std::array{1, 2, 3, 4}));
}
TEST_F(DynamicDequeTest, Modify_TryInsertMultiple) {
pw::DynamicDeque<Counter> container(allocator_);
container.assign({1, 2, 5});
Counter value(3);
auto it = container.try_insert(container.cbegin() + 2, 2, value);
ASSERT_TRUE(it.has_value());
EXPECT_EQ(*it, container.begin() + 2);
EXPECT_EQ(**it, 3);
EXPECT_TRUE(Equal(container, std::array{1, 2, 3, 3, 5}));
}
TEST_F(DynamicDequeTest, Modify_TryEmplace_Fails) {
pw::DynamicDeque<Counter> container(allocator_);
container.assign({1, 2, 4});
Counter::Reset();
allocator_.DisableAll();
auto it = container.try_emplace(container.cbegin() + 2, 3);
ASSERT_FALSE(it.has_value());
EXPECT_TRUE(Equal(container, std::array{1, 2, 4}));
EXPECT_EQ(Counter::created, 0);
allocator_.EnableAll();
it = container.try_emplace(container.cbegin() + 2, 3);
ASSERT_TRUE(it.has_value());
EXPECT_TRUE(Equal(container, std::array{1, 2, 3, 4}));
}
TEST_F(DynamicDequeTest, Modify_TryInsertCopy_Fails) {
pw::DynamicDeque<Counter> container(allocator_);
container.assign({1, 2, 4});
Counter value(3);
Counter::Reset();
allocator_.DisableAll();
auto it = container.try_insert(container.cbegin() + 2, value);
ASSERT_FALSE(it.has_value());
EXPECT_TRUE(Equal(container, std::array{1, 2, 4}));
EXPECT_EQ(Counter::created, 0);
allocator_.EnableAll();
it = container.try_insert(container.cbegin() + 2, value);
ASSERT_TRUE(it.has_value());
EXPECT_TRUE(Equal(container, std::array{1, 2, 3, 4}));
}
TEST_F(DynamicDequeTest, Modify_TryInsertMove_Fails) {
pw::DynamicDeque<Counter> container(allocator_);
container.assign({1, 2, 4});
Counter value(3);
Counter::Reset();
allocator_.DisableAll();
auto it = container.try_insert(container.cbegin() + 2, std::move(value));
ASSERT_FALSE(it.has_value());
EXPECT_TRUE(Equal(container, std::array{1, 2, 4}));
EXPECT_EQ(Counter::created, 0);
EXPECT_EQ(Counter::moved, 0);
allocator_.EnableAll();
// NOLINTNEXTLINE(bugprone-use-after-move)
it = container.try_insert(container.cbegin() + 2, std::move(value));
ASSERT_TRUE(it.has_value());
EXPECT_TRUE(Equal(container, std::array{1, 2, 3, 4}));
}
TEST_F(DynamicDequeTest, Modify_TryInsertMultiple_Fails) {
pw::DynamicDeque<Counter> container(allocator_);
container.assign({1, 2, 5});
Counter value(3);
Counter::Reset();
allocator_.DisableAll();
auto it = container.try_insert(container.cbegin() + 2, 2, value);
ASSERT_FALSE(it.has_value());
EXPECT_TRUE(Equal(container, std::array{1, 2, 5}));
EXPECT_EQ(Counter::created, 0);
allocator_.EnableAll();
it = container.try_insert(container.cbegin() + 2, 2, value);
ASSERT_TRUE(it.has_value());
EXPECT_TRUE(Equal(container, std::array{1, 2, 3, 3, 5}));
}
TEST_F(DynamicDequeTest, Modify_TryInsert_Begin) {
pw::DynamicDeque<int> deque(allocator_);
deque.assign({1, 2, 3});
auto it = deque.try_insert(deque.begin(), 0);
ASSERT_TRUE(it.has_value());
EXPECT_EQ(**it, 0);
ASSERT_TRUE(Equal(deque, std::array{0, 1, 2, 3}));
}
TEST_F(DynamicDequeTest, Modify_TryInsert_Begin_Fails) {
pw::DynamicDeque<int> deque(allocator_);
deque.reserve_exact(3);
deque.assign({1, 2, 3});
allocator_.DisableAll();
ASSERT_FALSE(deque.try_insert(deque.begin(), 0).has_value());
ASSERT_TRUE(Equal(deque, std::array{1, 2, 3}));
}
TEST_F(DynamicDequeTest, Modify_TryInsert_End) {
pw::DynamicDeque<int> deque(allocator_);
deque.assign({1, 2, 3});
auto it = deque.try_insert(deque.end(), 4);
ASSERT_TRUE(it.has_value());
EXPECT_EQ(**it, 4);
ASSERT_TRUE(Equal(deque, std::array{1, 2, 3, 4}));
}
TEST_F(DynamicDequeTest, Modify_TryInsert_End_Fails) {
pw::DynamicDeque<int> deque(allocator_);
deque.reserve_exact(3);
deque.assign({1, 2, 3});
allocator_.DisableAll();
ASSERT_FALSE(deque.try_insert(deque.end(), 4).has_value());
ASSERT_TRUE(Equal(deque, std::array{1, 2, 3}));
}
TEST_F(DynamicDequeTest, Modify_TryInsertIterators) {
pw::DynamicDeque<Counter> container(allocator_);
container.assign({1, 5});
std::array<Counter, 3> values = {Counter(2), Counter(3), Counter(4)};
auto it = container.try_insert(container.cbegin() + 1,
std::make_move_iterator(values.begin()),
std::make_move_iterator(values.end()));
ASSERT_TRUE(it.has_value());
EXPECT_EQ(*it, container.begin() + 1);
EXPECT_TRUE(Equal(container, std::array{1, 2, 3, 4, 5}));
}
TEST_F(DynamicDequeTest, Modify_TryInsertInitializerList) {
pw::DynamicDeque<Counter> container(allocator_);
container.assign({1, 5});
auto it = container.try_insert(container.cbegin() + 1, {2, 3, 4});
ASSERT_TRUE(it.has_value());
EXPECT_EQ(*it, container.begin() + 1);
EXPECT_TRUE(Equal(container, std::array{1, 2, 3, 4, 5}));
}
void PerformRandomOperations(int iterations, uint_fast32_t seed) {
static std::byte buffer[2048];
std::memset(buffer, 0, sizeof(buffer));
pw::allocator::FirstFitAllocator<> allocator(buffer);
pw::DynamicDeque<Counter> deque(allocator);
std::deque<int> oracle;
std::mt19937 random(seed);
std::uniform_int_distribution<unsigned> distro;
auto random_uint = [&] { return distro(random); };
enum Op : unsigned {
kPushBack,
kPushFront,
kPopBack,
kPopFront,
kErase,
kReserve,
kShrinkToFit,
kTryEmplace,
kTryInsertCopy,
kTryInsertCount,
kTryInsertIterators,
kTotalOperations,
};
using diff_t = pw::DynamicDeque<Counter>::difference_type;
bool tend_to_grow = true;
for (int i = 0; i < iterations; ++i) {
switch (static_cast<Op>(random_uint() % kTotalOperations)) {
case kPushBack: {
const int value = static_cast<int>(random_uint());
if (deque.try_push_back(value)) {
oracle.push_back(value);
} else {
tend_to_grow = false;
}
break;
}
case kPushFront: {
const int value = static_cast<int>(random_uint());
if (deque.try_push_front(value)) {
oracle.push_front(value);
} else {
tend_to_grow = false;
}
break;
}
case kPopBack:
if (tend_to_grow && (random_uint() % 2) == 0u) {
continue;
}
if (deque.empty()) {
tend_to_grow = true;
} else {
deque.pop_back();
oracle.pop_back();
}
break;
case kPopFront:
if (tend_to_grow && (random_uint() % 2) == 0u) {
continue;
}
if (deque.empty()) {
tend_to_grow = true;
} else {
deque.pop_front();
oracle.pop_front();
}
break;
case kErase:
if (tend_to_grow && (random_uint() % 2) == 0u) {
continue;
}
if (deque.empty()) {
tend_to_grow = true;
} else {
diff_t pos = static_cast<diff_t>(random_uint() % oracle.size());
const auto count = static_cast<diff_t>(
random_uint() % (static_cast<diff_t>(oracle.size()) - pos) + 1);
deque.erase(deque.begin() + pos, deque.begin() + pos + count);
oracle.erase(oracle.begin() + pos, oracle.begin() + pos + count);
}
break;
case kReserve:
std::ignore =
deque.try_reserve_exact(deque.size() + random_uint() % 100);
break;
case kShrinkToFit:
deque.shrink_to_fit();
break;
case kTryEmplace: {
diff_t pos = static_cast<diff_t>(random_uint() % (oracle.size() + 1));
const int value = static_cast<int>(random_uint());
if (deque.try_emplace(deque.begin() + pos, value).has_value()) {
oracle.emplace(oracle.begin() + pos, value);
} else {
tend_to_grow = false;
}
break;
}
case kTryInsertCopy: {
diff_t pos = static_cast<diff_t>(random_uint() % (oracle.size() + 1));
const int value = static_cast<int>(random_uint());
if (deque.try_insert(deque.begin() + pos, value).has_value()) {
oracle.insert(oracle.begin() + pos, value);
} else {
tend_to_grow = false;
}
break;
}
case kTryInsertCount: {
diff_t pos = static_cast<diff_t>(random_uint() % (oracle.size() + 1));
const auto count = static_cast<pw::DynamicDeque<Counter>::size_type>(
random_uint() % 100);
const int value = static_cast<int>(random_uint());
if (deque.try_insert(deque.begin() + pos, count, value).has_value()) {
oracle.insert(oracle.begin() + pos, count, value);
} else {
tend_to_grow = false;
}
break;
}
case kTryInsertIterators: {
diff_t pos = static_cast<diff_t>(random_uint() % (oracle.size() + 1));
const size_t count = random_uint() % 5;
std::vector<int> source;
source.reserve(count);
for (size_t k = 0; k < count; ++k) {
source.push_back(static_cast<int>(random_uint()));
}
if (deque.try_insert(deque.begin() + pos, source.begin(), source.end())
.has_value()) {
oracle.insert(oracle.begin() + pos, source.begin(), source.end());
} else {
tend_to_grow = false;
}
break;
}
case kTotalOperations:
default:
FAIL();
}
ASSERT_EQ(deque.size(), oracle.size());
for (decltype(deque)::size_type j = 0; j < deque.size(); ++j) {
ASSERT_EQ(deque[j], oracle[j]);
}
}
}
TEST(DynamicDeque, RandomOperations) {
PerformRandomOperations(10000, 1);
PerformRandomOperations(1000, 98);
PerformRandomOperations(1000, 5555);
}
// Instantiate shared iterator tests.
static_assert(
pw::containers::test::IteratorProperties<pw::DynamicDeque>::kPasses);
// Test that DynamicDeque<T> is NOT copy constructible
static_assert(!std::is_copy_constructible_v<pw::DynamicDeque<int>>);
// Test that DynamicDeque<T> is move constructible
static_assert(std::is_move_constructible_v<pw::DynamicDeque<MoveOnly>>);
// Test that DynamicDeque<T> is NOT copy assignable
static_assert(!std::is_copy_assignable_v<pw::DynamicDeque<CopyOnly>>);
// Test that DynamicDeque<T> is move assignable
static_assert(std::is_move_assignable_v<pw::DynamicDeque<MoveOnly>>);
// Check padding / layout of the object.
struct Uint8Layout {
uint8_t fields[4];
void* allocator;
void* buffer;
};
static_assert(sizeof(pw::DynamicDeque<int, uint8_t>) == sizeof(Uint8Layout));
static_assert(sizeof(pw::DynamicDeque<long long, uint16_t>) ==
4 * sizeof(uint16_t) + 2 * sizeof(void*));
static_assert(sizeof(pw::DynamicDeque<int, uint32_t>) ==
4 * sizeof(uint32_t) + 2 * sizeof(void*));
#if PW_NC_TEST(DynamicDeque_EmplaceBackOverwriteHidden)
PW_NC_EXPECT("(is inaccessible|is (a )?private member of)");
void Test(pw::DynamicDeque<int>& deque) { deque.emplace_back_overwrite(1); }
#elif PW_NC_TEST(DynamicDeque_PushBackOverwriteHidden)
PW_NC_EXPECT("(is inaccessible|is (a )?private member of)");
void Test(pw::DynamicDeque<int>& deque) { deque.push_back_overwrite(1); }
#endif
} // namespace