| // 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 |