| // 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 "common/quiche_circular_deque.h" | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <list> | |
| #include <memory> | |
| #include <type_traits> | |
| #include "common/platform/api/quiche_logging.h" | |
| #include "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 |