blob: 9278420ce6a1afb14d0901023a9c2139e812622d [file] [log] [blame]
// Copyright (c) 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "quiche/common/quiche_circular_deque.h"
#include <cstddef>
#include <cstdint>
#include <list>
#include <memory>
#include <ostream>
#include <type_traits>
#include <utility>
#include "quiche/common/platform/api/quiche_logging.h"
#include "quiche/common/platform/api/quiche_test.h"
using testing::ElementsAre;
namespace quiche {
namespace test {
namespace {
template <typename T, template <typename> class BaseAllocator = std::allocator>
class CountingAllocator : public BaseAllocator<T> {
using BaseType = BaseAllocator<T>;
public:
using propagate_on_container_copy_assignment = std::true_type;
using propagate_on_container_move_assignment = std::true_type;
using propagate_on_container_swap = std::true_type;
T* allocate(std::size_t n) {
++shared_counts_->allocate_count;
return BaseType::allocate(n);
}
void deallocate(T* ptr, std::size_t n) {
++shared_counts_->deallocate_count;
return BaseType::deallocate(ptr, n);
}
size_t allocate_count() const { return shared_counts_->allocate_count; }
size_t deallocate_count() const { return shared_counts_->deallocate_count; }
friend bool operator==(const CountingAllocator& lhs,
const CountingAllocator& rhs) {
return lhs.shared_counts_ == rhs.shared_counts_;
}
friend bool operator!=(const CountingAllocator& lhs,
const CountingAllocator& rhs) {
return !(lhs == rhs);
}
private:
struct Counts {
size_t allocate_count = 0;
size_t deallocate_count = 0;
};
std::shared_ptr<Counts> shared_counts_ = std::make_shared<Counts>();
};
template <typename T, typename propagate_on_copy_assignment,
typename propagate_on_move_assignment, typename propagate_on_swap,
bool equality_result,
template <typename> class BaseAllocator = std::allocator>
struct ConfigurableAllocator : public BaseAllocator<T> {
using propagate_on_container_copy_assignment = propagate_on_copy_assignment;
using propagate_on_container_move_assignment = propagate_on_move_assignment;
using propagate_on_container_swap = propagate_on_swap;
friend bool operator==(const ConfigurableAllocator& /*lhs*/,
const ConfigurableAllocator& /*rhs*/) {
return equality_result;
}
friend bool operator!=(const ConfigurableAllocator& lhs,
const ConfigurableAllocator& rhs) {
return !(lhs == rhs);
}
};
// [1, 2, 3, 4] ==> [4, 1, 2, 3]
template <typename Deque>
void ShiftRight(Deque* dq, bool emplace) {
auto back = *(&dq->back());
dq->pop_back();
if (emplace) {
dq->emplace_front(back);
} else {
dq->push_front(back);
}
}
// [1, 2, 3, 4] ==> [2, 3, 4, 1]
template <typename Deque>
void ShiftLeft(Deque* dq, bool emplace) {
auto front = *(&dq->front());
dq->pop_front();
if (emplace) {
dq->emplace_back(front);
} else {
dq->push_back(front);
}
}
class QuicheCircularDequeTest : public QuicheTest {};
TEST_F(QuicheCircularDequeTest, Empty) {
QuicheCircularDeque<int> dq;
EXPECT_TRUE(dq.empty());
EXPECT_EQ(0u, dq.size());
dq.clear();
dq.push_back(10);
EXPECT_FALSE(dq.empty());
EXPECT_EQ(1u, dq.size());
EXPECT_EQ(10, dq.front());
EXPECT_EQ(10, dq.back());
dq.pop_front();
EXPECT_TRUE(dq.empty());
EXPECT_EQ(0u, dq.size());
EXPECT_QUICHE_DEBUG_DEATH(dq.front(), "");
EXPECT_QUICHE_DEBUG_DEATH(dq.back(), "");
EXPECT_QUICHE_DEBUG_DEATH(dq.at(0), "");
EXPECT_QUICHE_DEBUG_DEATH(dq[0], "");
}
TEST_F(QuicheCircularDequeTest, Constructor) {
QuicheCircularDeque<int> dq;
EXPECT_TRUE(dq.empty());
std::allocator<int> alloc;
QuicheCircularDeque<int> dq1(alloc);
EXPECT_TRUE(dq1.empty());
QuicheCircularDeque<int> dq2(8, 100, alloc);
EXPECT_THAT(dq2, ElementsAre(100, 100, 100, 100, 100, 100, 100, 100));
QuicheCircularDeque<int> dq3(5, alloc);
EXPECT_THAT(dq3, ElementsAre(0, 0, 0, 0, 0));
QuicheCircularDeque<int> dq4_rand_iter(dq3.begin(), dq3.end(), alloc);
EXPECT_THAT(dq4_rand_iter, ElementsAre(0, 0, 0, 0, 0));
EXPECT_EQ(dq4_rand_iter, dq3);
std::list<int> dq4_src = {4, 4, 4, 4};
QuicheCircularDeque<int> dq4_bidi_iter(dq4_src.begin(), dq4_src.end());
EXPECT_THAT(dq4_bidi_iter, ElementsAre(4, 4, 4, 4));
QuicheCircularDeque<int> dq5(dq4_bidi_iter);
EXPECT_THAT(dq5, ElementsAre(4, 4, 4, 4));
EXPECT_EQ(dq5, dq4_bidi_iter);
QuicheCircularDeque<int> dq6(dq5, alloc);
EXPECT_THAT(dq6, ElementsAre(4, 4, 4, 4));
EXPECT_EQ(dq6, dq5);
QuicheCircularDeque<int> dq7(std::move(*&dq6));
EXPECT_THAT(dq7, ElementsAre(4, 4, 4, 4));
EXPECT_TRUE(dq6.empty());
QuicheCircularDeque<int> dq8_equal_allocator(std::move(*&dq7), alloc);
EXPECT_THAT(dq8_equal_allocator, ElementsAre(4, 4, 4, 4));
EXPECT_TRUE(dq7.empty());
QuicheCircularDeque<int, 3, CountingAllocator<int>> dq8_temp = {5, 6, 7, 8,
9};
QuicheCircularDeque<int, 3, CountingAllocator<int>> dq8_unequal_allocator(
std::move(*&dq8_temp), CountingAllocator<int>());
EXPECT_THAT(dq8_unequal_allocator, ElementsAre(5, 6, 7, 8, 9));
EXPECT_TRUE(dq8_temp.empty());
QuicheCircularDeque<int> dq9({3, 4, 5, 6, 7}, alloc);
EXPECT_THAT(dq9, ElementsAre(3, 4, 5, 6, 7));
}
TEST_F(QuicheCircularDequeTest, Assign) {
// assign()
QuicheCircularDeque<int, 3, CountingAllocator<int>> dq;
dq.assign(7, 1);
EXPECT_THAT(dq, ElementsAre(1, 1, 1, 1, 1, 1, 1));
EXPECT_EQ(1u, dq.get_allocator().allocate_count());
QuicheCircularDeque<int, 3, CountingAllocator<int>> dq2;
dq2.assign(dq.begin(), dq.end());
EXPECT_THAT(dq2, ElementsAre(1, 1, 1, 1, 1, 1, 1));
EXPECT_EQ(1u, dq2.get_allocator().allocate_count());
EXPECT_TRUE(std::equal(dq.begin(), dq.end(), dq2.begin(), dq2.end()));
dq2.assign({2, 2, 2, 2, 2, 2});
EXPECT_THAT(dq2, ElementsAre(2, 2, 2, 2, 2, 2));
// Assign from a non random access iterator.
std::list<int> dq3_src = {3, 3, 3, 3, 3};
QuicheCircularDeque<int, 3, CountingAllocator<int>> dq3;
dq3.assign(dq3_src.begin(), dq3_src.end());
EXPECT_THAT(dq3, ElementsAre(3, 3, 3, 3, 3));
EXPECT_LT(1u, dq3.get_allocator().allocate_count());
// Copy assignment
dq3 = *&dq3;
EXPECT_THAT(dq3, ElementsAre(3, 3, 3, 3, 3));
QuicheCircularDeque<
int, 3,
ConfigurableAllocator<int,
/*propagate_on_copy_assignment=*/std::true_type,
/*propagate_on_move_assignment=*/std::true_type,
/*propagate_on_swap=*/std::true_type,
/*equality_result=*/false>>
dq4, dq5;
dq4.assign(dq3.begin(), dq3.end());
dq5 = dq4;
EXPECT_THAT(dq5, ElementsAre(3, 3, 3, 3, 3));
QuicheCircularDeque<
int, 3,
ConfigurableAllocator<int,
/*propagate_on_copy_assignment=*/std::false_type,
/*propagate_on_move_assignment=*/std::true_type,
/*propagate_on_swap=*/std::true_type,
/*equality_result=*/true>>
dq6, dq7;
dq6.assign(dq3.begin(), dq3.end());
dq7 = dq6;
EXPECT_THAT(dq7, ElementsAre(3, 3, 3, 3, 3));
// Move assignment
dq3 = std::move(*&dq3);
EXPECT_THAT(dq3, ElementsAre(3, 3, 3, 3, 3));
ASSERT_TRUE(decltype(dq3.get_allocator())::
propagate_on_container_move_assignment::value);
decltype(dq3) dq8;
dq8 = std::move(*&dq3);
EXPECT_THAT(dq8, ElementsAre(3, 3, 3, 3, 3));
EXPECT_TRUE(dq3.empty());
QuicheCircularDeque<
int, 3,
ConfigurableAllocator<int,
/*propagate_on_copy_assignment=*/std::true_type,
/*propagate_on_move_assignment=*/std::false_type,
/*propagate_on_swap=*/std::true_type,
/*equality_result=*/true>>
dq9, dq10;
dq9.assign(dq8.begin(), dq8.end());
dq10.assign(dq2.begin(), dq2.end());
dq9 = std::move(*&dq10);
EXPECT_THAT(dq9, ElementsAre(2, 2, 2, 2, 2, 2));
EXPECT_TRUE(dq10.empty());
QuicheCircularDeque<
int, 3,
ConfigurableAllocator<int,
/*propagate_on_copy_assignment=*/std::true_type,
/*propagate_on_move_assignment=*/std::false_type,
/*propagate_on_swap=*/std::true_type,
/*equality_result=*/false>>
dq11, dq12;
dq11.assign(dq8.begin(), dq8.end());
dq12.assign(dq2.begin(), dq2.end());
dq11 = std::move(*&dq12);
EXPECT_THAT(dq11, ElementsAre(2, 2, 2, 2, 2, 2));
EXPECT_TRUE(dq12.empty());
}
TEST_F(QuicheCircularDequeTest, Access) {
// at()
// operator[]
// front()
// back()
QuicheCircularDeque<int, 3, CountingAllocator<int>> dq;
dq.push_back(10);
EXPECT_EQ(dq.front(), 10);
EXPECT_EQ(dq.back(), 10);
EXPECT_EQ(dq.at(0), 10);
EXPECT_EQ(dq[0], 10);
dq.front() = 12;
EXPECT_EQ(dq.front(), 12);
EXPECT_EQ(dq.back(), 12);
EXPECT_EQ(dq.at(0), 12);
EXPECT_EQ(dq[0], 12);
const auto& dqref = dq;
EXPECT_EQ(dqref.front(), 12);
EXPECT_EQ(dqref.back(), 12);
EXPECT_EQ(dqref.at(0), 12);
EXPECT_EQ(dqref[0], 12);
dq.pop_front();
EXPECT_TRUE(dqref.empty());
// Push to capacity.
dq.push_back(15);
dq.push_front(5);
dq.push_back(25);
EXPECT_EQ(dq.size(), dq.capacity());
EXPECT_THAT(dq, ElementsAre(5, 15, 25));
EXPECT_LT(&dq.front(), &dq.back());
EXPECT_EQ(dq.front(), 5);
EXPECT_EQ(dq.back(), 25);
EXPECT_EQ(dq.at(0), 5);
EXPECT_EQ(dq.at(1), 15);
EXPECT_EQ(dq.at(2), 25);
EXPECT_EQ(dq[0], 5);
EXPECT_EQ(dq[1], 15);
EXPECT_EQ(dq[2], 25);
// Shift right such that begin=1 and end=0. Data is still not wrapped.
dq.pop_front();
dq.push_back(35);
EXPECT_THAT(dq, ElementsAre(15, 25, 35));
EXPECT_LT(&dq.front(), &dq.back());
EXPECT_EQ(dq.front(), 15);
EXPECT_EQ(dq.back(), 35);
EXPECT_EQ(dq.at(0), 15);
EXPECT_EQ(dq.at(1), 25);
EXPECT_EQ(dq.at(2), 35);
EXPECT_EQ(dq[0], 15);
EXPECT_EQ(dq[1], 25);
EXPECT_EQ(dq[2], 35);
// Shift right such that data is wrapped.
dq.pop_front();
dq.push_back(45);
EXPECT_THAT(dq, ElementsAre(25, 35, 45));
EXPECT_GT(&dq.front(), &dq.back());
EXPECT_EQ(dq.front(), 25);
EXPECT_EQ(dq.back(), 45);
EXPECT_EQ(dq.at(0), 25);
EXPECT_EQ(dq.at(1), 35);
EXPECT_EQ(dq.at(2), 45);
EXPECT_EQ(dq[0], 25);
EXPECT_EQ(dq[1], 35);
EXPECT_EQ(dq[2], 45);
// Shift right again, data is still wrapped.
dq.pop_front();
dq.push_back(55);
EXPECT_THAT(dq, ElementsAre(35, 45, 55));
EXPECT_GT(&dq.front(), &dq.back());
EXPECT_EQ(dq.front(), 35);
EXPECT_EQ(dq.back(), 55);
EXPECT_EQ(dq.at(0), 35);
EXPECT_EQ(dq.at(1), 45);
EXPECT_EQ(dq.at(2), 55);
EXPECT_EQ(dq[0], 35);
EXPECT_EQ(dq[1], 45);
EXPECT_EQ(dq[2], 55);
// Shift right one last time. begin returns to 0. Data is no longer wrapped.
dq.pop_front();
dq.push_back(65);
EXPECT_THAT(dq, ElementsAre(45, 55, 65));
EXPECT_LT(&dq.front(), &dq.back());
EXPECT_EQ(dq.front(), 45);
EXPECT_EQ(dq.back(), 65);
EXPECT_EQ(dq.at(0), 45);
EXPECT_EQ(dq.at(1), 55);
EXPECT_EQ(dq.at(2), 65);
EXPECT_EQ(dq[0], 45);
EXPECT_EQ(dq[1], 55);
EXPECT_EQ(dq[2], 65);
EXPECT_EQ(1u, dq.get_allocator().allocate_count());
}
TEST_F(QuicheCircularDequeTest, Iterate) {
QuicheCircularDeque<int> dq;
EXPECT_EQ(dq.begin(), dq.end());
EXPECT_EQ(dq.cbegin(), dq.cend());
EXPECT_EQ(dq.rbegin(), dq.rend());
EXPECT_EQ(dq.crbegin(), dq.crend());
dq.emplace_back(2);
QuicheCircularDeque<int>::const_iterator citer = dq.begin();
EXPECT_NE(citer, dq.end());
EXPECT_EQ(*citer, 2);
++citer;
EXPECT_EQ(citer, dq.end());
EXPECT_EQ(*dq.begin(), 2);
EXPECT_EQ(*dq.cbegin(), 2);
EXPECT_EQ(*dq.rbegin(), 2);
EXPECT_EQ(*dq.crbegin(), 2);
dq.emplace_front(1);
QuicheCircularDeque<int>::const_reverse_iterator criter = dq.rbegin();
EXPECT_NE(criter, dq.rend());
EXPECT_EQ(*criter, 2);
++criter;
EXPECT_NE(criter, dq.rend());
EXPECT_EQ(*criter, 1);
++criter;
EXPECT_EQ(criter, dq.rend());
EXPECT_EQ(*dq.begin(), 1);
EXPECT_EQ(*dq.cbegin(), 1);
EXPECT_EQ(*dq.rbegin(), 2);
EXPECT_EQ(*dq.crbegin(), 2);
dq.push_back(3);
// Forward iterate.
int expected_value = 1;
for (QuicheCircularDeque<int>::iterator it = dq.begin(); it != dq.end();
++it) {
EXPECT_EQ(expected_value++, *it);
}
expected_value = 1;
for (QuicheCircularDeque<int>::const_iterator it = dq.cbegin();
it != dq.cend(); ++it) {
EXPECT_EQ(expected_value++, *it);
}
// Reverse iterate.
expected_value = 3;
for (QuicheCircularDeque<int>::reverse_iterator it = dq.rbegin();
it != dq.rend(); ++it) {
EXPECT_EQ(expected_value--, *it);
}
expected_value = 3;
for (QuicheCircularDeque<int>::const_reverse_iterator it = dq.crbegin();
it != dq.crend(); ++it) {
EXPECT_EQ(expected_value--, *it);
}
}
TEST_F(QuicheCircularDequeTest, Iterator) {
// Default constructed iterators of the same type compare equal.
EXPECT_EQ(QuicheCircularDeque<int>::iterator(),
QuicheCircularDeque<int>::iterator());
EXPECT_EQ(QuicheCircularDeque<int>::const_iterator(),
QuicheCircularDeque<int>::const_iterator());
EXPECT_EQ(QuicheCircularDeque<int>::reverse_iterator(),
QuicheCircularDeque<int>::reverse_iterator());
EXPECT_EQ(QuicheCircularDeque<int>::const_reverse_iterator(),
QuicheCircularDeque<int>::const_reverse_iterator());
QuicheCircularDeque<QuicheCircularDeque<int>, 3> dqdq = {
{1, 2}, {10, 20, 30}, {100, 200, 300, 400}};
// iter points to {1, 2}
decltype(dqdq)::iterator iter = dqdq.begin();
EXPECT_EQ(iter->size(), 2u);
EXPECT_THAT(*iter, ElementsAre(1, 2));
// citer points to {10, 20, 30}
decltype(dqdq)::const_iterator citer = dqdq.cbegin() + 1;
EXPECT_NE(*iter, *citer);
EXPECT_EQ(citer->size(), 3u);
int x = 10;
for (auto it = citer->begin(); it != citer->end(); ++it) {
EXPECT_EQ(*it, x);
x += 10;
}
EXPECT_LT(iter, citer);
EXPECT_LE(iter, iter);
EXPECT_GT(citer, iter);
EXPECT_GE(citer, citer);
// iter points to {100, 200, 300, 400}
iter += 2;
EXPECT_NE(*iter, *citer);
EXPECT_EQ(iter->size(), 4u);
for (int i = 1; i <= 4; ++i) {
EXPECT_EQ(iter->begin()[i - 1], i * 100);
}
EXPECT_LT(citer, iter);
EXPECT_LE(iter, iter);
EXPECT_GT(iter, citer);
EXPECT_GE(citer, citer);
// iter points to {10, 20, 30}. (same as citer)
iter -= 1;
EXPECT_EQ(*iter, *citer);
EXPECT_EQ(iter->size(), 3u);
x = 10;
for (auto it = iter->begin(); it != iter->end();) {
EXPECT_EQ(*(it++), x);
x += 10;
}
x = 30;
for (auto it = iter->begin() + 2; it != iter->begin();) {
EXPECT_EQ(*(it--), x);
x -= 10;
}
}
TEST_F(QuicheCircularDequeTest, Resize) {
QuicheCircularDeque<int, 3, CountingAllocator<int>> dq;
dq.resize(8);
EXPECT_THAT(dq, ElementsAre(0, 0, 0, 0, 0, 0, 0, 0));
EXPECT_EQ(1u, dq.get_allocator().allocate_count());
dq.resize(10, 5);
EXPECT_THAT(dq, ElementsAre(0, 0, 0, 0, 0, 0, 0, 0, 5, 5));
QuicheCircularDeque<int, 3, CountingAllocator<int>> dq2 = dq;
for (size_t new_size = dq.size(); new_size != 0; --new_size) {
dq.resize(new_size);
EXPECT_TRUE(
std::equal(dq.begin(), dq.end(), dq2.begin(), dq2.begin() + new_size));
}
dq.resize(0);
EXPECT_TRUE(dq.empty());
// Resize when data is wrapped.
ASSERT_EQ(dq2.size(), dq2.capacity());
while (dq2.size() < dq2.capacity()) {
dq2.push_back(5);
}
// Shift left once such that data is wrapped.
ASSERT_LT(&dq2.front(), &dq2.back());
dq2.pop_back();
dq2.push_front(-5);
ASSERT_GT(&dq2.front(), &dq2.back());
EXPECT_EQ(-5, dq2.front());
EXPECT_EQ(5, dq2.back());
dq2.resize(dq2.size() + 1, 10);
// Data should be unwrapped after the resize.
ASSERT_LT(&dq2.front(), &dq2.back());
EXPECT_EQ(-5, dq2.front());
EXPECT_EQ(10, dq2.back());
EXPECT_EQ(5, *(dq2.rbegin() + 1));
}
namespace {
class Foo {
public:
Foo() : Foo(0xF00) {}
explicit Foo(int i) : i_(new int(i)) {}
~Foo() {
if (i_ != nullptr) {
delete i_;
// Do not set i_ to nullptr such that if the container calls destructor
// multiple times, asan can detect it.
}
}
Foo(const Foo& other) : i_(new int(*other.i_)) {}
Foo(Foo&& other) = delete;
void Set(int i) { *i_ = i; }
int i() const { return *i_; }
friend bool operator==(const Foo& lhs, const Foo& rhs) {
return lhs.i() == rhs.i();
}
friend std::ostream& operator<<(std::ostream& os, const Foo& foo) {
return os << "Foo(" << foo.i() << ")";
}
private:
// By pointing i_ to a dynamically allocated integer, a memory leak will be
// reported if the container forget to properly destruct this object.
int* i_ = nullptr;
};
} // namespace
TEST_F(QuicheCircularDequeTest, RelocateNonTriviallyCopyable) {
// When relocating non-trivially-copyable objects:
// - Move constructor is preferred, if available.
// - Copy constructor is used otherwise.
{
// Move construct in Relocate.
using MoveConstructible = std::unique_ptr<Foo>;
ASSERT_FALSE(std::is_trivially_copyable<MoveConstructible>::value);
ASSERT_TRUE(std::is_move_constructible<MoveConstructible>::value);
QuicheCircularDeque<MoveConstructible, 3,
CountingAllocator<MoveConstructible>>
dq1;
dq1.resize(3);
EXPECT_EQ(dq1.size(), dq1.capacity());
EXPECT_EQ(1u, dq1.get_allocator().allocate_count());
dq1.emplace_back(new Foo(0xF1)); // Cause existing elements to relocate.
EXPECT_EQ(4u, dq1.size());
EXPECT_EQ(2u, dq1.get_allocator().allocate_count());
EXPECT_EQ(dq1[0], nullptr);
EXPECT_EQ(dq1[1], nullptr);
EXPECT_EQ(dq1[2], nullptr);
EXPECT_EQ(dq1[3]->i(), 0xF1);
}
{
// Copy construct in Relocate.
using NonMoveConstructible = Foo;
ASSERT_FALSE(std::is_trivially_copyable<NonMoveConstructible>::value);
ASSERT_FALSE(std::is_move_constructible<NonMoveConstructible>::value);
QuicheCircularDeque<NonMoveConstructible, 3,
CountingAllocator<NonMoveConstructible>>
dq2;
dq2.resize(3);
EXPECT_EQ(dq2.size(), dq2.capacity());
EXPECT_EQ(1u, dq2.get_allocator().allocate_count());
dq2.emplace_back(0xF1); // Cause existing elements to relocate.
EXPECT_EQ(4u, dq2.size());
EXPECT_EQ(2u, dq2.get_allocator().allocate_count());
EXPECT_EQ(dq2[0].i(), 0xF00);
EXPECT_EQ(dq2[1].i(), 0xF00);
EXPECT_EQ(dq2[2].i(), 0xF00);
EXPECT_EQ(dq2[3].i(), 0xF1);
}
}
TEST_F(QuicheCircularDequeTest, PushPop) {
// (push|pop|emplace)_(back|front)
{
QuicheCircularDeque<Foo, 4, CountingAllocator<Foo>> dq(4);
for (size_t i = 0; i < dq.size(); ++i) {
dq[i].Set(i + 1);
}
QUICHE_LOG(INFO) << "dq initialized to " << dq;
EXPECT_THAT(dq, ElementsAre(Foo(1), Foo(2), Foo(3), Foo(4)));
ShiftLeft(&dq, false);
QUICHE_LOG(INFO) << "shift left once : " << dq;
EXPECT_THAT(dq, ElementsAre(Foo(2), Foo(3), Foo(4), Foo(1)));
ShiftLeft(&dq, true);
QUICHE_LOG(INFO) << "shift left twice: " << dq;
EXPECT_THAT(dq, ElementsAre(Foo(3), Foo(4), Foo(1), Foo(2)));
ASSERT_GT(&dq.front(), &dq.back());
// dq destructs with wrapped data.
}
{
QuicheCircularDeque<Foo, 4, CountingAllocator<Foo>> dq1(4);
for (size_t i = 0; i < dq1.size(); ++i) {
dq1[i].Set(i + 1);
}
QUICHE_LOG(INFO) << "dq1 initialized to " << dq1;
EXPECT_THAT(dq1, ElementsAre(Foo(1), Foo(2), Foo(3), Foo(4)));
ShiftRight(&dq1, false);
QUICHE_LOG(INFO) << "shift right once : " << dq1;
EXPECT_THAT(dq1, ElementsAre(Foo(4), Foo(1), Foo(2), Foo(3)));
ShiftRight(&dq1, true);
QUICHE_LOG(INFO) << "shift right twice: " << dq1;
EXPECT_THAT(dq1, ElementsAre(Foo(3), Foo(4), Foo(1), Foo(2)));
ASSERT_GT(&dq1.front(), &dq1.back());
// dq1 destructs with wrapped data.
}
{ // Pop n elements from front.
QuicheCircularDeque<Foo, 4, CountingAllocator<Foo>> dq2(5);
for (size_t i = 0; i < dq2.size(); ++i) {
dq2[i].Set(i + 1);
}
EXPECT_THAT(dq2, ElementsAre(Foo(1), Foo(2), Foo(3), Foo(4), Foo(5)));
EXPECT_EQ(2u, dq2.pop_front_n(2));
EXPECT_THAT(dq2, ElementsAre(Foo(3), Foo(4), Foo(5)));
EXPECT_EQ(3u, dq2.pop_front_n(100));
EXPECT_TRUE(dq2.empty());
}
{ // Pop n elements from back.
QuicheCircularDeque<Foo, 4, CountingAllocator<Foo>> dq3(6);
for (size_t i = 0; i < dq3.size(); ++i) {
dq3[i].Set(i + 1);
}
EXPECT_THAT(dq3,
ElementsAre(Foo(1), Foo(2), Foo(3), Foo(4), Foo(5), Foo(6)));
ShiftRight(&dq3, true);
ShiftRight(&dq3, true);
ShiftRight(&dq3, true);
EXPECT_THAT(dq3,
ElementsAre(Foo(4), Foo(5), Foo(6), Foo(1), Foo(2), Foo(3)));
EXPECT_EQ(2u, dq3.pop_back_n(2));
EXPECT_THAT(dq3, ElementsAre(Foo(4), Foo(5), Foo(6), Foo(1)));
EXPECT_EQ(2u, dq3.pop_back_n(2));
EXPECT_THAT(dq3, ElementsAre(Foo(4), Foo(5)));
}
}
TEST_F(QuicheCircularDequeTest, Allocation) {
CountingAllocator<int> alloc;
{
QuicheCircularDeque<int, 3, CountingAllocator<int>> dq(alloc);
EXPECT_EQ(alloc, dq.get_allocator());
EXPECT_EQ(0u, dq.size());
EXPECT_EQ(0u, dq.capacity());
EXPECT_EQ(0u, alloc.allocate_count());
EXPECT_EQ(0u, alloc.deallocate_count());
for (int i = 1; i <= 18; ++i) {
SCOPED_TRACE(testing::Message()
<< "i=" << i << ", capacity_b4_push=" << dq.capacity());
dq.push_back(i);
EXPECT_EQ(i, static_cast<int>(dq.size()));
const size_t capacity = 3 + (i - 1) / 3 * 3;
EXPECT_EQ(capacity, dq.capacity());
EXPECT_EQ(capacity / 3, alloc.allocate_count());
EXPECT_EQ(capacity / 3 - 1, alloc.deallocate_count());
}
dq.push_back(19);
EXPECT_EQ(22u, dq.capacity()); // 18 + 18 / 4
EXPECT_EQ(7u, alloc.allocate_count());
EXPECT_EQ(6u, alloc.deallocate_count());
}
EXPECT_EQ(7u, alloc.deallocate_count());
}
} // namespace
} // namespace test
} // namespace quiche
// Use a non-quiche namespace to make sure swap can be used via ADL.
namespace {
template <typename T>
using SwappableAllocator = quiche::test::ConfigurableAllocator<
T,
/*propagate_on_copy_assignment=*/std::true_type,
/*propagate_on_move_assignment=*/std::true_type,
/*propagate_on_swap=*/std::true_type,
/*equality_result=*/true>;
template <typename T>
using UnswappableEqualAllocator = quiche::test::ConfigurableAllocator<
T,
/*propagate_on_copy_assignment=*/std::true_type,
/*propagate_on_move_assignment=*/std::true_type,
/*propagate_on_swap=*/std::false_type,
/*equality_result=*/true>;
template <typename T>
using UnswappableUnequalAllocator = quiche::test::ConfigurableAllocator<
T,
/*propagate_on_copy_assignment=*/std::true_type,
/*propagate_on_move_assignment=*/std::true_type,
/*propagate_on_swap=*/std::false_type,
/*equality_result=*/false>;
using quiche::test::QuicheCircularDequeTest;
TEST_F(QuicheCircularDequeTest, Swap) {
using std::swap;
quiche::QuicheCircularDeque<int64_t, 3, SwappableAllocator<int64_t>> dq1, dq2;
dq1.push_back(10);
dq1.push_back(11);
dq2.push_back(20);
swap(dq1, dq2);
EXPECT_THAT(dq1, ElementsAre(20));
EXPECT_THAT(dq2, ElementsAre(10, 11));
quiche::QuicheCircularDeque<char, 3, UnswappableEqualAllocator<char>> dq3,
dq4;
dq3 = {1, 2, 3, 4, 5};
dq4 = {6, 7, 8, 9, 0};
swap(dq3, dq4);
EXPECT_THAT(dq3, ElementsAre(6, 7, 8, 9, 0));
EXPECT_THAT(dq4, ElementsAre(1, 2, 3, 4, 5));
quiche::QuicheCircularDeque<int, 3, UnswappableUnequalAllocator<int>> dq5,
dq6;
dq6.push_front(4);
// Using UnswappableUnequalAllocator is ok as long as swap is not called.
dq5.assign(dq6.begin(), dq6.end());
EXPECT_THAT(dq5, ElementsAre(4));
// Undefined behavior to swap between two containers with unequal allocators.
EXPECT_QUICHE_DEBUG_DEATH(swap(dq5, dq6), "Undefined swap behavior");
}
} // namespace